summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore11
-rw-r--r--.gitmodules0
-rw-r--r--.mailmap8
-rw-r--r--CHANGES.md64
-rw-r--r--Gemfile58
-rw-r--r--LICENSE674
-rw-r--r--README.md149
-rw-r--r--Rakefile62
-rw-r--r--Vagrantfile53
-rwxr-xr-xbin/debug.sh29
-rw-r--r--bin/node_init86
-rwxr-xr-xbin/puppet_command313
-rwxr-xr-xbin/run_tests515
-rw-r--r--contrib/README.md9
-rw-r--r--contrib/commit-template.txt7
-rw-r--r--contrib/offlineimaprc.example.org24
-rw-r--r--doc/details/couchdb.md74
-rw-r--r--doc/details/development.md359
-rw-r--r--doc/details/en.haml4
-rw-r--r--doc/details/faq.md65
-rw-r--r--doc/details/under-the-hood.md40
-rw-r--r--doc/details/webapp.md282
-rw-r--r--doc/en.md85
-rw-r--r--doc/guide/commands.md419
-rw-r--r--doc/guide/config.md263
-rw-r--r--doc/guide/en.haml4
-rw-r--r--doc/guide/environments.md75
-rw-r--r--doc/guide/keys-and-certificates.md194
-rw-r--r--doc/guide/miscellaneous.md14
-rw-r--r--doc/guide/nodes.md187
-rw-r--r--doc/service-diagram.odgbin0 -> 12131 bytes
-rw-r--r--doc/service-diagram.pngbin0 -> 25988 bytes
-rw-r--r--doc/troubleshooting/en.haml3
-rw-r--r--doc/troubleshooting/known-issues.md115
-rw-r--r--doc/troubleshooting/tests.md70
-rw-r--r--doc/troubleshooting/vagrant.md45
-rw-r--r--doc/troubleshooting/where-to-look.md249
-rw-r--r--doc/tutorials/configure-provider.md31
-rw-r--r--doc/tutorials/en.haml4
-rw-r--r--doc/tutorials/quick-start.md385
-rw-r--r--doc/tutorials/single-node-email.md282
-rw-r--r--hiera.yaml6
-rw-r--r--leap-debug-remote.sh23
-rw-r--r--lib/leap_cli/commands/README11
-rw-r--r--lib/leap_cli/commands/ca.rb541
-rw-r--r--lib/leap_cli/commands/clean.rb16
-rw-r--r--lib/leap_cli/commands/compile.rb531
-rw-r--r--lib/leap_cli/commands/db.rb86
-rw-r--r--lib/leap_cli/commands/deploy.rb374
-rw-r--r--lib/leap_cli/commands/env.rb76
-rw-r--r--lib/leap_cli/commands/facts.rb100
-rw-r--r--lib/leap_cli/commands/info.rb15
-rw-r--r--lib/leap_cli/commands/inspect.rb144
-rw-r--r--lib/leap_cli/commands/list.rb132
-rw-r--r--lib/leap_cli/commands/node.rb188
-rw-r--r--lib/leap_cli/commands/node_init.rb169
-rw-r--r--lib/leap_cli/commands/ssh.rb225
-rw-r--r--lib/leap_cli/commands/test.rb74
-rw-r--r--lib/leap_cli/commands/user.rb136
-rw-r--r--lib/leap_cli/commands/util.rb50
-rw-r--r--lib/leap_cli/commands/vagrant.rb180
-rw-r--r--lib/leap_cli/macros.rb16
-rw-r--r--lib/leap_cli/macros/core.rb92
-rw-r--r--lib/leap_cli/macros/files.rb124
-rw-r--r--lib/leap_cli/macros/haproxy.rb73
-rw-r--r--lib/leap_cli/macros/hosts.rb90
-rw-r--r--lib/leap_cli/macros/keys.rb97
-rw-r--r--lib/leap_cli/macros/nodes.rb88
-rw-r--r--lib/leap_cli/macros/provider.rb90
-rw-r--r--lib/leap_cli/macros/secrets.rb39
-rw-r--r--lib/leap_cli/macros/stunnel.rb106
-rw-r--r--platform.rb119
-rw-r--r--provider_base/README9
-rw-r--r--provider_base/common.json97
-rw-r--r--provider_base/files/branding/head.scss1
-rw-r--r--provider_base/files/branding/tail.scss1
-rw-r--r--provider_base/files/service-definitions/provider.json.erb16
-rw-r--r--provider_base/files/service-definitions/v1/eip-service.json.erb55
-rw-r--r--provider_base/files/service-definitions/v1/smtp-service.json.erb29
-rw-r--r--provider_base/files/service-definitions/v1/soledad-service.json.erb29
-rw-r--r--provider_base/provider.json64
-rw-r--r--provider_base/services/_api_tester.json13
-rw-r--r--provider_base/services/_couchdb_mirror.json22
-rw-r--r--provider_base/services/_couchdb_multimaster.json24
-rw-r--r--provider_base/services/couchdb.json49
-rw-r--r--provider_base/services/couchdb.rb27
-rw-r--r--provider_base/services/dns.json14
-rw-r--r--provider_base/services/monitor.json29
-rw-r--r--provider_base/services/monitor.rb3
-rw-r--r--provider_base/services/mx.json53
-rw-r--r--provider_base/services/mx.rb1
-rw-r--r--provider_base/services/obfsproxy.json9
-rw-r--r--provider_base/services/openvpn.json45
-rw-r--r--provider_base/services/soledad.json21
-rw-r--r--provider_base/services/soledad.rb3
-rw-r--r--provider_base/services/static.json20
-rw-r--r--provider_base/services/tor.json15
-rw-r--r--provider_base/services/webapp.json93
-rw-r--r--provider_base/tags/development.json3
-rw-r--r--provider_base/tags/local.json3
-rw-r--r--provider_base/tags/production.json3
-rw-r--r--provider_base/templates/common.json3
-rw-r--r--provider_base/templates/couchdb.json5
-rw-r--r--provider_base/templates/openvpn.json7
-rw-r--r--provider_base/test/openvpn/client.ovpn.erb28
-rwxr-xr-xpuppet/bin/apply_on_node.sh30
-rw-r--r--puppet/hiera.yaml15
-rw-r--r--puppet/lib/puppet/parser/functions/create_resources_hash_from.rb116
-rw-r--r--puppet/lib/puppet/parser/functions/sorted_json.rb47
-rw-r--r--puppet/lib/puppet/parser/functions/sorted_yaml.rb400
-rw-r--r--puppet/manifests/site.pp60
-rw-r--r--puppet/modules/apache/.gitignore6
-rw-r--r--puppet/modules/apache/.rspec2
-rw-r--r--puppet/modules/apache/Gemfile13
-rw-r--r--puppet/modules/apache/LICENSE674
-rw-r--r--puppet/modules/apache/Puppetfile15
-rw-r--r--puppet/modules/apache/README.md233
-rw-r--r--puppet/modules/apache/Rakefile26
-rw-r--r--puppet/modules/apache/files/conf.d/CentOS/ssl.conf76
-rw-r--r--puppet/modules/apache/files/conf.d/CentOS/welcome.conf10
-rw-r--r--puppet/modules/apache/files/conf.d/Debian/charset6
-rw-r--r--puppet/modules/apache/files/conf.d/Debian/security50
-rw-r--r--puppet/modules/apache/files/conf.d/Debian/ssl.conf1
-rw-r--r--puppet/modules/apache/files/conf.d/do_includes.conf5
-rw-r--r--puppet/modules/apache/files/conf.d/git.conf5
-rw-r--r--puppet/modules/apache/files/conf.d/mozilla_autoconfig.conf6
-rw-r--r--puppet/modules/apache/files/conf.d/status.conf24
-rw-r--r--puppet/modules/apache/files/conf.d/vhosts.conf8
-rw-r--r--puppet/modules/apache/files/config/Debian.jessie/apache2.conf221
-rw-r--r--puppet/modules/apache/files/config/Debian.wheezy/apache2.conf268
-rw-r--r--puppet/modules/apache/files/config/Debian/apache2.conf230
-rw-r--r--puppet/modules/apache/files/config/OpenBSD/httpd.conf1120
-rw-r--r--puppet/modules/apache/files/include.d/defaults.inc5
-rw-r--r--puppet/modules/apache/files/include.d/joomla.inc30
-rw-r--r--puppet/modules/apache/files/include.d/silverstripe.inc17
-rw-r--r--puppet/modules/apache/files/itk_plus/conf.d/CentOS/ssl.conf75
-rw-r--r--puppet/modules/apache/files/modules.d/Gentoo/00_default_settings.conf105
-rw-r--r--puppet/modules/apache/files/modules.d/Gentoo/00_error_documents.conf66
-rw-r--r--puppet/modules/apache/files/modules.d/Gentoo/00_languages.conf137
-rw-r--r--puppet/modules/apache/files/modules.d/Gentoo/00_mod_autoindex.conf83
-rw-r--r--puppet/modules/apache/files/modules.d/Gentoo/00_mod_info.conf14
-rw-r--r--puppet/modules/apache/files/modules.d/Gentoo/00_mod_log_config.conf35
-rw-r--r--puppet/modules/apache/files/modules.d/Gentoo/00_mod_mime.conf55
-rw-r--r--puppet/modules/apache/files/modules.d/Gentoo/00_mod_status.conf19
-rw-r--r--puppet/modules/apache/files/modules.d/Gentoo/00_mod_userdir.conf40
-rw-r--r--puppet/modules/apache/files/modules.d/Gentoo/00_mpm.conf102
-rw-r--r--puppet/modules/apache/files/modules.d/Gentoo/10_mod_mem_cache.conf10
-rw-r--r--puppet/modules/apache/files/modules.d/Gentoo/40_mod_ssl.conf65
-rw-r--r--puppet/modules/apache/files/modules.d/Gentoo/45_mod_dav.conf56
-rw-r--r--puppet/modules/apache/files/modules.d/Gentoo/46_mod_ldap.conf29
-rw-r--r--puppet/modules/apache/files/modules.d/Gentoo/70_mod_php5.conf18
-rwxr-xr-xpuppet/modules/apache/files/munin/apache_activity99
-rw-r--r--puppet/modules/apache/files/scripts/OpenBSD/bin/apache_logrotate.sh7
-rw-r--r--puppet/modules/apache/files/scripts/OpenBSD/bin/restart_apache.sh6
-rw-r--r--puppet/modules/apache/files/scripts/OpenBSD/bin/restart_apache_ssl.sh6
-rw-r--r--puppet/modules/apache/files/service/CentOS/httpd22
-rw-r--r--puppet/modules/apache/files/service/CentOS/httpd.itk23
-rw-r--r--puppet/modules/apache/files/service/CentOS/httpd.itk_plus24
-rw-r--r--puppet/modules/apache/files/service/CentOS/httpd.worker22
-rw-r--r--puppet/modules/apache/files/vhosts.d/CentOS/0-default.conf11
-rw-r--r--puppet/modules/apache/files/vhosts.d/Debian/0-default.conf41
-rw-r--r--puppet/modules/apache/files/vhosts.d/Gentoo/0-default.conf51
-rw-r--r--puppet/modules/apache/files/vhosts.d/Gentoo/default_vhost.include79
-rw-r--r--puppet/modules/apache/files/vhosts.d/OpenBSD/0-default.conf8
-rw-r--r--puppet/modules/apache/lib/facter/apache_version.rb28
-rw-r--r--puppet/modules/apache/lib/puppet/parser/functions/guess_apache_version.rb39
-rw-r--r--puppet/modules/apache/lib/puppet/parser/functions/htpasswd_sha1.rb8
-rw-r--r--puppet/modules/apache/manifests/base.pp75
-rw-r--r--puppet/modules/apache/manifests/base/itk.pp6
-rw-r--r--puppet/modules/apache/manifests/centos.pp86
-rw-r--r--puppet/modules/apache/manifests/centos/itk.pp10
-rw-r--r--puppet/modules/apache/manifests/centos/itk_plus.pp20
-rw-r--r--puppet/modules/apache/manifests/centos/module.pp30
-rw-r--r--puppet/modules/apache/manifests/centos/worker.pp5
-rw-r--r--puppet/modules/apache/manifests/config/file.pp106
-rw-r--r--puppet/modules/apache/manifests/config/global.pp18
-rw-r--r--puppet/modules/apache/manifests/config/include.pp17
-rw-r--r--puppet/modules/apache/manifests/debian.pp44
-rw-r--r--puppet/modules/apache/manifests/debian/itk.pp9
-rw-r--r--puppet/modules/apache/manifests/debian/module.pp48
-rw-r--r--puppet/modules/apache/manifests/defaultdavdbdir.pp17
-rw-r--r--puppet/modules/apache/manifests/defaultphpdirs.pp31
-rw-r--r--puppet/modules/apache/manifests/file.pp15
-rw-r--r--puppet/modules/apache/manifests/file/readonly.pp12
-rw-r--r--puppet/modules/apache/manifests/file/rw.pp13
-rw-r--r--puppet/modules/apache/manifests/gentoo.pp39
-rw-r--r--puppet/modules/apache/manifests/gentoo/module.pp30
-rw-r--r--puppet/modules/apache/manifests/htpasswd_user.pp34
-rw-r--r--puppet/modules/apache/manifests/include/joomla.pp3
-rw-r--r--puppet/modules/apache/manifests/include/mod_fcgid.pp7
-rw-r--r--puppet/modules/apache/manifests/include/silverstripe.pp3
-rw-r--r--puppet/modules/apache/manifests/includes.pp5
-rw-r--r--puppet/modules/apache/manifests/init.pp44
-rw-r--r--puppet/modules/apache/manifests/itk.pp11
-rw-r--r--puppet/modules/apache/manifests/itk/lock.pp4
-rw-r--r--puppet/modules/apache/manifests/itk_plus.pp10
-rw-r--r--puppet/modules/apache/manifests/itk_plus/lock.pp4
-rw-r--r--puppet/modules/apache/manifests/logrotate/centos.pp10
-rw-r--r--puppet/modules/apache/manifests/logrotate/centos/vhosts.pp11
-rw-r--r--puppet/modules/apache/manifests/mod_dav_svn.pp7
-rw-r--r--puppet/modules/apache/manifests/mod_macro.pp7
-rw-r--r--puppet/modules/apache/manifests/module.pp35
-rw-r--r--puppet/modules/apache/manifests/module/alias.pp14
-rw-r--r--puppet/modules/apache/manifests/module/auth_basic.pp6
-rw-r--r--puppet/modules/apache/manifests/module/authn_core.pp6
-rw-r--r--puppet/modules/apache/manifests/module/authn_file.pp6
-rw-r--r--puppet/modules/apache/manifests/module/authz_core.pp7
-rw-r--r--puppet/modules/apache/manifests/module/authz_host.pp6
-rw-r--r--puppet/modules/apache/manifests/module/authz_user.pp6
-rw-r--r--puppet/modules/apache/manifests/module/cgi.pp6
-rw-r--r--puppet/modules/apache/manifests/module/dir.pp6
-rw-r--r--puppet/modules/apache/manifests/module/env.pp7
-rw-r--r--puppet/modules/apache/manifests/module/expires.pp5
-rw-r--r--puppet/modules/apache/manifests/module/headers.pp6
-rw-r--r--puppet/modules/apache/manifests/module/mime.pp6
-rw-r--r--puppet/modules/apache/manifests/module/mpm_event.pp7
-rw-r--r--puppet/modules/apache/manifests/module/mpm_prefork.pp6
-rw-r--r--puppet/modules/apache/manifests/module/negotiation.pp6
-rw-r--r--puppet/modules/apache/manifests/module/php5.pp6
-rw-r--r--puppet/modules/apache/manifests/module/removeip.pp6
-rw-r--r--puppet/modules/apache/manifests/module/rewrite.pp6
-rw-r--r--puppet/modules/apache/manifests/module/socache_shmcb.pp6
-rw-r--r--puppet/modules/apache/manifests/module/status.pp6
-rw-r--r--puppet/modules/apache/manifests/mozilla_autoconfig.pp37
-rw-r--r--puppet/modules/apache/manifests/munin.pp12
-rw-r--r--puppet/modules/apache/manifests/noiplog.pp5
-rw-r--r--puppet/modules/apache/manifests/openbsd.pp75
-rw-r--r--puppet/modules/apache/manifests/package.pp32
-rw-r--r--puppet/modules/apache/manifests/package/itk.pp5
-rw-r--r--puppet/modules/apache/manifests/sftponly.pp5
-rw-r--r--puppet/modules/apache/manifests/sftponly/centos.pp10
-rw-r--r--puppet/modules/apache/manifests/ssl.pp13
-rw-r--r--puppet/modules/apache/manifests/ssl/base.pp15
-rw-r--r--puppet/modules/apache/manifests/ssl/centos.pp12
-rw-r--r--puppet/modules/apache/manifests/ssl/debian.pp4
-rw-r--r--puppet/modules/apache/manifests/ssl/itk.pp8
-rw-r--r--puppet/modules/apache/manifests/ssl/itk/centos.pp6
-rw-r--r--puppet/modules/apache/manifests/ssl/itk_plus.pp6
-rw-r--r--puppet/modules/apache/manifests/ssl/itk_plus/centos.pp11
-rw-r--r--puppet/modules/apache/manifests/ssl/openbsd.pp18
-rw-r--r--puppet/modules/apache/manifests/status.pp13
-rw-r--r--puppet/modules/apache/manifests/status/base.pp1
-rw-r--r--puppet/modules/apache/manifests/status/centos.pp5
-rw-r--r--puppet/modules/apache/manifests/status/debian.pp4
-rw-r--r--puppet/modules/apache/manifests/vhost.pp127
-rw-r--r--puppet/modules/apache/manifests/vhost/davdbdir.pp40
-rw-r--r--puppet/modules/apache/manifests/vhost/file.pp151
-rw-r--r--puppet/modules/apache/manifests/vhost/file/documentrootdir.pp24
-rw-r--r--puppet/modules/apache/manifests/vhost/file/documentrootfile.pp27
-rw-r--r--puppet/modules/apache/manifests/vhost/gitweb.pp59
-rw-r--r--puppet/modules/apache/manifests/vhost/modperl.pp153
-rw-r--r--puppet/modules/apache/manifests/vhost/passenger.pp139
-rw-r--r--puppet/modules/apache/manifests/vhost/php/drupal.pp144
-rw-r--r--puppet/modules/apache/manifests/vhost/php/gallery2.pp141
-rw-r--r--puppet/modules/apache/manifests/vhost/php/global_exec_bin_dir.pp9
-rw-r--r--puppet/modules/apache/manifests/vhost/php/joomla.pp174
-rw-r--r--puppet/modules/apache/manifests/vhost/php/mediawiki.pp106
-rw-r--r--puppet/modules/apache/manifests/vhost/php/safe_mode_bin.pp17
-rw-r--r--puppet/modules/apache/manifests/vhost/php/silverstripe.pp119
-rw-r--r--puppet/modules/apache/manifests/vhost/php/simplemachine.pp125
-rw-r--r--puppet/modules/apache/manifests/vhost/php/spip.pp114
-rw-r--r--puppet/modules/apache/manifests/vhost/php/standard.pp304
-rw-r--r--puppet/modules/apache/manifests/vhost/php/typo3.pp150
-rw-r--r--puppet/modules/apache/manifests/vhost/php/webapp.pp148
-rw-r--r--puppet/modules/apache/manifests/vhost/php/wordpress.pp123
-rw-r--r--puppet/modules/apache/manifests/vhost/phpdirs.pp39
-rw-r--r--puppet/modules/apache/manifests/vhost/proxy.pp67
-rw-r--r--puppet/modules/apache/manifests/vhost/redirect.pp56
-rw-r--r--puppet/modules/apache/manifests/vhost/static.pp86
-rw-r--r--puppet/modules/apache/manifests/vhost/template.pp158
-rw-r--r--puppet/modules/apache/manifests/vhost/webdav.pp126
-rw-r--r--puppet/modules/apache/manifests/vhost/webdir.pp130
-rw-r--r--puppet/modules/apache/manifests/webdav.pp8
-rw-r--r--puppet/modules/apache/manifests/worker.pp5
-rw-r--r--puppet/modules/apache/spec/classes/init_spec.rb43
-rw-r--r--puppet/modules/apache/spec/defines/vhost_file_spec.rb131
-rw-r--r--puppet/modules/apache/spec/defines/vhost_php_drupal_spec.rb187
-rw-r--r--puppet/modules/apache/spec/defines/vhost_php_gallery2_spec.rb162
-rw-r--r--puppet/modules/apache/spec/defines/vhost_php_joomla_spec.rb279
-rw-r--r--puppet/modules/apache/spec/defines/vhost_php_standard_spec.rb534
-rw-r--r--puppet/modules/apache/spec/defines/vhost_php_webapp_spec.rb261
-rw-r--r--puppet/modules/apache/spec/defines/vhost_php_wordpress_spec.rb171
-rw-r--r--puppet/modules/apache/spec/defines/vhost_spec.rb202
-rw-r--r--puppet/modules/apache/spec/defines/vhost_static_spec.rb54
-rw-r--r--puppet/modules/apache/spec/defines/vhost_template_spec.rb297
-rw-r--r--puppet/modules/apache/spec/functions/guess_apache_version.rb50
-rw-r--r--puppet/modules/apache/spec/spec_helper.rb13
-rw-r--r--puppet/modules/apache/templates/default/default_index.erb13
-rw-r--r--puppet/modules/apache/templates/include.d/ssl_defaults.inc.erb78
-rw-r--r--puppet/modules/apache/templates/itk_plus/CentOS/00-listen-ssl.conf.erb6
-rw-r--r--puppet/modules/apache/templates/itk_plus/CentOS/00-listen.conf.erb8
-rw-r--r--puppet/modules/apache/templates/vhosts/0-default_ssl.conf.erb21
-rw-r--r--puppet/modules/apache/templates/vhosts/default.erb44
-rw-r--r--puppet/modules/apache/templates/vhosts/gitweb/partial.erb16
-rw-r--r--puppet/modules/apache/templates/vhosts/itk_plus.erb6
-rw-r--r--puppet/modules/apache/templates/vhosts/itk_plus/partial.erb31
-rw-r--r--puppet/modules/apache/templates/vhosts/partials/authentication.erb6
-rw-r--r--puppet/modules/apache/templates/vhosts/partials/header_default.erb22
-rw-r--r--puppet/modules/apache/templates/vhosts/partials/logs.erb18
-rw-r--r--puppet/modules/apache/templates/vhosts/partials/mod_security.erb27
-rw-r--r--puppet/modules/apache/templates/vhosts/partials/php_settings.erb20
-rw-r--r--puppet/modules/apache/templates/vhosts/partials/ssl.erb8
-rw-r--r--puppet/modules/apache/templates/vhosts/partials/std_override_options.erb4
-rw-r--r--puppet/modules/apache/templates/vhosts/passenger/partial.erb7
-rw-r--r--puppet/modules/apache/templates/vhosts/perl/partial.erb14
-rw-r--r--puppet/modules/apache/templates/vhosts/php/partial.erb5
-rw-r--r--puppet/modules/apache/templates/vhosts/php_drupal/partial.erb22
-rw-r--r--puppet/modules/apache/templates/vhosts/php_gallery2/partial.erb14
-rw-r--r--puppet/modules/apache/templates/vhosts/php_joomla/partial.erb30
-rw-r--r--puppet/modules/apache/templates/vhosts/php_mediawiki/partial.erb7
-rw-r--r--puppet/modules/apache/templates/vhosts/php_silverstripe/partial.erb12
-rw-r--r--puppet/modules/apache/templates/vhosts/php_typo3/partial.erb10
-rw-r--r--puppet/modules/apache/templates/vhosts/php_wordpress/partial.erb19
-rw-r--r--puppet/modules/apache/templates/vhosts/proxy/partial.erb8
-rw-r--r--puppet/modules/apache/templates/vhosts/redirect/partial.erb1
-rw-r--r--puppet/modules/apache/templates/vhosts/static/partial.erb4
-rw-r--r--puppet/modules/apache/templates/vhosts/webdav/partial.erb21
-rw-r--r--puppet/modules/apache/templates/webfiles/autoconfig/config.shtml.erb58
-rw-r--r--puppet/modules/apt/.gitignore12
-rw-r--r--puppet/modules/apt/.gitlab-ci.yml12
-rw-r--r--puppet/modules/apt/Gemfile13
-rw-r--r--puppet/modules/apt/LICENSE674
-rw-r--r--puppet/modules/apt/README602
-rw-r--r--puppet/modules/apt/Rakefile19
-rw-r--r--puppet/modules/apt/files/02show_upgraded4
-rw-r--r--puppet/modules/apt/files/03clean4
-rw-r--r--puppet/modules/apt/files/03clean_vserver4
-rw-r--r--puppet/modules/apt/files/upgrade_initiator1
-rw-r--r--puppet/modules/apt/lib/facter/apt_running.rb7
-rw-r--r--puppet/modules/apt/lib/facter/debian_codename.rb42
-rw-r--r--puppet/modules/apt/lib/facter/debian_lts.rb16
-rw-r--r--puppet/modules/apt/lib/facter/debian_nextcodename.rb23
-rw-r--r--puppet/modules/apt/lib/facter/debian_nextrelease.rb23
-rw-r--r--puppet/modules/apt/lib/facter/debian_release.rb38
-rw-r--r--puppet/modules/apt/lib/facter/ubuntu_codename.rb8
-rw-r--r--puppet/modules/apt/lib/facter/ubuntu_nextcodename.rb20
-rw-r--r--puppet/modules/apt/lib/facter/util/debian.rb18
-rw-r--r--puppet/modules/apt/lib/facter/util/ubuntu.rb21
-rw-r--r--puppet/modules/apt/manifests/apt_conf.pp45
-rw-r--r--puppet/modules/apt/manifests/apticron.pp24
-rw-r--r--puppet/modules/apt/manifests/cron/base.pp20
-rw-r--r--puppet/modules/apt/manifests/cron/dist_upgrade.pp29
-rw-r--r--puppet/modules/apt/manifests/cron/download.pp27
-rw-r--r--puppet/modules/apt/manifests/dist_upgrade.pp9
-rw-r--r--puppet/modules/apt/manifests/dist_upgrade/initiator.pp23
-rw-r--r--puppet/modules/apt/manifests/dot_d_directories.pp15
-rw-r--r--puppet/modules/apt/manifests/dselect.pp11
-rw-r--r--puppet/modules/apt/manifests/init.pp150
-rw-r--r--puppet/modules/apt/manifests/key.pp13
-rw-r--r--puppet/modules/apt/manifests/key/plain.pp13
-rw-r--r--puppet/modules/apt/manifests/listchanges.pp19
-rw-r--r--puppet/modules/apt/manifests/params.pp22
-rw-r--r--puppet/modules/apt/manifests/preferences.pp20
-rw-r--r--puppet/modules/apt/manifests/preferences/absent.pp7
-rw-r--r--puppet/modules/apt/manifests/preferences_snippet.pp59
-rw-r--r--puppet/modules/apt/manifests/preseeded_package.pp21
-rw-r--r--puppet/modules/apt/manifests/proxy_client.pp9
-rw-r--r--puppet/modules/apt/manifests/reboot_required_notify.pp21
-rw-r--r--puppet/modules/apt/manifests/sources_list.pp40
-rw-r--r--puppet/modules/apt/manifests/unattended_upgrades.pp34
-rw-r--r--puppet/modules/apt/manifests/update.pp7
-rw-r--r--puppet/modules/apt/manifests/upgrade_package.pp31
-rw-r--r--puppet/modules/apt/spec/spec_helper.rb12
-rw-r--r--puppet/modules/apt/spec/unit/custom_facts_spec.rb86
-rw-r--r--puppet/modules/apt/templates/20proxy.erb5
-rw-r--r--puppet/modules/apt/templates/50unattended-upgrades.erb38
l---------puppet/modules/apt/templates/Debian/apticron_jessie.erb1
-rw-r--r--puppet/modules/apt/templates/Debian/apticron_lenny.erb50
l---------puppet/modules/apt/templates/Debian/apticron_sid.erb1
-rw-r--r--puppet/modules/apt/templates/Debian/apticron_squeeze.erb82
-rw-r--r--puppet/modules/apt/templates/Debian/apticron_wheezy.erb80
l---------puppet/modules/apt/templates/Debian/listchanges_jessie.erb1
-rw-r--r--puppet/modules/apt/templates/Debian/listchanges_lenny.erb7
l---------puppet/modules/apt/templates/Debian/listchanges_sid.erb1
l---------puppet/modules/apt/templates/Debian/listchanges_squeeze.erb1
l---------puppet/modules/apt/templates/Debian/listchanges_wheezy.erb1
-rw-r--r--puppet/modules/apt/templates/Debian/preferences_jessie.erb14
-rw-r--r--puppet/modules/apt/templates/Debian/preferences_lenny.erb25
-rw-r--r--puppet/modules/apt/templates/Debian/preferences_sid.erb10
-rw-r--r--puppet/modules/apt/templates/Debian/preferences_squeeze.erb30
-rw-r--r--puppet/modules/apt/templates/Debian/preferences_wheezy.erb20
-rw-r--r--puppet/modules/apt/templates/Debian/sources.list.erb76
l---------puppet/modules/apt/templates/Ubuntu/preferences_lucid.erb1
-rw-r--r--puppet/modules/apt/templates/Ubuntu/preferences_maverick.erb30
l---------puppet/modules/apt/templates/Ubuntu/preferences_oneiric.erb1
l---------puppet/modules/apt/templates/Ubuntu/preferences_precise.erb1
l---------puppet/modules/apt/templates/Ubuntu/preferences_utopic.erb1
l---------puppet/modules/apt/templates/Ubuntu/preferences_vivid.erb1
l---------puppet/modules/apt/templates/Ubuntu/preferences_wily.erb1
l---------puppet/modules/apt/templates/Ubuntu/preferences_xenial.erb1
-rw-r--r--puppet/modules/apt/templates/Ubuntu/sources.list.erb22
-rw-r--r--puppet/modules/apt/templates/preferences_snippet.erb4
-rw-r--r--puppet/modules/apt/templates/preferences_snippet_release.erb4
-rw-r--r--puppet/modules/augeas/.fixtures.yml5
-rw-r--r--puppet/modules/augeas/.gitignore7
-rw-r--r--puppet/modules/augeas/.puppet-lint.rc (renamed from .puppet-lint.rc)0
-rw-r--r--puppet/modules/augeas/.sync.yml3
-rw-r--r--puppet/modules/augeas/.travis.yml30
-rw-r--r--puppet/modules/augeas/CHANGELOG.md45
-rw-r--r--puppet/modules/augeas/Gemfile41
-rw-r--r--puppet/modules/augeas/LICENSE201
-rw-r--r--puppet/modules/augeas/README.md76
-rw-r--r--puppet/modules/augeas/Rakefile11
-rw-r--r--puppet/modules/augeas/lib/puppet/parser/functions/augeas.rb68
-rw-r--r--puppet/modules/augeas/manifests/files.pp36
-rw-r--r--puppet/modules/augeas/manifests/init.pp24
-rw-r--r--puppet/modules/augeas/manifests/lens.pp79
-rw-r--r--puppet/modules/augeas/manifests/packages.pp14
-rw-r--r--puppet/modules/augeas/manifests/params.pp37
-rw-r--r--puppet/modules/augeas/metadata.json62
-rw-r--r--puppet/modules/augeas/spec/.rspec (renamed from spec/spec.opts)0
-rw-r--r--puppet/modules/augeas/spec/acceptance/nodesets/centos-7-x86_64-docker.yml12
-rw-r--r--puppet/modules/augeas/spec/acceptance/nodesets/centos-7-x86_64-openstack.yml13
-rw-r--r--puppet/modules/augeas/spec/acceptance/nodesets/centos-7-x86_64-vagrant.yml10
-rw-r--r--puppet/modules/augeas/spec/acceptance/nodesets/debian-6-x86_64-docker.yml12
-rw-r--r--puppet/modules/augeas/spec/acceptance/nodesets/debian-6-x86_64-vagrant.yml10
-rw-r--r--puppet/modules/augeas/spec/acceptance/nodesets/debian-7-x86_64-docker.yml12
-rw-r--r--puppet/modules/augeas/spec/acceptance/nodesets/debian-7-x86_64-openstack.yml13
-rw-r--r--puppet/modules/augeas/spec/acceptance/nodesets/debian-7-x86_64-vagrant.yml10
-rw-r--r--puppet/modules/augeas/spec/acceptance/nodesets/debian-8-x86_64-docker.yml12
-rw-r--r--puppet/modules/augeas/spec/acceptance/nodesets/debian-8-x86_64-openstack.yml13
-rw-r--r--puppet/modules/augeas/spec/acceptance/nodesets/ubuntu-14.04-x86_64-vagrant.yml10
-rw-r--r--puppet/modules/augeas/spec/classes/augeas_spec.rb149
-rw-r--r--puppet/modules/augeas/spec/defines/augeas_lens_spec.rb112
-rw-r--r--puppet/modules/augeas/spec/spec.opts6
-rw-r--r--puppet/modules/augeas/spec/spec_helper.rb42
-rw-r--r--puppet/modules/augeas/spec/unit/puppet/parser/functions/augeas_spec.rb83
-rw-r--r--puppet/modules/backupninja/LICENSE674
-rw-r--r--puppet/modules/backupninja/README202
-rwxr-xr-xpuppet/modules/backupninja/files/checkbackups.pl194
-rw-r--r--puppet/modules/backupninja/files/nagios_plugins/duplicity/README.md24
-rw-r--r--puppet/modules/backupninja/files/nagios_plugins/duplicity/backupninja_duplicity_freshness.sh268
-rw-r--r--puppet/modules/backupninja/files/nagios_plugins/duplicity/check_backupninja_duplicity.py123
-rw-r--r--puppet/modules/backupninja/manifests/cron.pp17
-rw-r--r--puppet/modules/backupninja/manifests/duplicity.pp147
-rw-r--r--puppet/modules/backupninja/manifests/generate_sshkey.pp33
-rw-r--r--puppet/modules/backupninja/manifests/init.pp52
-rw-r--r--puppet/modules/backupninja/manifests/key.pp41
-rw-r--r--puppet/modules/backupninja/manifests/labelmount.pp62
-rw-r--r--puppet/modules/backupninja/manifests/maildir.pp43
-rw-r--r--puppet/modules/backupninja/manifests/mysql.pp38
-rw-r--r--puppet/modules/backupninja/manifests/nagios_plugin/duplicity.pp45
-rw-r--r--puppet/modules/backupninja/manifests/pgsql.pp27
-rw-r--r--puppet/modules/backupninja/manifests/rdiff.pp109
-rw-r--r--puppet/modules/backupninja/manifests/rsync.pp128
-rw-r--r--puppet/modules/backupninja/manifests/server.pp147
-rw-r--r--puppet/modules/backupninja/manifests/sh.pp25
-rw-r--r--puppet/modules/backupninja/manifests/svn.pp28
-rw-r--r--puppet/modules/backupninja/manifests/sys.pp45
-rw-r--r--puppet/modules/backupninja/templates/backupninja.conf.erb25
-rw-r--r--puppet/modules/backupninja/templates/backupninja.cron.erb6
-rw-r--r--puppet/modules/backupninja/templates/dup.conf.erb46
-rw-r--r--puppet/modules/backupninja/templates/labelmount.conf.erb2
-rw-r--r--puppet/modules/backupninja/templates/labelmount.handler17
-rw-r--r--puppet/modules/backupninja/templates/maildir.conf.erb14
-rw-r--r--puppet/modules/backupninja/templates/mysql.conf.erb25
-rw-r--r--puppet/modules/backupninja/templates/pgsql.conf.erb13
-rw-r--r--puppet/modules/backupninja/templates/rdiff.conf.erb38
-rw-r--r--puppet/modules/backupninja/templates/rsync.conf.erb49
-rw-r--r--puppet/modules/backupninja/templates/sh.conf.erb10
-rw-r--r--puppet/modules/backupninja/templates/svn.conf.erb10
-rw-r--r--puppet/modules/backupninja/templates/sys.conf.erb18
-rw-r--r--puppet/modules/backupninja/templates/umount.conf.erb1
-rw-r--r--puppet/modules/backupninja/templates/umount.handler15
-rw-r--r--puppet/modules/bundler/.gitignore1
-rw-r--r--puppet/modules/bundler/LICENSE13
-rw-r--r--puppet/modules/bundler/README.md63
-rw-r--r--puppet/modules/bundler/manifests/config.pp74
-rw-r--r--puppet/modules/bundler/manifests/install.pp64
-rw-r--r--puppet/modules/bundler/manifests/params.pp31
-rw-r--r--puppet/modules/check_mk/.gitignore3
-rw-r--r--puppet/modules/check_mk/Changelog27
-rw-r--r--puppet/modules/check_mk/LICENSE674
-rw-r--r--puppet/modules/check_mk/Modulefile10
-rw-r--r--puppet/modules/check_mk/README.md268
-rw-r--r--puppet/modules/check_mk/Rakefile2
-rw-r--r--puppet/modules/check_mk/TODO5
-rw-r--r--puppet/modules/check_mk/debian.md35
-rw-r--r--puppet/modules/check_mk/example.yaml93
-rw-r--r--puppet/modules/check_mk/files/agent/local_checks/all_hosts/README.md2
-rw-r--r--puppet/modules/check_mk/files/use_ssh.mk5
-rw-r--r--puppet/modules/check_mk/manifests/agent.pp70
-rw-r--r--puppet/modules/check_mk/manifests/agent/config.pp59
-rw-r--r--puppet/modules/check_mk/manifests/agent/generate_sshkey.pp70
-rw-r--r--puppet/modules/check_mk/manifests/agent/install.pp70
-rw-r--r--puppet/modules/check_mk/manifests/agent/install_local.pp12
-rw-r--r--puppet/modules/check_mk/manifests/agent/local_checks.pp11
-rw-r--r--puppet/modules/check_mk/manifests/agent/mrpe.pp19
-rw-r--r--puppet/modules/check_mk/manifests/agent/ps.pp17
-rw-r--r--puppet/modules/check_mk/manifests/agent/register.pp8
-rw-r--r--puppet/modules/check_mk/manifests/agent/service.pp8
-rw-r--r--puppet/modules/check_mk/manifests/config.pp109
-rw-r--r--puppet/modules/check_mk/manifests/host.pp18
-rw-r--r--puppet/modules/check_mk/manifests/hostgroup.pp24
-rw-r--r--puppet/modules/check_mk/manifests/htpasswd.pp12
-rw-r--r--puppet/modules/check_mk/manifests/init.pp44
-rw-r--r--puppet/modules/check_mk/manifests/install.pp50
-rw-r--r--puppet/modules/check_mk/manifests/install_tarball.pp92
-rw-r--r--puppet/modules/check_mk/manifests/omd_repo.pp6
-rw-r--r--puppet/modules/check_mk/manifests/ps.pp34
-rw-r--r--puppet/modules/check_mk/manifests/server/collect_hosts.pp6
-rw-r--r--puppet/modules/check_mk/manifests/server/collect_ps.pp30
-rw-r--r--puppet/modules/check_mk/manifests/server/configure_ssh.pp16
-rw-r--r--puppet/modules/check_mk/manifests/service.pp23
-rw-r--r--puppet/modules/check_mk/templates/agent/check_mk.erb39
-rw-r--r--puppet/modules/check_mk/templates/main.mk.erb4
-rw-r--r--puppet/modules/check_mk/templates/setup.conf.erb29
-rw-r--r--puppet/modules/clamav/files/01-leap.conf58
-rw-r--r--puppet/modules/clamav/files/clamav-daemon_default8
-rw-r--r--puppet/modules/clamav/files/clamav-milter_default14
-rw-r--r--puppet/modules/clamav/manifests/daemon.pp91
-rw-r--r--puppet/modules/clamav/manifests/freshclam.pp23
-rw-r--r--puppet/modules/clamav/manifests/init.pp8
-rw-r--r--puppet/modules/clamav/manifests/milter.pp50
-rw-r--r--puppet/modules/clamav/manifests/unofficial_sigs.pp23
-rw-r--r--puppet/modules/clamav/templates/clamav-milter.conf.erb28
-rw-r--r--puppet/modules/clamav/templates/local.pdb.erb1
-rw-r--r--puppet/modules/clamav/templates/whitelisted_addresses.erb5
-rw-r--r--puppet/modules/common/LICENSE674
-rw-r--r--puppet/modules/common/README44
-rw-r--r--puppet/modules/common/lib/puppet/parser/functions/basename.rb22
-rw-r--r--puppet/modules/common/lib/puppet/parser/functions/dirname.rb22
-rw-r--r--puppet/modules/common/lib/puppet/parser/functions/get_default.rb15
-rw-r--r--puppet/modules/common/lib/puppet/parser/functions/hostname.rb13
-rw-r--r--puppet/modules/common/lib/puppet/parser/functions/multi_source_template.rb29
-rw-r--r--puppet/modules/common/lib/puppet/parser/functions/prefix_with.rb9
-rw-r--r--puppet/modules/common/lib/puppet/parser/functions/re_escape.rb7
-rw-r--r--puppet/modules/common/lib/puppet/parser/functions/slash_escape.rb7
-rw-r--r--puppet/modules/common/lib/puppet/parser/functions/substitute.rb20
-rw-r--r--puppet/modules/common/lib/puppet/parser/functions/tfile.rb19
-rw-r--r--puppet/modules/common/manifests/module_dir.pp34
-rw-r--r--puppet/modules/common/manifests/module_file.pp37
-rw-r--r--puppet/modules/common/manifests/moduledir.pp18
-rw-r--r--puppet/modules/common/manifests/moduledir/common.pp4
-rw-r--r--puppet/modules/common/spec/spec.opts6
-rw-r--r--puppet/modules/common/spec/spec_helper.rb16
-rw-r--r--puppet/modules/common/spec/unit/parser/functions/tfile.rb54
-rw-r--r--puppet/modules/concat/CHANGELOG29
-rw-r--r--puppet/modules/concat/LICENSE14
-rw-r--r--puppet/modules/concat/Modulefile8
-rw-r--r--puppet/modules/concat/README.markdown112
-rw-r--r--puppet/modules/concat/Rakefile13
-rwxr-xr-xpuppet/modules/concat/files/concatfragments.sh129
-rw-r--r--puppet/modules/concat/files/null/.gitignore0
-rw-r--r--puppet/modules/concat/lib/facter/concat_basedir.rb5
-rw-r--r--puppet/modules/concat/manifests/fragment.pp49
-rw-r--r--puppet/modules/concat/manifests/init.pp178
-rw-r--r--puppet/modules/concat/manifests/setup.pp49
-rw-r--r--puppet/modules/concat/spec/defines/init_spec.rb20
-rw-r--r--puppet/modules/concat/spec/spec_helper.rb9
-rw-r--r--puppet/modules/couchdb/.fixtures.yml6
-rw-r--r--puppet/modules/couchdb/Gemfile11
-rw-r--r--puppet/modules/couchdb/README.md32
-rw-r--r--puppet/modules/couchdb/Rakefile19
-rwxr-xr-xpuppet/modules/couchdb/files/Debian/couchdb160
-rw-r--r--puppet/modules/couchdb/files/couch-doc-diff17
-rw-r--r--puppet/modules/couchdb/files/couch-doc-update219
-rw-r--r--puppet/modules/couchdb/files/local.ini84
-rw-r--r--puppet/modules/couchdb/lib/facter/couchdb_pwhash_alg.rb43
-rw-r--r--puppet/modules/couchdb/lib/facter/couchdb_version.rb34
-rw-r--r--puppet/modules/couchdb/lib/puppet/parser/functions/couchdblookup.rb55
-rw-r--r--puppet/modules/couchdb/lib/puppet/parser/functions/pbkdf2.rb62
-rw-r--r--puppet/modules/couchdb/manifests/add_user.pp39
-rw-r--r--puppet/modules/couchdb/manifests/backup.pp51
-rw-r--r--puppet/modules/couchdb/manifests/base.pp124
-rw-r--r--puppet/modules/couchdb/manifests/bigcouch.pp51
-rw-r--r--puppet/modules/couchdb/manifests/bigcouch/add_node.pp8
-rw-r--r--puppet/modules/couchdb/manifests/bigcouch/debian.pp11
-rw-r--r--puppet/modules/couchdb/manifests/bigcouch/document.pp14
-rw-r--r--puppet/modules/couchdb/manifests/bigcouch/package/cloudant.pp35
-rw-r--r--puppet/modules/couchdb/manifests/create_db.pp21
-rw-r--r--puppet/modules/couchdb/manifests/debian.pp15
-rw-r--r--puppet/modules/couchdb/manifests/deploy_config.pp12
-rw-r--r--puppet/modules/couchdb/manifests/document.pp47
-rw-r--r--puppet/modules/couchdb/manifests/init.pp31
-rw-r--r--puppet/modules/couchdb/manifests/mirror_db.pp21
-rw-r--r--puppet/modules/couchdb/manifests/params.pp23
-rw-r--r--puppet/modules/couchdb/manifests/query.pp12
-rw-r--r--puppet/modules/couchdb/manifests/query/setup.pp10
-rw-r--r--puppet/modules/couchdb/manifests/redhat.pp1
-rw-r--r--puppet/modules/couchdb/manifests/ssl/deploy_cert.pp28
-rw-r--r--puppet/modules/couchdb/manifests/ssl/generate_cert.pp25
-rw-r--r--puppet/modules/couchdb/manifests/update.pp12
-rw-r--r--puppet/modules/couchdb/spec/classes/couchdb_spec.rb35
-rw-r--r--puppet/modules/couchdb/spec/fixtures/manifests/site.pp8
-rw-r--r--puppet/modules/couchdb/spec/functions/versioncmp_spec.rb9
-rw-r--r--puppet/modules/couchdb/spec/spec_helper.rb9
-rw-r--r--puppet/modules/couchdb/templates/admin.ini.erb9
-rw-r--r--puppet/modules/couchdb/templates/bigcouch/default.ini172
-rw-r--r--puppet/modules/couchdb/templates/bigcouch/vm.args32
-rw-r--r--puppet/modules/couchdb/templates/couchdb-backup.py.erb32
-rw-r--r--puppet/modules/git/files/config/CentOS/git-daemon26
-rw-r--r--puppet/modules/git/files/config/CentOS/git-daemon.vhosts27
-rw-r--r--puppet/modules/git/files/config/Debian/git-daemon22
-rw-r--r--puppet/modules/git/files/init.d/CentOS/git-daemon75
-rw-r--r--puppet/modules/git/files/init.d/Debian/git-daemon151
-rw-r--r--puppet/modules/git/files/web/gitweb.conf53
-rw-r--r--puppet/modules/git/files/xinetd.d/git16
-rw-r--r--puppet/modules/git/files/xinetd.d/git.disabled16
-rw-r--r--puppet/modules/git/files/xinetd.d/git.vhosts16
-rw-r--r--puppet/modules/git/manifests/base.pp7
-rw-r--r--puppet/modules/git/manifests/centos.pp2
-rw-r--r--puppet/modules/git/manifests/changes.pp33
-rw-r--r--puppet/modules/git/manifests/clone.pp60
-rw-r--r--puppet/modules/git/manifests/daemon.pp17
-rw-r--r--puppet/modules/git/manifests/daemon/base.pp31
-rw-r--r--puppet/modules/git/manifests/daemon/centos.pp19
-rw-r--r--puppet/modules/git/manifests/daemon/disable.pp33
-rw-r--r--puppet/modules/git/manifests/daemon/vhosts.pp10
-rw-r--r--puppet/modules/git/manifests/debian.pp6
-rw-r--r--puppet/modules/git/manifests/init.pp25
-rw-r--r--puppet/modules/git/manifests/svn.pp10
-rw-r--r--puppet/modules/git/manifests/web.pp20
-rw-r--r--puppet/modules/git/manifests/web/absent.pp17
-rw-r--r--puppet/modules/git/manifests/web/lighttpd.pp7
-rw-r--r--puppet/modules/git/manifests/web/repo.pp56
-rw-r--r--puppet/modules/git/manifests/web/repo/lighttpd.pp16
-rw-r--r--puppet/modules/git/templates/web/config31
-rw-r--r--puppet/modules/git/templates/web/lighttpd21
-rw-r--r--puppet/modules/haproxy/.fixtures.yml5
-rw-r--r--puppet/modules/haproxy/.gemfile5
-rw-r--r--puppet/modules/haproxy/.travis.yml23
-rw-r--r--puppet/modules/haproxy/CHANGELOG5
-rw-r--r--puppet/modules/haproxy/Modulefile12
-rw-r--r--puppet/modules/haproxy/README.md87
-rw-r--r--puppet/modules/haproxy/Rakefile1
-rw-r--r--puppet/modules/haproxy/manifests/balancermember.pp95
-rw-r--r--puppet/modules/haproxy/manifests/init.pp149
-rw-r--r--puppet/modules/haproxy/manifests/listen.pp95
-rw-r--r--puppet/modules/haproxy/manifests/params.pp65
-rw-r--r--puppet/modules/haproxy/spec/classes/haproxy_spec.rb138
-rw-r--r--puppet/modules/haproxy/spec/defines/balancermember_spec.rb82
-rw-r--r--puppet/modules/haproxy/spec/defines/listen_spec.rb53
-rw-r--r--puppet/modules/haproxy/spec/spec.opts6
-rw-r--r--puppet/modules/haproxy/spec/spec_helper.rb1
-rw-r--r--puppet/modules/haproxy/templates/haproxy-base.cfg.erb21
-rw-r--r--puppet/modules/haproxy/templates/haproxy_balancermember.erb3
-rw-r--r--puppet/modules/haproxy/templates/haproxy_listen_block.erb10
-rw-r--r--puppet/modules/haproxy/tests/init.pp69
-rw-r--r--puppet/modules/haveged/manifests/init.pp16
-rw-r--r--puppet/modules/journald/manifests/init.pp7
-rw-r--r--puppet/modules/leap/manifests/cli/install.pp46
-rw-r--r--puppet/modules/leap/manifests/init.pp3
-rw-r--r--puppet/modules/leap/manifests/logfile.pp34
-rw-r--r--puppet/modules/leap/templates/rsyslog.erb5
-rw-r--r--puppet/modules/leap_mx/manifests/init.pp119
-rw-r--r--puppet/modules/leap_mx/templates/mx.conf.erb18
-rw-r--r--puppet/modules/lsb/manifests/base.pp3
-rw-r--r--puppet/modules/lsb/manifests/centos.pp5
-rw-r--r--puppet/modules/lsb/manifests/debian.pp6
-rw-r--r--puppet/modules/lsb/manifests/init.pp6
-rw-r--r--puppet/modules/nagios/LICENSE674
-rw-r--r--puppet/modules/nagios/README305
-rw-r--r--puppet/modules/nagios/README.pnp4nagios65
-rw-r--r--puppet/modules/nagios/files/configs/CentOS/cgi.cfg280
-rw-r--r--puppet/modules/nagios/files/configs/CentOS/nagios.cfg949
-rw-r--r--puppet/modules/nagios/files/configs/CentOS/private/resource.cfg.i38634
-rw-r--r--puppet/modules/nagios/files/configs/CentOS/private/resource.cfg.x86_6434
-rw-r--r--puppet/modules/nagios/files/configs/Debian/cgi.cfg330
-rw-r--r--puppet/modules/nagios/files/configs/Debian/nagios.cfg1288
-rw-r--r--puppet/modules/nagios/files/configs/Debian/private/resource.cfg.amd6431
-rw-r--r--puppet/modules/nagios/files/configs/Debian/private/resource.cfg.i38631
-rw-r--r--puppet/modules/nagios/files/configs/Debian/private/resource.cfg.x86_6431
-rw-r--r--puppet/modules/nagios/files/configs/apache2.conf67
l---------puppet/modules/nagios/files/configs/cgi.cfg1
l---------puppet/modules/nagios/files/configs/nagios.cfg1
-rw-r--r--puppet/modules/nagios/files/configs/nagios_templates.cfg49
-rw-r--r--puppet/modules/nagios/files/htpasswd.users0
-rw-r--r--puppet/modules/nagios/files/irc_bot/riseup-nagios-client.pl72
-rw-r--r--puppet/modules/nagios/files/irc_bot/riseup-nagios-server.pl239
-rw-r--r--puppet/modules/nagios/files/munin/nagios_hosts32
-rw-r--r--puppet/modules/nagios/files/munin/nagios_perf38
-rw-r--r--puppet/modules/nagios/files/munin/nagios_svc37
-rw-r--r--puppet/modules/nagios/files/nrpe/nrpe_commands.cfg5
-rw-r--r--puppet/modules/nagios/files/nsca/nsca.cfg197
-rw-r--r--puppet/modules/nagios/files/nsca/send_nsca.cfg65
-rw-r--r--puppet/modules/nagios/files/plugin_data/sks-keyservers.netCA.pem32
-rw-r--r--puppet/modules/nagios/files/plugins/check_dns2102
-rw-r--r--puppet/modules/nagios/files/plugins/check_dnsbl107
-rw-r--r--puppet/modules/nagios/files/plugins/check_gpg115
-rw-r--r--puppet/modules/nagios/files/plugins/check_horde_login94
-rw-r--r--puppet/modules/nagios/files/plugins/check_imap_login80
-rw-r--r--puppet/modules/nagios/files/plugins/check_jabber_login30
-rwxr-xr-xpuppet/modules/nagios/files/plugins/check_mysql_health3780
-rwxr-xr-xpuppet/modules/nagios/files/plugins/check_openvpn_server.pl109
-rw-r--r--puppet/modules/nagios/files/plugins/check_pop3_login83
-rw-r--r--puppet/modules/nagios/files/pnp4nagios/action.gifbin0 -> 1536 bytes
-rw-r--r--puppet/modules/nagios/files/pnp4nagios/apache.conf31
-rw-r--r--puppet/modules/nagios/files/pnp4nagios/npcd8
-rw-r--r--puppet/modules/nagios/files/pnp4nagios/pnp4nagios-popup-templates.cfg31
-rw-r--r--puppet/modules/nagios/files/pnp4nagios/pnp4nagios-templates.cfg33
-rw-r--r--puppet/modules/nagios/files/pnp4nagios/status-header.ssi8
-rw-r--r--puppet/modules/nagios/images/nagiosgraph.gifbin0 -> 1252 bytes
-rw-r--r--puppet/modules/nagios/manifests/apache.pp15
-rw-r--r--puppet/modules/nagios/manifests/base.pp144
-rw-r--r--puppet/modules/nagios/manifests/centos.pp42
-rw-r--r--puppet/modules/nagios/manifests/command/imap_pop3.pp30
-rw-r--r--puppet/modules/nagios/manifests/command/nrpe.pp14
-rw-r--r--puppet/modules/nagios/manifests/command/nrpe_timeout.pp11
-rw-r--r--puppet/modules/nagios/manifests/command/smtp.pp22
-rw-r--r--puppet/modules/nagios/manifests/debian.pp54
-rw-r--r--puppet/modules/nagios/manifests/debian/apache.pp22
-rw-r--r--puppet/modules/nagios/manifests/defaults.pp12
-rw-r--r--puppet/modules/nagios/manifests/defaults/commands.pp145
-rw-r--r--puppet/modules/nagios/manifests/defaults/contactgroups.pp9
-rw-r--r--puppet/modules/nagios/manifests/defaults/contacts.pp15
-rw-r--r--puppet/modules/nagios/manifests/defaults/host_templates.pp24
-rw-r--r--puppet/modules/nagios/manifests/defaults/hostgroups.pp11
-rw-r--r--puppet/modules/nagios/manifests/defaults/plugins.pp10
-rw-r--r--puppet/modules/nagios/manifests/defaults/pnp4nagios.pp14
-rw-r--r--puppet/modules/nagios/manifests/defaults/service_templates.pp32
-rw-r--r--puppet/modules/nagios/manifests/defaults/templates.pp17
-rw-r--r--puppet/modules/nagios/manifests/defaults/timeperiods.pp33
-rw-r--r--puppet/modules/nagios/manifests/defaults/vars.pp11
-rw-r--r--puppet/modules/nagios/manifests/headless.pp5
-rw-r--r--puppet/modules/nagios/manifests/init.pp56
-rw-r--r--puppet/modules/nagios/manifests/irc_bot.pp50
-rw-r--r--puppet/modules/nagios/manifests/irc_bot/base.pp41
-rw-r--r--puppet/modules/nagios/manifests/irc_bot/centos.pp9
-rw-r--r--puppet/modules/nagios/manifests/irc_bot/debian.pp8
-rw-r--r--puppet/modules/nagios/manifests/irc_bot/disable.pp8
-rw-r--r--puppet/modules/nagios/manifests/lighttpd.pp12
-rw-r--r--puppet/modules/nagios/manifests/munin.pp19
-rw-r--r--puppet/modules/nagios/manifests/nrpe.pp41
-rw-r--r--puppet/modules/nagios/manifests/nrpe/base.pp58
-rw-r--r--puppet/modules/nagios/manifests/nrpe/command.pp34
-rw-r--r--puppet/modules/nagios/manifests/nrpe/debian.pp6
-rw-r--r--puppet/modules/nagios/manifests/nrpe/freebsd.pp16
-rw-r--r--puppet/modules/nagios/manifests/nrpe/linux.pp9
-rw-r--r--puppet/modules/nagios/manifests/nrpe/xinetd.pp11
-rw-r--r--puppet/modules/nagios/manifests/nsca.pp3
-rw-r--r--puppet/modules/nagios/manifests/nsca/client.pp18
-rw-r--r--puppet/modules/nagios/manifests/nsca/server.pp24
-rw-r--r--puppet/modules/nagios/manifests/plugin.pp28
-rw-r--r--puppet/modules/nagios/manifests/plugin/deploy.pp41
-rw-r--r--puppet/modules/nagios/manifests/plugin/scriptpaths.pp6
-rw-r--r--puppet/modules/nagios/manifests/plugins/gpg.pp30
-rw-r--r--puppet/modules/nagios/manifests/plugins/horde_login.pp11
-rw-r--r--puppet/modules/nagios/manifests/plugins/jabber.pp10
-rw-r--r--puppet/modules/nagios/manifests/plugins/mail_login.pp10
-rw-r--r--puppet/modules/nagios/manifests/pnp4nagios.pp68
-rw-r--r--puppet/modules/nagios/manifests/pnp4nagios/popup.pp24
-rw-r--r--puppet/modules/nagios/manifests/service.pp91
-rw-r--r--puppet/modules/nagios/manifests/service/dns.pp19
-rw-r--r--puppet/modules/nagios/manifests/service/dns_host.pp22
-rw-r--r--puppet/modules/nagios/manifests/service/gpgkey.pp49
-rw-r--r--puppet/modules/nagios/manifests/service/horde_login.pp18
-rw-r--r--puppet/modules/nagios/manifests/service/http.pp54
-rw-r--r--puppet/modules/nagios/manifests/service/imap.pp34
-rw-r--r--puppet/modules/nagios/manifests/service/imap_login.pp22
-rw-r--r--puppet/modules/nagios/manifests/service/mysql.pp58
-rw-r--r--puppet/modules/nagios/manifests/service/ntp.pp9
-rw-r--r--puppet/modules/nagios/manifests/service/passive.pp18
-rw-r--r--puppet/modules/nagios/manifests/service/ping.pp9
-rw-r--r--puppet/modules/nagios/manifests/service/pop.pp32
-rw-r--r--puppet/modules/nagios/manifests/service/pop3_login.pp22
-rw-r--r--puppet/modules/nagios/manifests/service/smtp.pp50
-rw-r--r--puppet/modules/nagios/manifests/service/ssmtp.pp32
-rw-r--r--puppet/modules/nagios/manifests/storeconfigs.pp61
-rw-r--r--puppet/modules/nagios/manifests/stored_config.pp19
-rw-r--r--puppet/modules/nagios/manifests/target.pp32
-rw-r--r--puppet/modules/nagios/manifests/target/fqdn.pp12
-rw-r--r--puppet/modules/nagios/templates/irc_bot/CentOS/nagios-nsa.sh.erb104
-rw-r--r--puppet/modules/nagios/templates/irc_bot/Debian/nagios-nsa.sh.erb72
-rw-r--r--puppet/modules/nagios/templates/irc_bot/nsa.cfg.erb15
-rw-r--r--puppet/modules/nagios/templates/nrpe/nrpe.cfg203
-rw-r--r--puppet/modules/nagios/templates/nrpe/nrpe_command.erb2
-rw-r--r--puppet/modules/ntp/.fixtures.yml5
-rw-r--r--puppet/modules/ntp/.gitignore3
-rw-r--r--puppet/modules/ntp/.nodeset.yml35
-rw-r--r--puppet/modules/ntp/.travis.yml40
-rw-r--r--puppet/modules/ntp/CHANGELOG61
-rw-r--r--puppet/modules/ntp/CONTRIBUTING.md9
-rw-r--r--puppet/modules/ntp/Gemfile19
-rw-r--r--puppet/modules/ntp/LICENSE202
-rw-r--r--puppet/modules/ntp/Modulefile11
-rw-r--r--puppet/modules/ntp/README.markdown215
-rw-r--r--puppet/modules/ntp/Rakefile2
-rw-r--r--puppet/modules/ntp/manifests/config.pp23
-rw-r--r--puppet/modules/ntp/manifests/init.pp58
-rw-r--r--puppet/modules/ntp/manifests/install.pp9
-rw-r--r--puppet/modules/ntp/manifests/params.pp116
-rw-r--r--puppet/modules/ntp/manifests/service.pp18
-rw-r--r--puppet/modules/ntp/spec/classes/ntp_spec.rb261
-rw-r--r--puppet/modules/ntp/spec/fixtures/modules/my_ntp/templates/ntp.conf.erb4
-rw-r--r--puppet/modules/ntp/spec/spec.opts6
-rw-r--r--puppet/modules/ntp/spec/spec_helper.rb1
-rw-r--r--puppet/modules/ntp/spec/spec_helper_system.rb26
-rw-r--r--puppet/modules/ntp/spec/system/basic_spec.rb13
-rw-r--r--puppet/modules/ntp/spec/system/class_spec.rb39
-rw-r--r--puppet/modules/ntp/spec/system/ntp_config_spec.rb35
-rw-r--r--puppet/modules/ntp/spec/system/ntp_install_spec.rb31
-rw-r--r--puppet/modules/ntp/spec/system/ntp_service_spec.rb25
-rw-r--r--puppet/modules/ntp/spec/system/preferred_servers_spec.rb20
-rw-r--r--puppet/modules/ntp/spec/system/restrict_spec.rb20
-rw-r--r--puppet/modules/ntp/spec/unit/puppet/provider/README.markdown4
-rw-r--r--puppet/modules/ntp/spec/unit/puppet/type/README.markdown4
-rw-r--r--puppet/modules/ntp/templates/ntp.conf.erb43
-rw-r--r--puppet/modules/ntp/tests/init.pp11
-rwxr-xr-xpuppet/modules/obfsproxy/files/obfsproxy_init93
-rw-r--r--puppet/modules/obfsproxy/files/obfsproxy_logrotate14
-rw-r--r--puppet/modules/obfsproxy/manifests/init.pp86
-rw-r--r--puppet/modules/obfsproxy/templates/etc_conf.erb11
-rw-r--r--puppet/modules/opendkim/manifests/init.pp67
-rw-r--r--puppet/modules/opendkim/templates/opendkim.conf45
-rw-r--r--puppet/modules/openvpn/.fixtures.yml6
-rw-r--r--puppet/modules/openvpn/.gitignore3
-rw-r--r--puppet/modules/openvpn/.rvmrc38
-rw-r--r--puppet/modules/openvpn/.travis.yml29
-rw-r--r--puppet/modules/openvpn/Gemfile7
-rw-r--r--puppet/modules/openvpn/Gemfile.lock36
-rw-r--r--puppet/modules/openvpn/LICENSE177
-rw-r--r--puppet/modules/openvpn/Modulefile11
-rw-r--r--puppet/modules/openvpn/Rakefile2
-rw-r--r--puppet/modules/openvpn/Readme.markdown54
-rw-r--r--puppet/modules/openvpn/Vagrantfile42
-rw-r--r--puppet/modules/openvpn/manifests/client.pp187
-rw-r--r--puppet/modules/openvpn/manifests/client_specific_config.pp79
-rw-r--r--puppet/modules/openvpn/manifests/config.pp52
-rw-r--r--puppet/modules/openvpn/manifests/init.pp43
-rw-r--r--puppet/modules/openvpn/manifests/install.pp46
-rw-r--r--puppet/modules/openvpn/manifests/params.pp37
-rw-r--r--puppet/modules/openvpn/manifests/server.pp233
-rw-r--r--puppet/modules/openvpn/manifests/service.pp36
-rw-r--r--puppet/modules/openvpn/spec/classes/openvpn_config_spec.rb15
-rw-r--r--puppet/modules/openvpn/spec/classes/openvpn_init_spec.rb9
-rw-r--r--puppet/modules/openvpn/spec/classes/openvpn_install_spec.rb11
-rw-r--r--puppet/modules/openvpn/spec/classes/openvpn_service_spec.rb13
-rw-r--r--puppet/modules/openvpn/spec/defines/openvpn_client_spec.rb88
-rw-r--r--puppet/modules/openvpn/spec/defines/openvpn_client_specific_config_spec.rb40
-rw-r--r--puppet/modules/openvpn/spec/defines/openvpn_server_spec.rb165
-rw-r--r--puppet/modules/openvpn/spec/spec_helper.rb2
-rw-r--r--puppet/modules/openvpn/templates/client.erb26
-rw-r--r--puppet/modules/openvpn/templates/client_specific_config.erb10
-rw-r--r--puppet/modules/openvpn/templates/etc-default-openvpn.erb20
-rw-r--r--puppet/modules/openvpn/templates/server.erb37
-rw-r--r--puppet/modules/openvpn/templates/vars.erb68
-rw-r--r--puppet/modules/openvpn/vagrant/client.pp5
-rw-r--r--puppet/modules/openvpn/vagrant/server.pp23
-rw-r--r--puppet/modules/passenger/README42
-rw-r--r--puppet/modules/passenger/files/mod_passenger.conf0
-rwxr-xr-xpuppet/modules/passenger/files/munin/passenger_memory_stats123
-rwxr-xr-xpuppet/modules/passenger/files/munin/passenger_stats47
-rw-r--r--puppet/modules/passenger/manifests/apache.pp7
-rw-r--r--puppet/modules/passenger/manifests/apache/base.pp4
-rw-r--r--puppet/modules/passenger/manifests/apache/centos.pp24
-rw-r--r--puppet/modules/passenger/manifests/apache/debian.pp24
-rw-r--r--puppet/modules/passenger/manifests/init.pp75
-rw-r--r--puppet/modules/passenger/manifests/munin.pp20
-rw-r--r--puppet/modules/postfix/LICENSE674
-rw-r--r--puppet/modules/postfix/README.md224
-rw-r--r--puppet/modules/postfix/files/header_checks.d/.ignore0
-rw-r--r--puppet/modules/postfix/files/main.cf1
-rw-r--r--puppet/modules/postfix/files/tls_policy.d/.ignore0
-rw-r--r--puppet/modules/postfix/manifests/amavis.pp5
-rw-r--r--puppet/modules/postfix/manifests/anonsasl.pp18
-rw-r--r--puppet/modules/postfix/manifests/config.pp49
-rw-r--r--puppet/modules/postfix/manifests/disable.pp7
-rw-r--r--puppet/modules/postfix/manifests/disable/base.pp12
-rw-r--r--puppet/modules/postfix/manifests/disable/debian.pp11
-rw-r--r--puppet/modules/postfix/manifests/hash.pp71
-rw-r--r--puppet/modules/postfix/manifests/header_checks.pp32
-rw-r--r--puppet/modules/postfix/manifests/header_checks_snippet.pp60
-rw-r--r--puppet/modules/postfix/manifests/init.pp221
-rw-r--r--puppet/modules/postfix/manifests/mailalias.pp32
-rw-r--r--puppet/modules/postfix/manifests/mailman.pp34
-rw-r--r--puppet/modules/postfix/manifests/mta.pp70
-rw-r--r--puppet/modules/postfix/manifests/satellite.pp49
-rw-r--r--puppet/modules/postfix/manifests/smtp_auth.pp37
-rw-r--r--puppet/modules/postfix/manifests/tlspolicy.pp55
-rw-r--r--puppet/modules/postfix/manifests/tlspolicy_snippet.pp45
-rw-r--r--puppet/modules/postfix/manifests/transport.pp44
-rw-r--r--puppet/modules/postfix/manifests/transport_regexp.pp56
-rw-r--r--puppet/modules/postfix/manifests/transport_regexp_snippet.pp67
-rw-r--r--puppet/modules/postfix/manifests/virtual.pp44
-rw-r--r--puppet/modules/postfix/manifests/virtual_regexp.pp56
-rw-r--r--puppet/modules/postfix/manifests/virtual_regexp_snippet.pp67
-rw-r--r--puppet/modules/postfix/templates/anonsasl_header_checks.erb2
-rw-r--r--puppet/modules/postfix/templates/master.cf.debian-5.erb126
-rw-r--r--puppet/modules/postfix/templates/master.cf.debian-6.erb158
-rw-r--r--puppet/modules/postfix/templates/master.cf.debian-7.erb161
-rw-r--r--puppet/modules/postfix/templates/master.cf.debian-8.erb160
-rw-r--r--puppet/modules/postfix/templates/master.cf.debian-sid.erb157
-rw-r--r--puppet/modules/postfix/templates/master.cf.redhat5.erb87
-rw-r--r--puppet/modules/postfwd/files/postfwd_default19
-rw-r--r--puppet/modules/postfwd/manifests/init.pp43
-rw-r--r--puppet/modules/postfwd/templates/postfwd.cf.erb28
-rw-r--r--puppet/modules/resolvconf/manifests/init.pp27
-rw-r--r--puppet/modules/resolvconf/templates/resolvconf.OpenBSD.erb5
-rw-r--r--puppet/modules/resolvconf/templates/resolvconf.erb7
-rw-r--r--puppet/modules/rsyslog/.fixtures.yml3
-rw-r--r--puppet/modules/rsyslog/.gemfile14
-rw-r--r--puppet/modules/rsyslog/.gitignore5
-rw-r--r--puppet/modules/rsyslog/.travis.yml56
-rw-r--r--puppet/modules/rsyslog/LICENSE202
-rw-r--r--puppet/modules/rsyslog/README.md202
-rw-r--r--puppet/modules/rsyslog/Rakefile6
-rw-r--r--puppet/modules/rsyslog/lib/facter/rsyslog_version.rb38
-rw-r--r--puppet/modules/rsyslog/manifests/client.pp64
-rw-r--r--puppet/modules/rsyslog/manifests/config.pp51
-rw-r--r--puppet/modules/rsyslog/manifests/database.pp57
-rw-r--r--puppet/modules/rsyslog/manifests/imfile.pp48
-rw-r--r--puppet/modules/rsyslog/manifests/init.pp54
-rw-r--r--puppet/modules/rsyslog/manifests/install.pp32
-rw-r--r--puppet/modules/rsyslog/manifests/modload.pp15
-rw-r--r--puppet/modules/rsyslog/manifests/params.pp222
-rw-r--r--puppet/modules/rsyslog/manifests/server.pp70
-rw-r--r--puppet/modules/rsyslog/manifests/service.pp21
-rw-r--r--puppet/modules/rsyslog/manifests/snippet.pp35
-rw-r--r--puppet/modules/rsyslog/metadata.json62
-rw-r--r--puppet/modules/rsyslog/spec/classes/rsyslog_client_spec.rb146
-rw-r--r--puppet/modules/rsyslog/spec/classes/rsyslog_database_spec.rb308
-rw-r--r--puppet/modules/rsyslog/spec/classes/rsyslog_server_spec.rb182
-rw-r--r--puppet/modules/rsyslog/spec/classes/rsyslog_spec.rb469
-rw-r--r--puppet/modules/rsyslog/spec/defines/rsyslog_imfile_spec.rb169
-rw-r--r--puppet/modules/rsyslog/spec/defines/rsyslog_snippet_spec.rb157
-rw-r--r--puppet/modules/rsyslog/spec/spec.opts6
-rw-r--r--puppet/modules/rsyslog/spec/spec_helper.rb28
-rw-r--r--puppet/modules/rsyslog/templates/client.conf.erb180
-rw-r--r--puppet/modules/rsyslog/templates/database.conf.erb6
-rw-r--r--puppet/modules/rsyslog/templates/imfile.erb15
-rw-r--r--puppet/modules/rsyslog/templates/modload.erb3
-rw-r--r--puppet/modules/rsyslog/templates/rsyslog.conf.erb49
-rw-r--r--puppet/modules/rsyslog/templates/rsyslog_default.erb9
-rw-r--r--puppet/modules/rsyslog/templates/rsyslog_default_gentoo.erb16
-rw-r--r--puppet/modules/rsyslog/templates/rsyslog_default_rhel7.erb2
-rw-r--r--puppet/modules/rsyslog/templates/server-default.conf.erb42
-rw-r--r--puppet/modules/rsyslog/templates/server-hostname.conf.erb41
-rw-r--r--puppet/modules/rsyslog/templates/server/_default-footer.conf.erb13
-rw-r--r--puppet/modules/rsyslog/templates/server/_default-header.conf.erb36
-rw-r--r--puppet/modules/rsyslog/tests/database.pp9
-rw-r--r--puppet/modules/rsyslog/tests/init.pp1
-rw-r--r--puppet/modules/rsyslog/tests/log_templates.pp9
-rw-r--r--puppet/modules/rsyslog/tests/multiple_hosts.pp17
-rw-r--r--puppet/modules/ruby/manifests/devel.pp5
-rw-r--r--puppet/modules/ruby/manifests/init.pp72
-rw-r--r--puppet/modules/ruby/manifests/mysql.pp7
-rw-r--r--puppet/modules/ruby/manifests/postgres.pp6
-rw-r--r--puppet/modules/ruby/manifests/shadow.pp6
-rw-r--r--puppet/modules/ruby/manifests/shadow/base.pp6
-rw-r--r--puppet/modules/ruby/manifests/shadow/debian.pp8
-rw-r--r--puppet/modules/rubygems/files/gemrc3
-rw-r--r--puppet/modules/rubygems/manifests/activerecord.pp7
-rw-r--r--puppet/modules/rubygems/manifests/activesupport.pp7
-rw-r--r--puppet/modules/rubygems/manifests/backports.pp7
-rw-r--r--puppet/modules/rubygems/manifests/bcrypt.pp14
-rw-r--r--puppet/modules/rubygems/manifests/brokengem.pp14
-rw-r--r--puppet/modules/rubygems/manifests/camping.pp7
-rw-r--r--puppet/modules/rubygems/manifests/captcha/v_0_1_2.pp5
-rw-r--r--puppet/modules/rubygems/manifests/chronic_duration.pp5
-rw-r--r--puppet/modules/rubygems/manifests/devel.pp6
-rw-r--r--puppet/modules/rubygems/manifests/fastercsv.pp6
-rw-r--r--puppet/modules/rubygems/manifests/gd/v_0_7_4.pp5
-rw-r--r--puppet/modules/rubygems/manifests/gem.pp108
-rw-r--r--puppet/modules/rubygems/manifests/gem/cachedir.pp4
-rw-r--r--puppet/modules/rubygems/manifests/gpgme.pp35
-rw-r--r--puppet/modules/rubygems/manifests/hiera.pp7
-rw-r--r--puppet/modules/rubygems/manifests/hiera_puppet.pp7
-rw-r--r--puppet/modules/rubygems/manifests/highline.pp14
-rw-r--r--puppet/modules/rubygems/manifests/init.pp31
-rw-r--r--puppet/modules/rubygems/manifests/ip.pp7
-rw-r--r--puppet/modules/rubygems/manifests/json/v_1_4_6.pp3
-rw-r--r--puppet/modules/rubygems/manifests/lockfile.pp7
-rw-r--r--puppet/modules/rubygems/manifests/mail.pp19
-rw-r--r--puppet/modules/rubygems/manifests/maildir.pp15
-rw-r--r--puppet/modules/rubygems/manifests/markaby.pp7
-rw-r--r--puppet/modules/rubygems/manifests/moneta.pp7
-rw-r--r--puppet/modules/rubygems/manifests/mysql.pp5
-rw-r--r--puppet/modules/rubygems/manifests/net_ldap/v_0_0_4.pp3
-rw-r--r--puppet/modules/rubygems/manifests/ntlm/v_0_1_1.pp3
-rw-r--r--puppet/modules/rubygems/manifests/open4.pp7
-rw-r--r--puppet/modules/rubygems/manifests/pbkdf2.pp8
-rw-r--r--puppet/modules/rubygems/manifests/postgres.pp11
-rw-r--r--puppet/modules/rubygems/manifests/rack.pp7
-rw-r--r--puppet/modules/rubygems/manifests/sinatra.pp7
-rw-r--r--puppet/modules/rubygems/manifests/sqlite.pp6
-rw-r--r--puppet/modules/rubygems/manifests/systemu.pp7
-rw-r--r--puppet/modules/rubygems/manifests/thin.pp7
-rw-r--r--puppet/modules/rubygems/manifests/tlsmail.pp7
-rw-r--r--puppet/modules/rubygems/manifests/tmail.pp7
-rw-r--r--puppet/modules/rubygems/manifests/xmlsimple.pp20
-rw-r--r--puppet/modules/rubygems/manifests/xmpp4r.pp7
-rw-r--r--puppet/modules/rubygems/manifests/ya2yaml.pp7
-rw-r--r--puppet/modules/shorewall/LICENSE674
-rw-r--r--puppet/modules/shorewall/README219
-rw-r--r--puppet/modules/shorewall/files/boilerplate/blacklist.footer1
-rw-r--r--puppet/modules/shorewall/files/boilerplate/blacklist.header10
-rw-r--r--puppet/modules/shorewall/files/boilerplate/clear.footer1
-rw-r--r--puppet/modules/shorewall/files/boilerplate/clear.header13
-rw-r--r--puppet/modules/shorewall/files/boilerplate/continue.footer1
-rw-r--r--puppet/modules/shorewall/files/boilerplate/continue.header14
-rw-r--r--puppet/modules/shorewall/files/boilerplate/hosts.footer1
-rw-r--r--puppet/modules/shorewall/files/boilerplate/hosts.header9
-rw-r--r--puppet/modules/shorewall/files/boilerplate/init.footer1
-rw-r--r--puppet/modules/shorewall/files/boilerplate/init.header13
-rw-r--r--puppet/modules/shorewall/files/boilerplate/initdone.footer1
-rw-r--r--puppet/modules/shorewall/files/boilerplate/initdone.header14
-rw-r--r--puppet/modules/shorewall/files/boilerplate/interfaces.footer1
-rw-r--r--puppet/modules/shorewall/files/boilerplate/interfaces.header10
-rw-r--r--puppet/modules/shorewall/files/boilerplate/maclog.footer1
-rw-r--r--puppet/modules/shorewall/files/boilerplate/maclog.header14
-rw-r--r--puppet/modules/shorewall/files/boilerplate/mangle.footer1
-rw-r--r--puppet/modules/shorewall/files/boilerplate/mangle.header7
-rw-r--r--puppet/modules/shorewall/files/boilerplate/masq.footer1
-rw-r--r--puppet/modules/shorewall/files/boilerplate/masq.header9
-rw-r--r--puppet/modules/shorewall/files/boilerplate/nat.footer1
-rw-r--r--puppet/modules/shorewall/files/boilerplate/nat.header9
-rw-r--r--puppet/modules/shorewall/files/boilerplate/params.footer1
-rw-r--r--puppet/modules/shorewall/files/boilerplate/params.header26
-rw-r--r--puppet/modules/shorewall/files/boilerplate/policy.footer1
-rw-r--r--puppet/modules/shorewall/files/boilerplate/policy.header9
-rw-r--r--puppet/modules/shorewall/files/boilerplate/providers.footer1
-rw-r--r--puppet/modules/shorewall/files/boilerplate/providers.header9
-rw-r--r--puppet/modules/shorewall/files/boilerplate/proxyarp.footer1
-rw-r--r--puppet/modules/shorewall/files/boilerplate/proxyarp.header9
-rw-r--r--puppet/modules/shorewall/files/boilerplate/rfc1918.footer5
-rw-r--r--puppet/modules/shorewall/files/boilerplate/rfc1918.header5
-rw-r--r--puppet/modules/shorewall/files/boilerplate/routestopped.footer1
-rw-r--r--puppet/modules/shorewall/files/boilerplate/routestopped.header11
-rw-r--r--puppet/modules/shorewall/files/boilerplate/rtrules.footer1
-rw-r--r--puppet/modules/shorewall/files/boilerplate/rtrules.header8
-rw-r--r--puppet/modules/shorewall/files/boilerplate/rules.footer1
-rw-r--r--puppet/modules/shorewall/files/boilerplate/rules.header10
-rw-r--r--puppet/modules/shorewall/files/boilerplate/start.footer1
-rw-r--r--puppet/modules/shorewall/files/boilerplate/start.header12
-rw-r--r--puppet/modules/shorewall/files/boilerplate/started.footer1
-rw-r--r--puppet/modules/shorewall/files/boilerplate/started.header20
-rw-r--r--puppet/modules/shorewall/files/boilerplate/stop.footer1
-rw-r--r--puppet/modules/shorewall/files/boilerplate/stop.header13
-rw-r--r--puppet/modules/shorewall/files/boilerplate/stopped.footer1
-rw-r--r--puppet/modules/shorewall/files/boilerplate/stopped.header13
-rw-r--r--puppet/modules/shorewall/files/boilerplate/tcclasses.footer1
-rw-r--r--puppet/modules/shorewall/files/boilerplate/tcclasses.header9
-rw-r--r--puppet/modules/shorewall/files/boilerplate/tcdevices.footer1
-rw-r--r--puppet/modules/shorewall/files/boilerplate/tcdevices.header10
-rw-r--r--puppet/modules/shorewall/files/boilerplate/tcrules.footer1
-rw-r--r--puppet/modules/shorewall/files/boilerplate/tcrules.header15
-rw-r--r--puppet/modules/shorewall/files/boilerplate/tunnel.footer1
-rw-r--r--puppet/modules/shorewall/files/boilerplate/tunnel.header11
-rw-r--r--puppet/modules/shorewall/files/boilerplate/zones.footer1
-rw-r--r--puppet/modules/shorewall/files/boilerplate/zones.header11
-rw-r--r--puppet/modules/shorewall/files/empty/.ignore1
-rw-r--r--puppet/modules/shorewall/manifests/base.pp48
-rw-r--r--puppet/modules/shorewall/manifests/blacklist.pp9
-rw-r--r--puppet/modules/shorewall/manifests/centos.pp13
-rw-r--r--puppet/modules/shorewall/manifests/debian.pp11
-rw-r--r--puppet/modules/shorewall/manifests/entry.pp12
-rw-r--r--puppet/modules/shorewall/manifests/extension_script.pp14
-rw-r--r--puppet/modules/shorewall/manifests/gentoo.pp5
-rw-r--r--puppet/modules/shorewall/manifests/host.pp10
-rw-r--r--puppet/modules/shorewall/manifests/init.pp75
-rw-r--r--puppet/modules/shorewall/manifests/interface.pp29
-rw-r--r--puppet/modules/shorewall/manifests/managed_file.pp17
-rw-r--r--puppet/modules/shorewall/manifests/mangle.pp19
-rw-r--r--puppet/modules/shorewall/manifests/masq.pp17
-rw-r--r--puppet/modules/shorewall/manifests/nat.pp11
-rw-r--r--puppet/modules/shorewall/manifests/params.pp5
-rw-r--r--puppet/modules/shorewall/manifests/policy.pp12
-rw-r--r--puppet/modules/shorewall/manifests/providers.pp16
-rw-r--r--puppet/modules/shorewall/manifests/proxyarp.pp11
-rw-r--r--puppet/modules/shorewall/manifests/rfc1918.pp8
-rw-r--r--puppet/modules/shorewall/manifests/routestopped.pp14
-rw-r--r--puppet/modules/shorewall/manifests/rtrules.pp11
-rw-r--r--puppet/modules/shorewall/manifests/rule.pp20
-rw-r--r--puppet/modules/shorewall/manifests/rule_section.pp7
-rw-r--r--puppet/modules/shorewall/manifests/rules/cobbler.pp19
-rw-r--r--puppet/modules/shorewall/manifests/rules/dns.pp18
-rw-r--r--puppet/modules/shorewall/manifests/rules/dns/disable.pp5
-rw-r--r--puppet/modules/shorewall/manifests/rules/ekeyd.pp10
-rw-r--r--puppet/modules/shorewall/manifests/rules/ftp.pp10
-rw-r--r--puppet/modules/shorewall/manifests/rules/gitdaemon.pp10
-rw-r--r--puppet/modules/shorewall/manifests/rules/gitdaemon/absent.pp5
-rw-r--r--puppet/modules/shorewall/manifests/rules/http.pp10
-rw-r--r--puppet/modules/shorewall/manifests/rules/http/disable.pp5
-rw-r--r--puppet/modules/shorewall/manifests/rules/https.pp10
-rw-r--r--puppet/modules/shorewall/manifests/rules/identd.pp10
-rw-r--r--puppet/modules/shorewall/manifests/rules/imap.pp11
-rw-r--r--puppet/modules/shorewall/manifests/rules/ipsec.pp32
-rw-r--r--puppet/modules/shorewall/manifests/rules/ipsec_nat.pp18
-rw-r--r--puppet/modules/shorewall/manifests/rules/jabberserver.pp19
-rw-r--r--puppet/modules/shorewall/manifests/rules/jetty.pp12
-rw-r--r--puppet/modules/shorewall/manifests/rules/jetty/http.pp9
-rw-r--r--puppet/modules/shorewall/manifests/rules/jetty/ssl.pp11
-rw-r--r--puppet/modules/shorewall/manifests/rules/keyserver.pp11
-rw-r--r--puppet/modules/shorewall/manifests/rules/libvirt/host.pp79
-rw-r--r--puppet/modules/shorewall/manifests/rules/managesieve.pp11
-rw-r--r--puppet/modules/shorewall/manifests/rules/mdns.pp8
-rw-r--r--puppet/modules/shorewall/manifests/rules/munin.pp16
-rw-r--r--puppet/modules/shorewall/manifests/rules/mysql.pp11
-rw-r--r--puppet/modules/shorewall/manifests/rules/nfsd.pp115
-rw-r--r--puppet/modules/shorewall/manifests/rules/ntp/client.pp11
-rw-r--r--puppet/modules/shorewall/manifests/rules/ntp/server.pp10
-rw-r--r--puppet/modules/shorewall/manifests/rules/openfire.pp12
-rw-r--r--puppet/modules/shorewall/manifests/rules/out/ekeyd.pp10
-rw-r--r--puppet/modules/shorewall/manifests/rules/out/git.pp10
-rw-r--r--puppet/modules/shorewall/manifests/rules/out/ibackup.pp12
-rw-r--r--puppet/modules/shorewall/manifests/rules/out/imap.pp11
-rw-r--r--puppet/modules/shorewall/manifests/rules/out/irc.pp10
-rw-r--r--puppet/modules/shorewall/manifests/rules/out/ircs.pp10
-rw-r--r--puppet/modules/shorewall/manifests/rules/out/keyserver.pp11
-rw-r--r--puppet/modules/shorewall/manifests/rules/out/managesieve.pp11
-rw-r--r--puppet/modules/shorewall/manifests/rules/out/munin.pp10
-rw-r--r--puppet/modules/shorewall/manifests/rules/out/mysql.pp11
-rw-r--r--puppet/modules/shorewall/manifests/rules/out/pop3.pp11
-rw-r--r--puppet/modules/shorewall/manifests/rules/out/postgres.pp11
-rw-r--r--puppet/modules/shorewall/manifests/rules/out/puppet.pp20
-rw-r--r--puppet/modules/shorewall/manifests/rules/out/silc.pp19
-rw-r--r--puppet/modules/shorewall/manifests/rules/out/smtp.pp11
-rw-r--r--puppet/modules/shorewall/manifests/rules/out/ssh.pp10
-rw-r--r--puppet/modules/shorewall/manifests/rules/out/ssh/disable.pp5
-rw-r--r--puppet/modules/shorewall/manifests/rules/out/ssh/remove.pp5
-rw-r--r--puppet/modules/shorewall/manifests/rules/out/whois.pp11
-rw-r--r--puppet/modules/shorewall/manifests/rules/out/xmpp.pp10
-rw-r--r--puppet/modules/shorewall/manifests/rules/pop3.pp11
-rw-r--r--puppet/modules/shorewall/manifests/rules/postgres.pp10
-rw-r--r--puppet/modules/shorewall/manifests/rules/puppet.pp11
-rw-r--r--puppet/modules/shorewall/manifests/rules/puppet/master.pp10
-rw-r--r--puppet/modules/shorewall/manifests/rules/rsync.pp10
-rw-r--r--puppet/modules/shorewall/manifests/rules/silcd.pp19
-rw-r--r--puppet/modules/shorewall/manifests/rules/smtp.pp10
-rw-r--r--puppet/modules/shorewall/manifests/rules/smtp/disable.pp5
-rw-r--r--puppet/modules/shorewall/manifests/rules/smtp_submission.pp10
-rw-r--r--puppet/modules/shorewall/manifests/rules/smtp_submission/disable.pp5
-rw-r--r--puppet/modules/shorewall/manifests/rules/smtps.pp10
-rw-r--r--puppet/modules/shorewall/manifests/rules/smtps/disable.pp5
-rw-r--r--puppet/modules/shorewall/manifests/rules/sobby/instance.pp11
-rw-r--r--puppet/modules/shorewall/manifests/rules/ssh.pp13
-rw-r--r--puppet/modules/shorewall/manifests/rules/syslog.pp12
-rw-r--r--puppet/modules/shorewall/manifests/rules/tftp.pp18
-rw-r--r--puppet/modules/shorewall/manifests/rules/tinc.pp34
-rw-r--r--puppet/modules/shorewall/manifests/rules/tomcat.pp12
-rw-r--r--puppet/modules/shorewall/manifests/rules/torify.pp29
-rw-r--r--puppet/modules/shorewall/manifests/rules/torify/allow_tor_transparent_proxy.pp21
-rw-r--r--puppet/modules/shorewall/manifests/rules/torify/allow_tor_user.pp15
-rw-r--r--puppet/modules/shorewall/manifests/rules/torify/redirect_tcp_to_tor.pp40
-rw-r--r--puppet/modules/shorewall/manifests/rules/torify/reject_non_tor.pp32
-rw-r--r--puppet/modules/shorewall/manifests/rules/torify/user.pp27
-rw-r--r--puppet/modules/shorewall/manifests/tcclasses.pp12
-rw-r--r--puppet/modules/shorewall/manifests/tcdevices.pp11
-rw-r--r--puppet/modules/shorewall/manifests/tcrules.pp12
-rw-r--r--puppet/modules/shorewall/manifests/tunnel.pp11
-rw-r--r--puppet/modules/shorewall/manifests/ubuntu/karmic.pp5
-rw-r--r--puppet/modules/shorewall/manifests/zone.pp14
-rw-r--r--puppet/modules/shorewall/templates/debian_default.erb26
-rw-r--r--puppet/modules/site_apache/files/conf.d/security55
-rw-r--r--puppet/modules/site_apache/files/include.d/ssl_common.inc7
-rw-r--r--puppet/modules/site_apache/manifests/common.pp30
-rw-r--r--puppet/modules/site_apache/manifests/common/tls.pp6
-rw-r--r--puppet/modules/site_apache/templates/vhosts.d/api.conf.erb48
-rw-r--r--puppet/modules/site_apache/templates/vhosts.d/common.conf.erb76
-rw-r--r--puppet/modules/site_apache/templates/vhosts.d/hidden_service.conf.erb55
-rw-r--r--puppet/modules/site_apt/files/Debian/51unattended-upgrades-leap6
-rw-r--r--puppet/modules/site_apt/files/keys/leap-archive.gpgbin0 -> 20188 bytes
-rw-r--r--puppet/modules/site_apt/files/keys/leap-experimental-archive.gpgbin0 -> 3423 bytes
-rw-r--r--puppet/modules/site_apt/manifests/dist_upgrade.pp17
-rw-r--r--puppet/modules/site_apt/manifests/init.pp55
-rw-r--r--puppet/modules/site_apt/manifests/leap_repo.pp16
-rw-r--r--puppet/modules/site_apt/manifests/preferences/check_mk.pp9
-rw-r--r--puppet/modules/site_apt/manifests/preferences/passenger.pp14
-rw-r--r--puppet/modules/site_apt/manifests/preferences/rsyslog.pp13
-rw-r--r--puppet/modules/site_apt/manifests/unattended_upgrades.pp20
-rw-r--r--puppet/modules/site_apt/templates/jessie/postfix.seeds1
-rw-r--r--puppet/modules/site_apt/templates/preferences.include_squeeze25
-rw-r--r--puppet/modules/site_apt/templates/secondary.list3
-rw-r--r--puppet/modules/site_apt/templates/wheezy/postfix.seeds1
-rw-r--r--puppet/modules/site_check_mk/files/agent/local_checks/all_hosts/run_node_tests.sh5
-rwxr-xr-xpuppet/modules/site_check_mk/files/agent/local_checks/couchdb/leap_couch_stats.sh122
-rwxr-xr-xpuppet/modules/site_check_mk/files/agent/local_checks/mx/check_leap_mx.sh33
-rw-r--r--puppet/modules/site_check_mk/files/agent/logwatch/bigcouch.cfg28
-rw-r--r--puppet/modules/site_check_mk/files/agent/logwatch/leap_mx.cfg4
-rw-r--r--puppet/modules/site_check_mk/files/agent/logwatch/logwatch.cfg31
-rw-r--r--puppet/modules/site_check_mk/files/agent/logwatch/openvpn.cfg19
-rw-r--r--puppet/modules/site_check_mk/files/agent/logwatch/soledad.cfg6
-rw-r--r--puppet/modules/site_check_mk/files/agent/logwatch/stunnel.cfg10
-rw-r--r--puppet/modules/site_check_mk/files/agent/logwatch/syslog/bigcouch.cfg5
-rw-r--r--puppet/modules/site_check_mk/files/agent/logwatch/syslog/couchdb.cfg2
-rw-r--r--puppet/modules/site_check_mk/files/agent/logwatch/syslog_header.cfg1
-rw-r--r--puppet/modules/site_check_mk/files/agent/logwatch/syslog_tail.cfg21
-rw-r--r--puppet/modules/site_check_mk/files/agent/logwatch/webapp.cfg8
-rwxr-xr-xpuppet/modules/site_check_mk/files/agent/nagios_plugins/check_unix_open_fds.pl322
-rwxr-xr-xpuppet/modules/site_check_mk/files/agent/plugins/mk_logwatch.1.2.4374
-rw-r--r--puppet/modules/site_check_mk/files/extra_service_conf.mk14
-rw-r--r--puppet/modules/site_check_mk/files/ignored_services.mk3
-rw-r--r--puppet/modules/site_check_mk/manifests/agent.pp35
-rw-r--r--puppet/modules/site_check_mk/manifests/agent/couchdb.pp34
-rw-r--r--puppet/modules/site_check_mk/manifests/agent/couchdb/bigcouch.pp49
-rw-r--r--puppet/modules/site_check_mk/manifests/agent/couchdb/plain.pp23
-rw-r--r--puppet/modules/site_check_mk/manifests/agent/haproxy.pp15
-rw-r--r--puppet/modules/site_check_mk/manifests/agent/haveged.pp15
-rw-r--r--puppet/modules/site_check_mk/manifests/agent/logwatch.pp36
-rw-r--r--puppet/modules/site_check_mk/manifests/agent/logwatch/syslog.pp18
-rw-r--r--puppet/modules/site_check_mk/manifests/agent/mrpe.pp24
-rw-r--r--puppet/modules/site_check_mk/manifests/agent/mx.pp27
-rw-r--r--puppet/modules/site_check_mk/manifests/agent/openvpn.pp10
-rw-r--r--puppet/modules/site_check_mk/manifests/agent/package/nagios_plugins_contrib.pp5
-rw-r--r--puppet/modules/site_check_mk/manifests/agent/package/perl_plugin.pp5
-rw-r--r--puppet/modules/site_check_mk/manifests/agent/soledad.pp17
-rw-r--r--puppet/modules/site_check_mk/manifests/agent/stunnel.pp9
-rw-r--r--puppet/modules/site_check_mk/manifests/agent/webapp.pp15
-rw-r--r--puppet/modules/site_check_mk/manifests/server.pp103
-rw-r--r--puppet/modules/site_check_mk/templates/extra_host_conf.mk13
-rw-r--r--puppet/modules/site_check_mk/templates/host_contactgroups.mk17
-rw-r--r--puppet/modules/site_check_mk/templates/hostgroups.mk17
-rw-r--r--puppet/modules/site_check_mk/templates/use_ssh.mk6
-rw-r--r--puppet/modules/site_config/files/xterm-title.sh8
-rw-r--r--puppet/modules/site_config/lib/facter/dhcp_enabled.rb22
-rw-r--r--puppet/modules/site_config/lib/facter/ip_interface.rb13
-rw-r--r--puppet/modules/site_config/manifests/caching_resolver.pp27
-rw-r--r--puppet/modules/site_config/manifests/default.pp71
-rw-r--r--puppet/modules/site_config/manifests/dhclient.pp40
-rw-r--r--puppet/modules/site_config/manifests/files.pp24
-rw-r--r--puppet/modules/site_config/manifests/hosts.pp44
-rw-r--r--puppet/modules/site_config/manifests/initial_firewall.pp64
-rw-r--r--puppet/modules/site_config/manifests/packages.pp32
-rw-r--r--puppet/modules/site_config/manifests/packages/build_essential.pp28
-rw-r--r--puppet/modules/site_config/manifests/packages/gnutls.pp5
-rw-r--r--puppet/modules/site_config/manifests/params.pp35
-rw-r--r--puppet/modules/site_config/manifests/remove.pp11
-rw-r--r--puppet/modules/site_config/manifests/remove/bigcouch.pp42
-rw-r--r--puppet/modules/site_config/manifests/remove/files.pp56
-rw-r--r--puppet/modules/site_config/manifests/remove/jessie.pp14
-rw-r--r--puppet/modules/site_config/manifests/remove/monitoring.pp13
-rw-r--r--puppet/modules/site_config/manifests/remove/tapicero.pp72
-rw-r--r--puppet/modules/site_config/manifests/remove/webapp.pp7
-rw-r--r--puppet/modules/site_config/manifests/resolvconf.pp14
-rw-r--r--puppet/modules/site_config/manifests/ruby.pp8
-rw-r--r--puppet/modules/site_config/manifests/ruby/dev.pp8
-rw-r--r--puppet/modules/site_config/manifests/setup.pp50
-rw-r--r--puppet/modules/site_config/manifests/shell.pp22
-rw-r--r--puppet/modules/site_config/manifests/slow.pp10
-rw-r--r--puppet/modules/site_config/manifests/sysctl.pp8
-rw-r--r--puppet/modules/site_config/manifests/syslog.pp62
-rw-r--r--puppet/modules/site_config/manifests/vagrant.pp11
-rw-r--r--puppet/modules/site_config/manifests/x509/ca.pp11
-rw-r--r--puppet/modules/site_config/manifests/x509/ca_bundle.pp17
-rw-r--r--puppet/modules/site_config/manifests/x509/cert.pp12
-rw-r--r--puppet/modules/site_config/manifests/x509/client_ca/ca.pp16
-rw-r--r--puppet/modules/site_config/manifests/x509/client_ca/key.pp16
-rw-r--r--puppet/modules/site_config/manifests/x509/commercial/ca.pp11
-rw-r--r--puppet/modules/site_config/manifests/x509/commercial/cert.pp15
-rw-r--r--puppet/modules/site_config/manifests/x509/commercial/key.pp11
-rw-r--r--puppet/modules/site_config/manifests/x509/key.pp11
-rw-r--r--puppet/modules/site_config/templates/hosts19
-rw-r--r--puppet/modules/site_config/templates/ipv4firewall_up.rules.erb14
-rw-r--r--puppet/modules/site_config/templates/ipv6firewall_up.rules.erb8
-rw-r--r--puppet/modules/site_config/templates/reload_dhclient.erb13
-rw-r--r--puppet/modules/site_couchdb/files/couchdb_scripts_defaults.conf4
-rw-r--r--puppet/modules/site_couchdb/files/designs/Readme.md14
-rw-r--r--puppet/modules/site_couchdb/files/designs/customers/Customer.json18
-rw-r--r--puppet/modules/site_couchdb/files/designs/identities/Identity.json34
-rw-r--r--puppet/modules/site_couchdb/files/designs/invite_codes/InviteCode.json22
-rw-r--r--puppet/modules/site_couchdb/files/designs/messages/Message.json18
-rw-r--r--puppet/modules/site_couchdb/files/designs/sessions/Session.json8
-rw-r--r--puppet/modules/site_couchdb/files/designs/shared/docs.json8
-rw-r--r--puppet/modules/site_couchdb/files/designs/shared/syncs.json11
-rw-r--r--puppet/modules/site_couchdb/files/designs/shared/transactions.json13
-rw-r--r--puppet/modules/site_couchdb/files/designs/tickets/Ticket.json50
-rw-r--r--puppet/modules/site_couchdb/files/designs/tokens/Token.json14
-rw-r--r--puppet/modules/site_couchdb/files/designs/users/User.json22
-rwxr-xr-xpuppet/modules/site_couchdb/files/leap_ca_daemon157
-rw-r--r--puppet/modules/site_couchdb/files/local.ini8
-rw-r--r--puppet/modules/site_couchdb/files/runit_config6
-rw-r--r--puppet/modules/site_couchdb/lib/puppet/parser/functions/rotated_db_name.rb24
-rw-r--r--puppet/modules/site_couchdb/manifests/add_users.pp57
-rw-r--r--puppet/modules/site_couchdb/manifests/backup.pp23
-rw-r--r--puppet/modules/site_couchdb/manifests/bigcouch.pp50
-rw-r--r--puppet/modules/site_couchdb/manifests/bigcouch/add_nodes.pp8
-rw-r--r--puppet/modules/site_couchdb/manifests/bigcouch/compaction.pp8
-rw-r--r--puppet/modules/site_couchdb/manifests/bigcouch/settle_cluster.pp11
-rw-r--r--puppet/modules/site_couchdb/manifests/create_dbs.pp102
-rw-r--r--puppet/modules/site_couchdb/manifests/designs.pp46
-rw-r--r--puppet/modules/site_couchdb/manifests/init.pp81
-rw-r--r--puppet/modules/site_couchdb/manifests/logrotate.pp14
-rw-r--r--puppet/modules/site_couchdb/manifests/mirror.pp78
-rw-r--r--puppet/modules/site_couchdb/manifests/plain.pp14
-rw-r--r--puppet/modules/site_couchdb/manifests/setup.pp61
-rw-r--r--puppet/modules/site_couchdb/manifests/upload_design.pp14
-rw-r--r--puppet/modules/site_haproxy/files/haproxy-stats.cfg6
-rw-r--r--puppet/modules/site_haproxy/manifests/init.pp41
-rw-r--r--puppet/modules/site_haproxy/templates/couch.erb32
-rw-r--r--puppet/modules/site_haproxy/templates/haproxy.cfg.erb11
-rw-r--r--puppet/modules/site_mx/manifests/init.pp20
-rw-r--r--puppet/modules/site_nagios/files/configs/Debian/nagios.cfg1302
-rwxr-xr-xpuppet/modules/site_nagios/files/plugins/check_last_regex_in_log85
-rw-r--r--puppet/modules/site_nagios/manifests/add_host_services.pp32
-rw-r--r--puppet/modules/site_nagios/manifests/add_service.pp32
-rw-r--r--puppet/modules/site_nagios/manifests/init.pp13
-rw-r--r--puppet/modules/site_nagios/manifests/plugins.pp16
-rw-r--r--puppet/modules/site_nagios/manifests/server.pp97
-rw-r--r--puppet/modules/site_nagios/manifests/server/add_contacts.pp18
-rw-r--r--puppet/modules/site_nagios/manifests/server/apache.pp25
-rw-r--r--puppet/modules/site_nagios/manifests/server/contactgroup.pp8
-rw-r--r--puppet/modules/site_nagios/manifests/server/hostgroup.pp7
-rw-r--r--puppet/modules/site_nagios/manifests/server/icli.pp26
-rw-r--r--puppet/modules/site_nagios/templates/icli_aliases.erb7
-rw-r--r--puppet/modules/site_nickserver/manifests/init.pp178
-rw-r--r--puppet/modules/site_nickserver/templates/nickserver-proxy.conf.erb19
-rw-r--r--puppet/modules/site_nickserver/templates/nickserver.yml.erb19
-rw-r--r--puppet/modules/site_obfsproxy/README0
-rw-r--r--puppet/modules/site_obfsproxy/manifests/init.pp38
-rw-r--r--puppet/modules/site_openvpn/README20
-rw-r--r--puppet/modules/site_openvpn/manifests/dh_key.pp10
-rw-r--r--puppet/modules/site_openvpn/manifests/init.pp238
-rw-r--r--puppet/modules/site_openvpn/manifests/resolver.pp50
-rw-r--r--puppet/modules/site_openvpn/manifests/server_config.pp228
-rw-r--r--puppet/modules/site_openvpn/templates/add_gateway_ips.sh.erb11
-rw-r--r--puppet/modules/site_postfix/files/checks/received_anon2
-rw-r--r--puppet/modules/site_postfix/manifests/debug.pp9
-rw-r--r--puppet/modules/site_postfix/manifests/mx.pp152
-rw-r--r--puppet/modules/site_postfix/manifests/mx/checks.pp23
-rw-r--r--puppet/modules/site_postfix/manifests/mx/received_anon.pp13
-rw-r--r--puppet/modules/site_postfix/manifests/mx/rewrite_openpgp_header.pp11
-rw-r--r--puppet/modules/site_postfix/manifests/mx/smtp_auth.pp6
-rw-r--r--puppet/modules/site_postfix/manifests/mx/smtp_tls.pp43
-rw-r--r--puppet/modules/site_postfix/manifests/mx/smtpd_checks.pp36
-rw-r--r--puppet/modules/site_postfix/manifests/mx/smtpd_tls.pp69
-rw-r--r--puppet/modules/site_postfix/manifests/mx/static_aliases.pp88
-rw-r--r--puppet/modules/site_postfix/manifests/satellite.pp47
-rw-r--r--puppet/modules/site_postfix/templates/checks/helo_access.erb21
-rw-r--r--puppet/modules/site_postfix/templates/checks/rewrite_openpgp_headers.erb13
-rw-r--r--puppet/modules/site_postfix/templates/virtual-aliases.erb21
-rw-r--r--puppet/modules/site_rsyslog/templates/client.conf.erb134
-rw-r--r--puppet/modules/site_shorewall/files/Debian/shorewall.service23
-rw-r--r--puppet/modules/site_shorewall/manifests/defaults.pp86
-rw-r--r--puppet/modules/site_shorewall/manifests/dnat.pp19
-rw-r--r--puppet/modules/site_shorewall/manifests/dnat_rule.pp50
-rw-r--r--puppet/modules/site_shorewall/manifests/eip.pp92
-rw-r--r--puppet/modules/site_shorewall/manifests/ip_forward.pp10
-rw-r--r--puppet/modules/site_shorewall/manifests/monitor.pp8
-rw-r--r--puppet/modules/site_shorewall/manifests/mx.pp24
-rw-r--r--puppet/modules/site_shorewall/manifests/obfsproxy.pp25
-rw-r--r--puppet/modules/site_shorewall/manifests/service/http.pp13
-rw-r--r--puppet/modules/site_shorewall/manifests/service/https.pp12
-rw-r--r--puppet/modules/site_shorewall/manifests/service/smtp.pp13
-rw-r--r--puppet/modules/site_shorewall/manifests/service/webapp_api.pp23
-rw-r--r--puppet/modules/site_shorewall/manifests/soledad.pp23
-rw-r--r--puppet/modules/site_shorewall/manifests/sshd.pp31
-rw-r--r--puppet/modules/site_shorewall/manifests/stunnel/client.pp40
-rw-r--r--puppet/modules/site_shorewall/manifests/stunnel/server.pp22
-rw-r--r--puppet/modules/site_shorewall/manifests/tor.pp26
-rw-r--r--puppet/modules/site_shorewall/manifests/webapp.pp7
-rw-r--r--puppet/modules/site_squid_deb_proxy/manifests/client.pp5
-rw-r--r--puppet/modules/site_sshd/manifests/authorized_keys.pp34
-rw-r--r--puppet/modules/site_sshd/manifests/deploy_authorized_keys.pp9
-rw-r--r--puppet/modules/site_sshd/manifests/init.pp82
-rw-r--r--puppet/modules/site_sshd/manifests/mosh.pp21
-rw-r--r--puppet/modules/site_sshd/templates/authorized_keys.erb10
-rw-r--r--puppet/modules/site_sshd/templates/ssh_config.erb40
-rw-r--r--puppet/modules/site_sshd/templates/ssh_known_hosts.erb7
-rw-r--r--puppet/modules/site_static/README3
-rw-r--r--puppet/modules/site_static/manifests/domain.pp33
-rw-r--r--puppet/modules/site_static/manifests/init.pp72
-rw-r--r--puppet/modules/site_static/manifests/location.pp36
-rw-r--r--puppet/modules/site_static/templates/amber.erb13
-rw-r--r--puppet/modules/site_static/templates/apache.conf.erb88
-rw-r--r--puppet/modules/site_static/templates/rack.erb19
-rw-r--r--puppet/modules/site_stunnel/manifests/client.pp64
-rw-r--r--puppet/modules/site_stunnel/manifests/clients.pp23
-rw-r--r--puppet/modules/site_stunnel/manifests/init.pp48
-rw-r--r--puppet/modules/site_stunnel/manifests/override_service.pp18
-rw-r--r--puppet/modules/site_stunnel/manifests/servers.pp51
-rw-r--r--puppet/modules/site_tor/manifests/disable_exit.pp7
-rw-r--r--puppet/modules/site_tor/manifests/init.pp45
-rw-r--r--puppet/modules/site_webapp/files/server-status.conf26
-rw-r--r--puppet/modules/site_webapp/manifests/apache.pp28
-rw-r--r--puppet/modules/site_webapp/manifests/common_vhost.pp18
-rw-r--r--puppet/modules/site_webapp/manifests/couchdb.pp52
-rw-r--r--puppet/modules/site_webapp/manifests/cron.pp37
-rw-r--r--puppet/modules/site_webapp/manifests/hidden_service.pp52
-rw-r--r--puppet/modules/site_webapp/manifests/init.pp179
-rw-r--r--puppet/modules/site_webapp/templates/config.yml.erb36
-rw-r--r--puppet/modules/site_webapp/templates/couchdb.admin.yml.erb9
-rw-r--r--puppet/modules/site_webapp/templates/couchdb.yml.erb9
-rw-r--r--puppet/modules/soledad/manifests/client.pp16
-rw-r--r--puppet/modules/soledad/manifests/common.pp8
-rw-r--r--puppet/modules/soledad/manifests/server.pp104
-rw-r--r--puppet/modules/soledad/templates/default-soledad.erb5
-rw-r--r--puppet/modules/soledad/templates/soledad-server.conf.erb12
-rw-r--r--puppet/modules/squid_deb_proxy/README.md8
-rw-r--r--puppet/modules/squid_deb_proxy/files/Debian/squid-deb-proxy.conf91
-rw-r--r--puppet/modules/squid_deb_proxy/files/Ubuntu/squid-deb-proxy.conf89
-rw-r--r--puppet/modules/squid_deb_proxy/files/allowed-networks-src.acl.d/20-custom1
-rwxr-xr-xpuppet/modules/squid_deb_proxy/files/client/apt-avahi-discover138
-rw-r--r--puppet/modules/squid_deb_proxy/files/mirror-dstdomain.acl.d/20-custom1
-rw-r--r--puppet/modules/squid_deb_proxy/manifests/client.pp16
-rw-r--r--puppet/modules/squid_deb_proxy/manifests/server.pp41
-rw-r--r--puppet/modules/sshd/.fixtures.yml3
-rw-r--r--puppet/modules/sshd/.gitignore4
-rw-r--r--puppet/modules/sshd/.rspec4
-rw-r--r--puppet/modules/sshd/.travis.yml27
-rw-r--r--puppet/modules/sshd/Gemfile14
-rw-r--r--puppet/modules/sshd/Gemfile.lock116
-rw-r--r--puppet/modules/sshd/LICENSE674
-rw-r--r--puppet/modules/sshd/Modulefile10
-rw-r--r--puppet/modules/sshd/Puppetfile3
-rw-r--r--puppet/modules/sshd/Puppetfile.lock8
-rw-r--r--puppet/modules/sshd/README.md247
-rw-r--r--puppet/modules/sshd/Rakefile16
-rw-r--r--puppet/modules/sshd/files/autossh.init.d164
-rw-r--r--puppet/modules/sshd/lib/facter/ssh_version.rb5
-rw-r--r--puppet/modules/sshd/lib/puppet/parser/functions/ssh_keygen.rb30
-rw-r--r--puppet/modules/sshd/manifests/autossh.pp40
-rw-r--r--puppet/modules/sshd/manifests/base.pp41
-rw-r--r--puppet/modules/sshd/manifests/client.pp22
-rw-r--r--puppet/modules/sshd/manifests/client/base.pp15
-rw-r--r--puppet/modules/sshd/manifests/client/debian.pp5
-rw-r--r--puppet/modules/sshd/manifests/client/linux.pp5
-rw-r--r--puppet/modules/sshd/manifests/debian.pp13
-rw-r--r--puppet/modules/sshd/manifests/gentoo.pp5
-rw-r--r--puppet/modules/sshd/manifests/init.pp92
-rw-r--r--puppet/modules/sshd/manifests/libssh2.pp7
-rw-r--r--puppet/modules/sshd/manifests/libssh2/devel.pp7
-rw-r--r--puppet/modules/sshd/manifests/linux.pp8
-rw-r--r--puppet/modules/sshd/manifests/nagios.pp24
-rw-r--r--puppet/modules/sshd/manifests/openbsd.pp8
-rw-r--r--puppet/modules/sshd/manifests/redhat.pp5
-rw-r--r--puppet/modules/sshd/manifests/ssh_authorized_key.pp85
-rw-r--r--puppet/modules/sshd/manifests/sshkey.pp21
-rw-r--r--puppet/modules/sshd/spec/classes/client_spec.rb42
-rw-r--r--puppet/modules/sshd/spec/classes/init_spec.rb122
-rw-r--r--puppet/modules/sshd/spec/defines/ssh_authorized_key_spec.rb45
-rw-r--r--puppet/modules/sshd/spec/functions/ssh_keygen_spec.rb116
-rw-r--r--puppet/modules/sshd/spec/spec_helper.rb21
-rw-r--r--puppet/modules/sshd/spec/spec_helper_system.rb25
l---------puppet/modules/sshd/templates/sshd_config/CentOS_5.erb1
-rw-r--r--puppet/modules/sshd/templates/sshd_config/CentOS_6.erb172
-rw-r--r--puppet/modules/sshd/templates/sshd_config/CentOS_7.erb186
-rw-r--r--puppet/modules/sshd/templates/sshd_config/Debian_jessie.erb124
-rw-r--r--puppet/modules/sshd/templates/sshd_config/Debian_sid.erb124
-rw-r--r--puppet/modules/sshd/templates/sshd_config/Debian_squeeze.erb127
-rw-r--r--puppet/modules/sshd/templates/sshd_config/Debian_wheezy.erb132
-rw-r--r--puppet/modules/sshd/templates/sshd_config/FreeBSD.erb168
-rw-r--r--puppet/modules/sshd/templates/sshd_config/Gentoo.erb164
-rw-r--r--puppet/modules/sshd/templates/sshd_config/OpenBSD.erb144
-rw-r--r--puppet/modules/sshd/templates/sshd_config/Ubuntu.erb133
-rw-r--r--puppet/modules/sshd/templates/sshd_config/Ubuntu_lucid.erb136
l---------puppet/modules/sshd/templates/sshd_config/Ubuntu_oneiric.erb1
l---------puppet/modules/sshd/templates/sshd_config/Ubuntu_precise.erb1
l---------puppet/modules/sshd/templates/sshd_config/XenServer_xenenterprise.erb1
-rw-r--r--puppet/modules/stdlib/.fixtures.yml3
-rw-r--r--puppet/modules/stdlib/.gemspec40
-rw-r--r--puppet/modules/stdlib/.gitignore9
-rw-r--r--puppet/modules/stdlib/.project23
-rw-r--r--puppet/modules/stdlib/.rspec4
-rw-r--r--puppet/modules/stdlib/.sync.yml9
-rw-r--r--puppet/modules/stdlib/.travis.yml18
-rw-r--r--puppet/modules/stdlib/CHANGELOG.md500
-rw-r--r--puppet/modules/stdlib/CONTRIBUTING.md220
-rw-r--r--puppet/modules/stdlib/Gemfile35
-rw-r--r--puppet/modules/stdlib/LICENSE19
-rw-r--r--puppet/modules/stdlib/README.markdown741
-rw-r--r--puppet/modules/stdlib/README_DEVELOPER.markdown35
-rw-r--r--puppet/modules/stdlib/README_SPECS.markdown7
-rw-r--r--puppet/modules/stdlib/RELEASE_PROCESS.markdown24
-rw-r--r--puppet/modules/stdlib/Rakefile18
-rw-r--r--puppet/modules/stdlib/lib/facter/facter_dot_d.rb202
-rw-r--r--puppet/modules/stdlib/lib/facter/netmask_cidr_interface.rb22
-rw-r--r--puppet/modules/stdlib/lib/facter/pe_version.rb53
-rw-r--r--puppet/modules/stdlib/lib/facter/puppet_vardir.rb26
-rw-r--r--puppet/modules/stdlib/lib/facter/root_home.rb32
-rw-r--r--puppet/modules/stdlib/lib/facter/util/puppet_settings.rb21
-rw-r--r--puppet/modules/stdlib/lib/puppet/functions/type_of.rb17
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/abs.rb36
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/any2array.rb33
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/base64.rb37
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/basename.rb34
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/bool2num.rb26
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/bool2str.rb27
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/camelcase.rb33
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/capitalize.rb33
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/chomp.rb34
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/chop.rb36
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/concat.rb41
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/count.rb22
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/deep_merge.rb44
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/defined_with_params.rb35
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/delete.rb49
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/delete_at.rb49
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/delete_undef_values.rb34
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/delete_values.rb26
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/difference.rb36
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/dirname.rb15
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/downcase.rb32
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/empty.rb27
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/ensure_packages.rb35
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/ensure_resource.rb46
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/flatten.rb33
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/floor.rb25
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/fqdn_rotate.rb45
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/get_module_path.rb17
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/getparam.rb35
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/getvar.rb29
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/grep.rb33
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/has_interface_with.rb68
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/has_ip_address.rb25
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/has_ip_network.rb25
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/has_key.rb28
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/hash.rb41
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/intersection.rb34
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/is_array.rb22
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/is_bool.rb22
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/is_domain_name.rb50
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/is_float.rb30
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/is_function_available.rb26
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/is_hash.rb22
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/is_integer.rb45
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/is_ip_address.rb32
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/is_mac_address.rb27
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/is_numeric.rb75
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/is_string.rb26
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/join.rb41
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/join_keys_to_values.rb47
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/keys.rb26
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/loadyaml.rb20
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/lstrip.rb32
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/max.rb21
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/member.rb62
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/merge.rb34
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/min.rb21
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/num2bool.rb43
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/obfuscate_email.rb16
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/parsejson.rb24
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/parseyaml.rb24
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/pick.rb29
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/pick_default.rb35
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/prefix.rb45
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/private.rb29
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/range.rb88
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/reject.rb31
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/reverse.rb27
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/rstrip.rb31
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/shuffle.rb45
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/size.rb48
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/sort.rb27
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/squeeze.rb36
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/str2bool.rb46
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/str2saltedsha1.rb32
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/str2saltedsha512.rb32
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/str2sha1_and_salt.rb36
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/str_and_salt2sha1.rb32
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/strftime.rb107
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/strip.rb38
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/suffix.rb45
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/swapcase.rb38
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/time.rb49
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/to_bytes.rb31
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/type.rb19
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/type3x.rb51
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/union.rb34
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/unique.rb50
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/upcase.rb40
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/uriescape.rb34
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/validate_absolute_path.rb69
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/validate_array.rb33
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/validate_augeas.rb83
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/validate_bool.rb34
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/validate_cmd.rb63
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/validate_hash.rb33
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/validate_ipv4_address.rb48
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/validate_ipv6_address.rb49
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/validate_re.rb40
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/validate_slength.rb71
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/validate_string.rb38
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/values.rb39
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/values_at.rb99
-rw-r--r--puppet/modules/stdlib/lib/puppet/parser/functions/zip.rb39
-rw-r--r--puppet/modules/stdlib/lib/puppet/provider/file_line/ruby.rb85
-rw-r--r--puppet/modules/stdlib/lib/puppet/type/anchor.rb46
-rw-r--r--puppet/modules/stdlib/lib/puppet/type/file_line.rb75
-rw-r--r--puppet/modules/stdlib/manifests/init.pp20
-rw-r--r--puppet/modules/stdlib/manifests/stages.pp43
-rw-r--r--puppet/modules/stdlib/metadata.json113
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/abs_spec.rb30
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/any2array_spec.rb49
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/base64_spec.rb18
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/bool2num_spec.rb34
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/build_csv.rb83
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/capitalize_spec.rb33
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/chomp_spec.rb21
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/chop_spec.rb45
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/concat_spec.rb40
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/count_spec.rb30
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/deep_merge_spec.rb20
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/defined_with_params_spec.rb22
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/delete_at_spec.rb19
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/delete_spec.rb19
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/delete_undef_values_spec.rb19
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/delete_values_spec.rb25
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/difference_spec.rb26
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/dirname_spec.rb42
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/downcase_spec.rb39
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/empty_spec.rb39
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/ensure_packages_spec.rb22
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/ensure_resource_spec.rb22
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/flatten_spec.rb39
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/floor_spec.rb39
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/fqdn_rotate_spec.rb47
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/get_module_path_spec.rb27
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/getparam_spec.rb24
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/getvar_spec.rb26
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/grep_spec.rb26
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/has_interface_with_spec.rb54
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/has_ip_address_spec.rb33
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/has_ip_network_spec.rb33
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/has_key_spec.rb41
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/hash_spec.rb26
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/intersection_spec.rb27
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/is_array_spec.rb67
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/is_bool_spec.rb81
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/is_domain_name_spec.rb83
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/is_float_spec.rb86
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/is_function_available_spec.rb58
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/is_hash_spec.rb63
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/is_integer_spec.rb95
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/is_ip_address_spec.rb80
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/is_mac_address_spec.rb38
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/is_numeric_spec.rb95
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/is_string_spec.rb102
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/join_keys_to_values_spec.rb24
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/join_spec.rb26
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/keys_spec.rb23
-rw-r--r--puppet/modules/stdlib/spec/acceptance/loadyaml_spec.rb33
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/lstrip_spec.rb34
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/max_spec.rb20
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/member_spec.rb54
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/merge_spec.rb23
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/min_spec.rb20
-rw-r--r--puppet/modules/stdlib/spec/acceptance/nodesets/centos-59-x64.yml10
-rw-r--r--puppet/modules/stdlib/spec/acceptance/nodesets/centos-6-vcloud.yml15
-rw-r--r--puppet/modules/stdlib/spec/acceptance/nodesets/centos-64-x64-pe.yml12
-rw-r--r--puppet/modules/stdlib/spec/acceptance/nodesets/centos-64-x64.yml10
-rw-r--r--puppet/modules/stdlib/spec/acceptance/nodesets/centos-65-x64.yml10
-rw-r--r--puppet/modules/stdlib/spec/acceptance/nodesets/default.yml10
-rw-r--r--puppet/modules/stdlib/spec/acceptance/nodesets/fedora-18-x64.yml10
-rw-r--r--puppet/modules/stdlib/spec/acceptance/nodesets/sles-11-x64.yml10
-rw-r--r--puppet/modules/stdlib/spec/acceptance/nodesets/ubuntu-server-10044-x64.yml10
-rw-r--r--puppet/modules/stdlib/spec/acceptance/nodesets/ubuntu-server-12042-x64.yml10
-rw-r--r--puppet/modules/stdlib/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml11
-rw-r--r--puppet/modules/stdlib/spec/acceptance/nodesets/windows-2003-i386.yml26
-rw-r--r--puppet/modules/stdlib/spec/acceptance/nodesets/windows-2003-x86_64.yml26
-rw-r--r--puppet/modules/stdlib/spec/acceptance/nodesets/windows-2008-x86_64.yml26
-rw-r--r--puppet/modules/stdlib/spec/acceptance/nodesets/windows-2008r2-x86_64.yml26
-rw-r--r--puppet/modules/stdlib/spec/acceptance/nodesets/windows-2012-x86_64.yml26
-rw-r--r--puppet/modules/stdlib/spec/acceptance/nodesets/windows-2012r2-x86_64.yml26
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/num2bool_spec.rb76
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/parsejson_spec.rb34
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/parseyaml_spec.rb35
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/pick_default_spec.rb54
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/pick_spec.rb44
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/prefix_spec.rb42
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/range_spec.rb36
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/reject_spec.rb42
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/reverse_spec.rb23
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/rstrip_spec.rb34
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/shuffle_spec.rb34
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/size_spec.rb55
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/sort_spec.rb34
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/squeeze_spec.rb47
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/str2bool_spec.rb31
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/str2saltedsha512_spec.rb22
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/strftime_spec.rb22
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/strip_spec.rb34
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/suffix_spec.rb42
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/swapcase_spec.rb22
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/time_spec.rb36
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/to_bytes_spec.rb27
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/type_spec.rb37
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/union_spec.rb24
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/unique_spec.rb33
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/unsupported_spec.rb11
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/upcase_spec.rb33
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/uriescape_spec.rb23
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/validate_absolute_path_spec.rb31
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/validate_array_spec.rb37
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/validate_augeas_spec.rb63
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/validate_bool_spec.rb37
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/validate_cmd_spec.rb52
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/validate_hash_spec.rb37
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/validate_ipv4_address_spec.rb31
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/validate_ipv6_address_spec.rb31
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/validate_re_spec.rb47
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/validate_slength_spec.rb72
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/validate_string_spec.rb36
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/values_at_spec.rb73
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/values_spec.rb35
-rwxr-xr-xpuppet/modules/stdlib/spec/acceptance/zip_spec.rb86
-rwxr-xr-xpuppet/modules/stdlib/spec/classes/anchor_spec.rb30
-rw-r--r--puppet/modules/stdlib/spec/fixtures/dscacheutil/root8
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/abs_spec.rb25
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/any2array_spec.rb55
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/base64_spec.rb34
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/bool2num_spec.rb38
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/capitalize_spec.rb28
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/chomp_spec.rb28
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/chop_spec.rb28
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/concat_spec.rb50
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/count_spec.rb31
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/deep_merge_spec.rb105
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/defined_with_params_spec.rb37
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/delete_at_spec.rb25
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/delete_spec.rb61
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/delete_undef_values_spec.rb41
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/delete_values_spec.rb36
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/difference_spec.rb19
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/dirname_spec.rb24
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/downcase_spec.rb33
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/empty_spec.rb32
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/ensure_packages_spec.rb81
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/ensure_resource_spec.rb113
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/flatten_spec.rb27
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/floor_spec.rb39
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/fqdn_rotate_spec.rb43
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/get_module_path_spec.rb46
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/getparam_spec.rb76
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/getvar_spec.rb37
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/grep_spec.rb19
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/has_interface_with_spec.rb64
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/has_ip_address_spec.rb39
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/has_ip_network_spec.rb36
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/has_key_spec.rb42
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/hash_spec.rb19
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/intersection_spec.rb19
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/is_array_spec.rb29
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/is_bool_spec.rb44
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/is_domain_name_spec.rb64
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/is_float_spec.rb33
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/is_function_available.rb31
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/is_hash_spec.rb29
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/is_integer_spec.rb69
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/is_ip_address_spec.rb39
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/is_mac_address_spec.rb29
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/is_numeric_spec.rb119
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/is_string_spec.rb34
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/join_keys_to_values_spec.rb40
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/join_spec.rb19
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/keys_spec.rb21
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/loadyaml_spec.rb25
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/lstrip_spec.rb28
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/max_spec.rb27
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/member_spec.rb34
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/merge_spec.rb52
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/min_spec.rb27
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/num2bool_spec.rb67
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/parsejson_spec.rb22
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/parseyaml_spec.rb24
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/pick_default_spec.rb58
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/pick_spec.rb34
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/prefix_spec.rb28
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/private_spec.rb55
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/range_spec.rb86
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/reject_spec.rb20
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/reverse_spec.rb28
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/rstrip_spec.rb33
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/shuffle_spec.rb33
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/size_spec.rb24
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/sort_spec.rb24
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/squeeze_spec.rb24
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/str2bool_spec.rb31
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/str2saltedsha512_spec.rb45
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/strftime_spec.rb29
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/strip_spec.rb27
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/suffix_spec.rb27
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/swapcase_spec.rb28
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/time_spec.rb29
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/to_bytes_spec.rb83
-rw-r--r--puppet/modules/stdlib/spec/functions/type3x_spec.rb43
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/type_spec.rb44
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/union_spec.rb19
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/unique_spec.rb33
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/upcase_spec.rb33
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/uriescape_spec.rb33
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/validate_absolute_path_spec.rb104
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/validate_array_spec.rb38
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/validate_augeas_spec.rb103
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/validate_bool_spec.rb51
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/validate_cmd_spec.rb85
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/validate_hash_spec.rb43
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/validate_ipv4_address_spec.rb64
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/validate_ipv6_address_spec.rb67
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/validate_re_spec.rb77
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/validate_slength_spec.rb67
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/validate_string_spec.rb60
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/values_at_spec.rb38
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/values_spec.rb31
-rwxr-xr-xpuppet/modules/stdlib/spec/functions/zip_spec.rb31
-rwxr-xr-xpuppet/modules/stdlib/spec/lib/puppet_spec/compiler.rb47
-rwxr-xr-xpuppet/modules/stdlib/spec/lib/puppet_spec/database.rb30
-rwxr-xr-xpuppet/modules/stdlib/spec/lib/puppet_spec/files.rb61
-rwxr-xr-xpuppet/modules/stdlib/spec/lib/puppet_spec/fixtures.rb29
-rwxr-xr-xpuppet/modules/stdlib/spec/lib/puppet_spec/matchers.rb121
-rwxr-xr-xpuppet/modules/stdlib/spec/lib/puppet_spec/modules.rb27
-rwxr-xr-xpuppet/modules/stdlib/spec/lib/puppet_spec/pops.rb17
-rwxr-xr-xpuppet/modules/stdlib/spec/lib/puppet_spec/scope.rb15
-rwxr-xr-xpuppet/modules/stdlib/spec/lib/puppet_spec/settings.rb16
-rwxr-xr-xpuppet/modules/stdlib/spec/lib/puppet_spec/verbose.rb10
-rwxr-xr-xpuppet/modules/stdlib/spec/monkey_patches/alias_should_to_must.rb9
-rwxr-xr-xpuppet/modules/stdlib/spec/monkey_patches/publicize_methods.rb11
-rw-r--r--puppet/modules/stdlib/spec/spec.opts6
-rwxr-xr-xpuppet/modules/stdlib/spec/spec_helper.rb34
-rwxr-xr-xpuppet/modules/stdlib/spec/spec_helper_acceptance.rb50
-rwxr-xr-xpuppet/modules/stdlib/spec/unit/facter/facter_dot_d_spec.rb32
-rwxr-xr-xpuppet/modules/stdlib/spec/unit/facter/pe_version_spec.rb76
-rwxr-xr-xpuppet/modules/stdlib/spec/unit/facter/root_home_spec.rb52
-rwxr-xr-xpuppet/modules/stdlib/spec/unit/facter/util/puppet_settings_spec.rb36
-rw-r--r--puppet/modules/stdlib/spec/unit/puppet/functions/type_of_spec.rb33
-rwxr-xr-xpuppet/modules/stdlib/spec/unit/puppet/parser/functions/basename_spec.rb46
-rwxr-xr-xpuppet/modules/stdlib/spec/unit/puppet/parser/functions/bool2str_spec.rb46
-rwxr-xr-xpuppet/modules/stdlib/spec/unit/puppet/parser/functions/camelcase_spec.rb24
-rw-r--r--puppet/modules/stdlib/spec/unit/puppet/parser/functions/str2saltedsha1_spec.rb45
-rwxr-xr-xpuppet/modules/stdlib/spec/unit/puppet/provider/file_line/ruby_spec.rb225
-rwxr-xr-xpuppet/modules/stdlib/spec/unit/puppet/type/anchor_spec.rb11
-rwxr-xr-xpuppet/modules/stdlib/spec/unit/puppet/type/file_line_spec.rb70
-rw-r--r--puppet/modules/stdlib/tests/file_line.pp9
-rw-r--r--puppet/modules/stdlib/tests/has_interface_with.pp10
-rw-r--r--puppet/modules/stdlib/tests/has_ip_address.pp3
-rw-r--r--puppet/modules/stdlib/tests/has_ip_network.pp4
-rw-r--r--puppet/modules/stdlib/tests/init.pp1
-rw-r--r--puppet/modules/stunnel/LICENSE674
-rw-r--r--puppet/modules/stunnel/README77
-rw-r--r--puppet/modules/stunnel/files/CentOS/stunnel.init143
-rw-r--r--puppet/modules/stunnel/manifests/base.pp13
-rw-r--r--puppet/modules/stunnel/manifests/centos.pp35
-rw-r--r--puppet/modules/stunnel/manifests/debian.pp21
-rw-r--r--puppet/modules/stunnel/manifests/init.pp66
-rw-r--r--puppet/modules/stunnel/manifests/linux.pp6
-rw-r--r--puppet/modules/stunnel/manifests/service.pp79
-rw-r--r--puppet/modules/stunnel/manifests/service/nagios.pp12
-rw-r--r--puppet/modules/stunnel/templates/Debian/default13
-rw-r--r--puppet/modules/stunnel/templates/refresh_stunnel.sh.erb22
-rw-r--r--puppet/modules/stunnel/templates/service.conf.erb47
-rw-r--r--puppet/modules/sysctl/README20
-rw-r--r--puppet/modules/sysctl/manifests/config.pp18
-rw-r--r--puppet/modules/sysctl/manifests/init.pp10
-rw-r--r--puppet/modules/systemd/.gitignore10
-rw-r--r--puppet/modules/systemd/.puppet-lint.rc5
-rw-r--r--puppet/modules/systemd/.sync.yml (renamed from .sync.yml)0
-rw-r--r--puppet/modules/systemd/.travis.yml (renamed from .travis.yml)0
-rw-r--r--puppet/modules/systemd/CHANGELOG.md (renamed from CHANGELOG.md)0
-rw-r--r--puppet/modules/systemd/Gemfile47
-rw-r--r--puppet/modules/systemd/HISTORY.md (renamed from HISTORY.md)0
-rw-r--r--puppet/modules/systemd/README.md38
-rw-r--r--puppet/modules/systemd/Rakefile23
-rw-r--r--puppet/modules/systemd/manifests/init.pp (renamed from manifests/init.pp)0
-rw-r--r--puppet/modules/systemd/metadata.json (renamed from metadata.json)0
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/centos-5-x86_64-docker.yml (renamed from spec/acceptance/nodesets/centos-5-x86_64-docker.yml)0
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/centos-6-x86_64-docker.yml (renamed from spec/acceptance/nodesets/centos-6-x86_64-docker.yml)0
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/centos-6-x86_64-openstack.yml (renamed from spec/acceptance/nodesets/centos-6-x86_64-openstack.yml)0
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/centos-6-x86_64-vagrant.yml (renamed from spec/acceptance/nodesets/centos-6-x86_64-vagrant.yml)0
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/centos-7-x86_64-docker.yml (renamed from spec/acceptance/nodesets/centos-7-x86_64-docker.yml)0
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/centos-7-x86_64-openstack.yml (renamed from spec/acceptance/nodesets/centos-7-x86_64-openstack.yml)0
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/centos-7-x86_64-vagrant.yml (renamed from spec/acceptance/nodesets/centos-7-x86_64-vagrant.yml)0
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/debian-6-x86_64-docker.yml (renamed from spec/acceptance/nodesets/debian-6-x86_64-docker.yml)0
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/debian-6-x86_64-openstack.yml (renamed from spec/acceptance/nodesets/debian-6-x86_64-openstack.yml)0
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/debian-6-x86_64-vagrant.yml (renamed from spec/acceptance/nodesets/debian-6-x86_64-vagrant.yml)0
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/debian-7-x86_64-docker.yml (renamed from spec/acceptance/nodesets/debian-7-x86_64-docker.yml)0
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/debian-7-x86_64-openstack.yml (renamed from spec/acceptance/nodesets/debian-7-x86_64-openstack.yml)0
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/debian-7-x86_64-vagrant.yml (renamed from spec/acceptance/nodesets/debian-7-x86_64-vagrant.yml)0
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/debian-8-x86_64-docker.yml (renamed from spec/acceptance/nodesets/debian-8-x86_64-docker.yml)0
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/debian-8-x86_64-openstack.yml (renamed from spec/acceptance/nodesets/debian-8-x86_64-openstack.yml)0
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/debian-8-x86_64-vagrant.yml (renamed from spec/acceptance/nodesets/debian-8-x86_64-vagrant.yml)0
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-10.04-x86_64-docker.yml (renamed from spec/acceptance/nodesets/ubuntu-10.04-x86_64-docker.yml)0
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-12.04-x86_64-docker.yml (renamed from spec/acceptance/nodesets/ubuntu-12.04-x86_64-docker.yml)0
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-12.04-x86_64-openstack.yml (renamed from spec/acceptance/nodesets/ubuntu-12.04-x86_64-openstack.yml)0
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.04-x86_64-docker.yml (renamed from spec/acceptance/nodesets/ubuntu-14.04-x86_64-docker.yml)0
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.04-x86_64-openstack.yml (renamed from spec/acceptance/nodesets/ubuntu-14.04-x86_64-openstack.yml)0
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.04-x86_64-vagrant.yml (renamed from spec/acceptance/nodesets/ubuntu-14.04-x86_64-vagrant.yml)0
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.10-x86_64-docker.yml (renamed from spec/acceptance/nodesets/ubuntu-14.10-x86_64-docker.yml)0
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.10-x86_64-openstack.yml (renamed from spec/acceptance/nodesets/ubuntu-14.10-x86_64-openstack.yml)0
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-15.04-x86_64-docker.yml (renamed from spec/acceptance/nodesets/ubuntu-15.04-x86_64-docker.yml)0
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-15.04-x86_64-openstack.yml (renamed from spec/acceptance/nodesets/ubuntu-15.04-x86_64-openstack.yml)0
-rw-r--r--puppet/modules/systemd/spec/spec.opts6
-rw-r--r--puppet/modules/systemd/spec/spec_helper.rb (renamed from spec/spec_helper.rb)0
-rw-r--r--puppet/modules/templatewlv/Modulefile11
-rw-r--r--puppet/modules/templatewlv/README.md21
-rw-r--r--puppet/modules/templatewlv/lib/puppet/parser/functions/templatewlv.rb41
-rw-r--r--puppet/modules/templatewlv/lib/puppet/parser/templatewrapperwlv.rb39
-rw-r--r--puppet/modules/tor/.gitignore1
-rw-r--r--puppet/modules/tor/LICENSE661
-rw-r--r--puppet/modules/tor/README214
-rwxr-xr-xpuppet/modules/tor/files/munin/tor_connections162
-rwxr-xr-xpuppet/modules/tor/files/munin/tor_routers151
-rwxr-xr-xpuppet/modules/tor/files/munin/tor_traffic154
-rw-r--r--puppet/modules/tor/files/polipo/polipo.conf164
-rw-r--r--puppet/modules/tor/files/tor-exit-notice.html144
-rw-r--r--puppet/modules/tor/files/tor.html3157
-rw-r--r--puppet/modules/tor/manifests/arm.pp9
-rw-r--r--puppet/modules/tor/manifests/base.pp14
-rw-r--r--puppet/modules/tor/manifests/compact.pp7
-rw-r--r--puppet/modules/tor/manifests/daemon.pp22
-rw-r--r--puppet/modules/tor/manifests/daemon/base.pp77
-rw-r--r--puppet/modules/tor/manifests/daemon/bridge.pp18
-rw-r--r--puppet/modules/tor/manifests/daemon/control.pp27
-rw-r--r--puppet/modules/tor/manifests/daemon/directory.pp27
-rw-r--r--puppet/modules/tor/manifests/daemon/dns.pp17
-rw-r--r--puppet/modules/tor/manifests/daemon/exit_policy.pp18
-rw-r--r--puppet/modules/tor/manifests/daemon/hidden_service.pp17
-rw-r--r--puppet/modules/tor/manifests/daemon/map_address.pp17
-rw-r--r--puppet/modules/tor/manifests/daemon/relay.pp42
-rw-r--r--puppet/modules/tor/manifests/daemon/snippet.pp16
-rw-r--r--puppet/modules/tor/manifests/daemon/socks.pp15
-rw-r--r--puppet/modules/tor/manifests/daemon/transparent.pp17
-rw-r--r--puppet/modules/tor/manifests/init.pp6
-rw-r--r--puppet/modules/tor/manifests/munin.pp21
-rw-r--r--puppet/modules/tor/manifests/polipo.pp9
-rw-r--r--puppet/modules/tor/manifests/polipo/base.pp22
-rw-r--r--puppet/modules/tor/manifests/polipo/debian.pp7
-rw-r--r--puppet/modules/tor/manifests/repo.pp16
-rw-r--r--puppet/modules/tor/manifests/repo/debian.pp9
-rw-r--r--puppet/modules/tor/manifests/torsocks.pp9
-rw-r--r--puppet/modules/tor/templates/torrc.bridge.erb3
-rw-r--r--puppet/modules/tor/templates/torrc.control.erb16
-rw-r--r--puppet/modules/tor/templates/torrc.directory.erb11
-rw-r--r--puppet/modules/tor/templates/torrc.dns.erb5
-rw-r--r--puppet/modules/tor/templates/torrc.exit_policy.erb11
-rw-r--r--puppet/modules/tor/templates/torrc.global.erb24
-rw-r--r--puppet/modules/tor/templates/torrc.header.erb2
-rw-r--r--puppet/modules/tor/templates/torrc.hidden_service.erb6
-rw-r--r--puppet/modules/tor/templates/torrc.map_address.erb3
-rw-r--r--puppet/modules/tor/templates/torrc.relay.erb46
-rw-r--r--puppet/modules/tor/templates/torrc.socks.erb9
-rw-r--r--puppet/modules/tor/templates/torrc.transparent.erb5
-rw-r--r--puppet/modules/try/README.md13
-rw-r--r--puppet/modules/try/manifests/file.pp114
-rw-r--r--puppet/modules/try/manifests/init.pp3
-rw-r--r--puppet/modules/unbound/LICENSE13
-rw-r--r--puppet/modules/unbound/Modulefile10
-rw-r--r--puppet/modules/unbound/README79
-rw-r--r--puppet/modules/unbound/manifests/anchor.pp26
-rw-r--r--puppet/modules/unbound/manifests/forward.pp32
-rw-r--r--puppet/modules/unbound/manifests/init.pp117
-rw-r--r--puppet/modules/unbound/manifests/package.pp15
-rw-r--r--puppet/modules/unbound/manifests/params.pp42
-rw-r--r--puppet/modules/unbound/manifests/root_hints.pp35
-rw-r--r--puppet/modules/unbound/manifests/service.pp22
-rw-r--r--puppet/modules/unbound/manifests/service/openbsd.pp21
-rw-r--r--puppet/modules/unbound/manifests/ssl.pp25
-rw-r--r--puppet/modules/unbound/manifests/stub.pp32
-rw-r--r--puppet/modules/unbound/metadata.json50
-rw-r--r--puppet/modules/unbound/spec/spec_helper.rb17
-rw-r--r--puppet/modules/unbound/templates/unbound.conf.erb8
-rw-r--r--puppet/modules/unbound/tests/anchor.pp2
-rw-r--r--puppet/modules/unbound/tests/forward.pp7
-rw-r--r--puppet/modules/unbound/tests/init.pp26
-rw-r--r--puppet/modules/unbound/tests/package.pp2
-rw-r--r--puppet/modules/unbound/tests/params.pp1
-rw-r--r--puppet/modules/unbound/tests/root_hints.pp2
-rw-r--r--puppet/modules/unbound/tests/service.pp2
-rw-r--r--puppet/modules/unbound/tests/service/openbsd.pp1
-rw-r--r--puppet/modules/unbound/tests/ssl.pp2
-rw-r--r--puppet/modules/unbound/tests/stub.pp7
-rw-r--r--puppet/modules/vcsrepo/.gitattributes5
-rw-r--r--puppet/modules/vcsrepo/.gitignore11
-rw-r--r--puppet/modules/vcsrepo/.rspec2
-rw-r--r--puppet/modules/vcsrepo/.sync.yml3
-rw-r--r--puppet/modules/vcsrepo/.travis.yml20
-rw-r--r--puppet/modules/vcsrepo/CHANGELOG.md150
-rw-r--r--puppet/modules/vcsrepo/CONTRIBUTING.md220
-rw-r--r--puppet/modules/vcsrepo/Gemfile39
-rw-r--r--puppet/modules/vcsrepo/LICENSE339
-rw-r--r--puppet/modules/vcsrepo/NOTICE20
-rw-r--r--puppet/modules/vcsrepo/README.markdown758
-rwxr-xr-xpuppet/modules/vcsrepo/Rakefile42
-rw-r--r--puppet/modules/vcsrepo/examples/bzr/branch.pp6
-rw-r--r--puppet/modules/vcsrepo/examples/bzr/init_repo.pp4
-rw-r--r--puppet/modules/vcsrepo/examples/cvs/local.pp11
-rw-r--r--puppet/modules/vcsrepo/examples/cvs/remote.pp5
-rw-r--r--puppet/modules/vcsrepo/examples/git/bare_init.pp4
-rw-r--r--puppet/modules/vcsrepo/examples/git/clone.pp5
-rw-r--r--puppet/modules/vcsrepo/examples/git/shallow-clone-with-just-one-commit.pp7
-rw-r--r--puppet/modules/vcsrepo/examples/git/working_copy_init.pp4
-rw-r--r--puppet/modules/vcsrepo/examples/hg/clone.pp6
-rw-r--r--puppet/modules/vcsrepo/examples/hg/clone_basic_auth.pp7
-rw-r--r--puppet/modules/vcsrepo/examples/hg/init_repo.pp4
-rw-r--r--puppet/modules/vcsrepo/examples/p4/create_client.pp4
-rw-r--r--puppet/modules/vcsrepo/examples/p4/delete_client.pp4
-rw-r--r--puppet/modules/vcsrepo/examples/p4/latest_client.pp5
-rw-r--r--puppet/modules/vcsrepo/examples/p4/sync_client.pp6
-rw-r--r--puppet/modules/vcsrepo/examples/svn/checkout.pp5
-rw-r--r--puppet/modules/vcsrepo/examples/svn/server.pp4
-rw-r--r--puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo.rb42
-rw-r--r--puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo/bzr.rb93
-rw-r--r--puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo/cvs.rb135
-rw-r--r--puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo/dummy.rb12
-rw-r--r--puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo/git.rb483
-rw-r--r--puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo/hg.rb130
-rw-r--r--puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo/p4.rb278
-rw-r--r--puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo/svn.rb139
-rw-r--r--puppet/modules/vcsrepo/lib/puppet/type/vcsrepo.rb248
-rw-r--r--puppet/modules/vcsrepo/metadata.json81
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/basic_auth/basic_auth_checkout_http.rb69
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/basic_auth/basic_auth_checkout_https.rb77
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/basic_auth/negative/basic_auth_checkout_git.rb53
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/branch_checkout_file.rb48
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/branch_checkout_file_path.rb48
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/branch_checkout_git.rb53
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/branch_checkout_http.rb61
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/branch_checkout_https.rb68
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/branch_checkout_scp.rb59
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/branch_checkout_ssh.rb59
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/negative/branch_checkout_not_exists.rb46
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_file.rb46
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_file_path.rb46
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_git.rb51
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_http.rb59
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_https.rb66
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_over_different_exiting_repo_with_force.rb49
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_repo_with_excludes_in_repo.rb46
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_repo_with_excludes_not_in_repo.rb46
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_scp.rb57
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_ssh.rb57
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/negative/clone_over_different_exiting_repo.rb47
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/negative/clone_repo_with_exec_excludes.rb45
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/compression_0_checkout.rb43
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/compression_1_checkout.rb43
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/compression_2_checkout.rb43
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/compression_3_checkout.rb43
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/compression_4_checkout.rb43
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/compression_5_checkout.rb43
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/compression_6_checkout.rb43
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/negative/compression_7_checkout.rb43
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/negative/compression_alpha_checkout.rb43
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/negative/compression_eval_checkout.rb43
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/negative/compression_exec_checkout.rb43
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/negative/compression_negative_checkout.rb43
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/create/create_bare_repo_that_already_exists.rb40
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/create/create_repo_that_already_exists.rb42
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/create/negative/create_bare_repo_specifying_revision.rb38
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/group_checkout_file.rb53
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/group_checkout_file_path.rb53
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/group_checkout_git.rb58
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/group_checkout_http.rb66
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/group_checkout_https.rb73
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/group_checkout_scp.rb64
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/group_checkout_ssh.rb64
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/negative/group_checkout_file_non_existent_group.rb51
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/negative/revision_checkout_not_exists.rb46
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/revision_checkout_file.rb53
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/revision_checkout_file_path.rb53
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/revision_checkout_git.rb58
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/revision_checkout_http.rb66
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/revision_checkout_https.rb74
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/revision_checkout_scp.rb64
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/revision_checkout_ssh.rb64
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/negative/shallow_clone_exec_depth.rb43
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/negative/shallow_clone_file_path.rb44
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/negative/shallow_clone_http.rb55
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/negative/shallow_clone_negative_depth.rb43
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/negative/shallow_clone_overflow_depth.rb45
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/shallow_clone_file.rb47
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/shallow_clone_git.rb52
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/shallow_clone_https.rb68
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/shallow_clone_scp.rb58
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/shallow_clone_ssh.rb58
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/shallow_clone_zero_depth.rb43
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/negative/tag_checkout_not_exists.rb47
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/tag_checkout_file.rb48
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/tag_checkout_file_path.rb48
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/tag_checkout_git.rb59
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/tag_checkout_http.rb67
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/tag_checkout_https.rb74
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/tag_checkout_scp.rb65
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/tag_checkout_ssh.rb65
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/negative/user_checkout_file_non_existent_user.rb51
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/user_checkout_file.rb53
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/user_checkout_file_path.rb53
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/user_checkout_git.rb58
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/user_checkout_http.rb66
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/user_checkout_https.rb73
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/user_checkout_scp.rb64
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/user_checkout_ssh.rb64
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/beaker_helper.rb51
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/clone_repo_spec.rb534
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/create_repo_spec.rb89
-rwxr-xr-xpuppet/modules/vcsrepo/spec/acceptance/files/create_git_repo.sh39
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/files/server.crt13
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/files/server.key15
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/modules_1596_spec.rb72
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/modules_1800_spec.rb41
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/modules_2326_spec.rb69
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/modules_660_spec.rb89
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/modules_753_spec.rb68
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/nodesets/centos-59-x64.yml10
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/nodesets/centos-64-x64-pe.yml12
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/nodesets/centos-64-x64.yml10
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/nodesets/centos-65-x64.yml10
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/nodesets/debian-607-x64.yml10
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/nodesets/debian-73-x64.yml10
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/nodesets/default.yml10
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/nodesets/ubuntu-server-10044-x64.yml10
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/nodesets/ubuntu-server-12042-x64.yml10
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml11
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/remove_repo_spec.rb30
-rw-r--r--puppet/modules/vcsrepo/spec/acceptance/remove_repo_spec_noop.rb31
-rw-r--r--puppet/modules/vcsrepo/spec/fixtures/bzr_version_info.txt5
-rw-r--r--puppet/modules/vcsrepo/spec/fixtures/git_branch_a.txt14
-rw-r--r--puppet/modules/vcsrepo/spec/fixtures/git_branch_feature_bar.txt14
-rw-r--r--puppet/modules/vcsrepo/spec/fixtures/git_branch_none.txt15
-rw-r--r--puppet/modules/vcsrepo/spec/fixtures/hg_parents.txt6
-rw-r--r--puppet/modules/vcsrepo/spec/fixtures/hg_tags.txt18
-rw-r--r--puppet/modules/vcsrepo/spec/fixtures/svn_info.txt10
-rw-r--r--puppet/modules/vcsrepo/spec/spec.opts6
-rw-r--r--puppet/modules/vcsrepo/spec/spec_helper.rb8
-rw-r--r--puppet/modules/vcsrepo/spec/spec_helper_acceptance.rb46
-rw-r--r--puppet/modules/vcsrepo/spec/spec_helper_local.rb7
-rw-r--r--puppet/modules/vcsrepo/spec/support/filesystem_helpers.rb18
-rw-r--r--puppet/modules/vcsrepo/spec/support/fixture_helpers.rb7
-rw-r--r--puppet/modules/vcsrepo/spec/unit/puppet/provider/vcsrepo/bzr_spec.rb109
-rw-r--r--puppet/modules/vcsrepo/spec/unit/puppet/provider/vcsrepo/cvs_spec.rb124
-rw-r--r--puppet/modules/vcsrepo/spec/unit/puppet/provider/vcsrepo/git_spec.rb401
-rw-r--r--puppet/modules/vcsrepo/spec/unit/puppet/provider/vcsrepo/hg_spec.rb138
-rw-r--r--puppet/modules/vcsrepo/spec/unit/puppet/provider/vcsrepo/p4_spec.rb82
-rw-r--r--puppet/modules/vcsrepo/spec/unit/puppet/provider/vcsrepo/svn_spec.rb160
-rw-r--r--puppet/modules/vcsrepo/spec/unit/puppet/type/README.markdown4
-rw-r--r--puppet/modules/x509/manifests/base.pp45
-rw-r--r--puppet/modules/x509/manifests/ca.pp34
-rw-r--r--puppet/modules/x509/manifests/cert.pp34
-rw-r--r--puppet/modules/x509/manifests/init.pp2
-rw-r--r--puppet/modules/x509/manifests/key.pp37
-rw-r--r--puppet/modules/x509/manifests/variables.pp7
-rw-r--r--tests/README.md25
-rw-r--r--tests/helpers/bonafide_helper.rb235
-rw-r--r--tests/helpers/client_side_db.py167
-rw-r--r--tests/helpers/couchdb_helper.rb142
-rw-r--r--tests/helpers/files_helper.rb54
-rw-r--r--tests/helpers/http_helper.rb157
-rw-r--r--tests/helpers/network_helper.rb79
-rw-r--r--tests/helpers/os_helper.rb41
-rw-r--r--tests/helpers/smtp_helper.rb45
-rwxr-xr-xtests/helpers/soledad_sync.py89
-rw-r--r--tests/helpers/srp_helper.rb171
-rw-r--r--tests/order.rb22
-rw-r--r--tests/white-box/couchdb.rb186
-rw-r--r--tests/white-box/dummy.rb71
-rw-r--r--tests/white-box/mx.rb267
-rw-r--r--tests/white-box/network.rb90
-rw-r--r--tests/white-box/openvpn.rb16
-rw-r--r--tests/white-box/soledad.rb17
-rw-r--r--tests/white-box/webapp.rb134
-rwxr-xr-xvagrant/add-pixelated.sh32
-rwxr-xr-xvagrant/configure-leap.sh92
-rwxr-xr-xvagrant/install-platform.pp15
-rw-r--r--vagrant/vagrant.config22
2142 files changed, 111621 insertions, 107 deletions
diff --git a/.gitignore b/.gitignore
index 65839fa0..146a1006 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,10 +1,3 @@
-pkg/
+/.vagrant
+/puppet/modules/site_custom
Gemfile.lock
-vendor/
-spec/fixtures/
-.vagrant/
-.bundle/
-coverage/
-log/
-.*.swp
-*~
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.gitmodules
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 00000000..aee70b0a
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,8 @@
+Varac <varacanero@zeromail.org>
+Micah Anderson <micah@leap.se> Micah Anderson <micah@riseup.net>
+Micah Anderson <micah@leap.se> micah <micah@leap.se>
+Kwadronaut <kwadronaut@leap.se>
+Elijah <elijah@riseup.net> elijah <elijah@ChrUbuntu.(none)>
+Elijah <elijah@riseup.net> elijah <elijah@riseup.net>
+Leap Admins <admin@leap.se> root <root@localhost>
+
diff --git a/CHANGES.md b/CHANGES.md
new file mode 100644
index 00000000..ad42dd7a
--- /dev/null
+++ b/CHANGES.md
@@ -0,0 +1,64 @@
+Platform 0.8
+--------------------------------------
+
+This release focused on the email service. Debian Jessie is now required,
+which also means that you must migrate all data from BigCouch to CouchDB.
+
+UPGRADING: It is tricky to upgrade the OS and migrate the database. You can
+follow the tutorial here: https://leap.se/en/upgrade-0-8
+
+WARNING: failure to migrate data from BigCouch to CouchDB will cause all user
+accounts to get destroyed.
+
+Other new features:
+
+* It is possible to require invite codes for new users signing up.
+
+* Tapicero has been removed. Now user storage databases are created as needed
+ by soledad, and deleted eventually when no longer needed.
+
+* Admins can now suspect/enable users and block/enable their ability to send
+ and receive email.
+
+* Support for SPF and DKIM.
+
+Compatibility:
+
+* Now, soledad and couchdb must be on the same node.
+* Requires Debian Jessie. Wheezy is no longer supported.
+* Requires CouchDB, BigCouch is no longer supported.
+* Requires leap_cli version 1.8
+* Requires bitmask client version >= 0.9
+* Includes:
+ * leap_mx 0.8
+ * webapp 0.8
+ * soledad 0.8
+
+Commits: https://leap.se/git/leap_platform.git/shortlog/refs/tags/0.8
+Issues fixed: https://leap.se/code/versions/189
+
+
+Platform 0.7.1
+--------------------------------------
+
+Compatibility:
+
+* Requires leap_cli version 1.7.4
+* Requires bitmask client version >= 0.7
+* Previous releases supported cookies when using the provider API. Now, only
+ tokens are supported.
+* Includes:
+ * leap_mx 0.7.0
+ * tapicero 0.7
+ * webapp 0.7
+ * soledad 0.7
+
+Commits: https://leap.se/git/leap_platform.git/shortlog/refs/tags/0.7.1
+Issues fixed: https://leap.se/code/versions/159
+
+Upgrading:
+
+* `gem install leap_cli --version 1.7.4`.
+* `cd leap_platform; git pull; git checkout 0.7.1`.
+* `leap deploy`
+* `leap test` to make sure everything is working
diff --git a/Gemfile b/Gemfile
index 0cb59337..8925a904 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,47 +1,13 @@
-source ENV['GEM_SOURCE'] || "https://rubygems.org"
-
-group :development, :unit_tests do
- gem 'rake', :require => false
- gem 'rspec', '< 3.2', :require => false if RUBY_VERSION =~ /^1.8/
- gem 'rspec-puppet', :require => false
- gem 'puppetlabs_spec_helper', :require => false
- gem 'metadata-json-lint', :require => false
- gem 'puppet-lint', :require => false
- gem 'puppet-lint-unquoted_string-check', :require => false
- gem 'puppet-lint-empty_string-check', :require => false
- gem 'puppet-lint-spaceship_operator_without_tag-check', :require => false
- gem 'puppet-lint-variable_contains_upcase', :require => false
- gem 'puppet-lint-absolute_classname-check', :require => false
- gem 'puppet-lint-undef_in_function-check', :require => false
- gem 'puppet-lint-leading_zero-check', :require => false
- gem 'puppet-lint-trailing_comma-check', :require => false
- gem 'puppet-lint-file_ensure-check', :require => false
- gem 'puppet-lint-version_comparison-check', :require => false
- gem 'puppet-lint-fileserver-check', :require => false
- gem 'puppet-lint-file_source_rights-check', :require => false
- gem 'puppet-lint-alias-check', :require => false
- gem 'rspec-puppet-facts', :require => false
- gem 'github_changelog_generator', :require => false, :git => 'https://github.com/raphink/github-changelog-generator.git', :branch => 'dev/all_patches' if RUBY_VERSION !~ /^1.8/
- gem 'puppet-blacksmith', :require => false if RUBY_VERSION !~ /^1.8/
-end
-
-group :system_tests do
- gem 'beaker', :require => false
- gem 'beaker-rspec', :require => false
- gem 'beaker_spec_helper', :require => false
- gem 'serverspec', :require => false
-end
-
-if facterversion = ENV['FACTER_GEM_VERSION']
- gem 'facter', facterversion, :require => false
-else
- gem 'facter', :require => false
-end
-
-if puppetversion = ENV['PUPPET_GEM_VERSION']
- gem 'puppet', puppetversion, :require => false
-else
- gem 'puppet', :require => false
+source "https://rubygems.org"
+
+group :test do
+ gem "rake"
+ gem "rspec", '< 3.2.0'
+ gem "puppet", ENV['PUPPET_VERSION'] || ENV['GEM_PUPPET_VERSION'] || ENV['PUPPET_GEM_VERSION'] || '~> 3.7.0'
+ gem "facter", ENV['FACTER_VERSION'] || ENV['GEM_FACTER_VERSION'] || ENV['FACTER_GEM_VERSION'] || '~> 2.2.0'
+ gem "rspec-puppet"
+ gem "puppetlabs_spec_helper"
+ gem "metadata-json-lint"
+ gem "rspec-puppet-facts"
+ gem "mocha"
end
-
-# vim:ft=ruby
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..94a9ed02
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/README.md b/README.md
index f70bcb0c..edc272d8 100644
--- a/README.md
+++ b/README.md
@@ -1,38 +1,111 @@
-# Systemd
-
-[![Puppet Forge](http://img.shields.io/puppetforge/v/camptocamp/systemd.svg)](https://forge.puppetlabs.com/camptocamp/systemd)
-[![Build Status](https://travis-ci.org/camptocamp/puppet-systemd.png?branch=master)](https://travis-ci.org/camptocamp/puppet-systemd)
-
-## Overview
-
-This module declares exec resources that you can use when you change systemd units or configuration files.
-
-## Examples
-
-### systemctl --daemon-reload
-
-```puppet
-include ::systemd
-file { '/usr/lib/systemd/system/foo.service':
- ensure => file,
- owner => 'root',
- group => 'root',
- mode => '0644',
- source => "puppet:///modules/${module_name}/foo.service",
-} ~>
-Exec['systemctl-daemon-reload']
-```
-
-### systemd-tmpfiles --create
-
-```puppet
-include ::systemd
-file { '/etc/tmpfiles.d/foo.conf':
- ensure => file,
- owner => 'root',
- group => 'root',
- mode => '0644',
- source => "puppet:///modules/${module_name}/foo.conf",
-} ~>
-Exec['systemd-tmpfiles-create']
-```
+Leap Platform
+=============================
+
+[![Build Status](https://jenkins.leap.se/job/platform_develop/badge/icon)](https://jenkins.leap.se/job/platform_develop/)
+
+The LEAP Platform is set of complementary packages and server recipes to automate the maintenance of LEAP services in a hardened Debian environment. Its goal is to make it as painless as possible for sysadmins to deploy and maintain a service provider's infrastructure for secure communication. These recipes define an abstract service provider. It is a set of Puppet modules designed to work together to provide to sysadmins everything they need to manage a service provider infrastructure that provides secure communication services.
+
+Getting started
+=============================
+
+It is highly recommended that you start by reading the overview of the [LEAP Platform](https://leap.se/docs/platform) and then begin with the [Quick Start tutorial](https://leap.se/en/docs/platform/tutorials/quick-start) to walk through a test environment setup to get familiar with how things work before deploying to live servers.
+
+An offline copy of this documentation is contained in the `doc` subdirectory. For more current updates to the documentation, visit the website.
+
+Requirements
+------------------
+
+For testing a virtual deployment simulated on your computer, you will need a fairly recent computer x86_64 with hardware virtualization features (AMD-V or VT-x) and plenty of RAM. If you follow the "Quick Start" documentation we will walk you through using Vagrant to setup a test deployment.
+
+For a live deployment of the platform, the number of servers that is required depends on your needs and which services you want to deploy. At the moment, the LEAP Platform supports servers with a base Debian Wheezy installation.
+
+Troubleshooting
+=============================
+
+If you have a problem, we are interested in fixing it!
+
+If you have a problem, be sure to have a look at the [Known Issues](https://leap.se/docs/platform/known-issues) to see if your issue is detailed there.
+
+If not, the best way for us to solve your problem is if you provide to us the complete log of what you did, and the output that was produced. Please don't cut out what appears to be useless information and only include the error that you received, instead copy and paste the complete log so that we can better determine the overall situation. If you can run the same command that produced the error with a raised verbosity level (such as -v2), that provides us with more useful debugging information.
+
+To capture the log, you can copy from the console, or run `leap --log FILE` or edit Leapfile to include `@log = '/tmp/leap.log'`.
+
+Visit https://leap.se/en/docs/get-involved/communication for details on how to contact the developers.
+
+Known issues
+============
+
+The following issues are known to exist in 0.5.2 and later:
+
+CouchDB Sync
+------------
+You can't deploy new couchdb nodes after one or more have been deployed. Make *sure* that you configure and deploy all your couchdb nodes when first creating your provider. The problem is that we dont not have a clean way of adding couch nodes after initial creation of the databases, so any nodes added after result in improperly synchronized data. See Bug [#5601](https://leap.se/code/issues/5601) for more information.
+
+User setup and ssh
+------------------
+
+. if you aren't using a single ssh key, but have different ones, you will need to define the following at the top of your ~/.ssh/config:
+ HostName <ip address>
+ IdentityFile <path to identity file>
+
+ (see: https://leap.se/code/issues/2946 and https://leap.se/code/issues/3002)
+
+. If the ssh host key changes, you need to run node init again (see: https://leap.se/en/docs/platform/guide#Working.with.SSH)
+
+. At the moment, only ECDSA ssh host keys are supported. If you get the following error: `= FAILED ssh-keyscan: no hostkey alg (must be missing an ecdsa public host key)` then you should confirm that you have the following line defined in your server's **/etc/ssh/sshd_config**: `HostKey /etc/ssh/ssh_host_ecdsa_key`. If that file doesn't exist, run `ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key -N ""` in order to create it. If you made a change to your sshd_config, then you need to run `/etc/init.d/ssh restart` (see: https://leap.se/code/issues/2373)
+
+. To remove an admin's access to your servers, please remove the directory for that user under the `users/` subdirectory in your provider directory and then remove that user's ssh keys from files/ssh/authorized_keys. When finished you *must* run a `leap deploy` to update that information on the servers.
+
+. At the moment, it is only possible to add an admin who will have access to all LEAP servers (see: https://leap.se/code/issues/2280)
+
+. leap add-user --self allows only one key - if you run that command twice with different keys, you will just replace the key with the second key. To add a second key, add it manually to files/ssh/authorized_keys (see: https://leap.se/code/issues/866)
+
+
+Deploying
+---------
+
+. If you have any errors during a run, please try to deploy again as this often solves non-deterministic issues that were not uncovered in our testing. Please re-deploy with `leap -v2 deploy` to get more verbose logs and capture the complete output to provide to us for debugging.
+
+. If when deploying your debian mirror fails for some reason, network anomoly or the mirror itself is out of date, then platform deployment will not succeed properly. Check the mirror is up and try to deploy again when it is resolved (see: https://leap.se/code/issues/1091)
+
+. Deployment gives 'error: in `%`: too few arguments (ArgumentError)' - this is because you attempted to do a deploy before initializing a node, please initialize the node first and then do a deploy afterwards (see: https://leap.se/code/issues/2550)
+
+. This release has no ability to custom configure apt sources or proxies (see: https://leap.se/code/issues/1971)
+
+. When running a deploy at a verbosity level of 2 and above, you will notice puppet deprecation warnings, these are known and we are working on fixing them
+
+Special Environments
+--------------------
+
+. When deploying to OpenStack release "nova" or newer, you will need to do an initial deploy, then when it has finished run `leap facts update` and then deploy again (see: https://leap.se/code/issues/3020)
+
+leap-mx
+-------
+
+. see https://github.com/leapcode/leap_mx#070 for issues regarding leap_mx
+
+
+Contributing
+============
+
+In order to validate the syntax and style guide compliance
+before you commit, see https://github.com/pixelated-project/puppet-git-hooks#installation
+
+
+Changes
+=========
+
+Read CHANGES.md or run `git log`.
+
+Authors and Credits
+===================
+
+See contributors:
+
+ git shortlog -es --all
+
+
+Copyright/License
+=================
+
+Read LICENSE
diff --git a/Rakefile b/Rakefile
index adcac180..0d1b18ad 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,23 +1,57 @@
require 'puppetlabs_spec_helper/rake_tasks'
require 'puppet-lint/tasks/puppet-lint'
+require 'puppet-syntax/tasks/puppet-syntax'
+# return list of modules, either
+# submodules, custom or all modules
+# so we can check each array seperately
+def modules_pattern (type)
+ submodules = Array.new
+ custom_modules = Array.new
+ all_modules = Array.new
+
+ Dir['puppet/modules/*'].sort.each do |m|
+ system("grep -q #{m} .gitmodules")
+ if $?.exitstatus == 0
+ submodules << m + '/**/*.pp'
+ else
+ custom_modules << m + '/**/*.pp'
+ end
+ all_modules << m + '/**/*.pp'
+ end
+
+ case type
+ when 'submodule'
+ submodules
+ when 'custom'
+ custom_modules
+ when 'all'
+ all_modules
+ end
+end
+
+exclude_paths = ["**/vendor/**/*", "spec/fixtures/**/*", "pkg/**/*" ]
+
+# redefine lint task so we don't lint submoudules for now
Rake::Task[:lint].clear
PuppetLint::RakeTask.new :lint do |config|
- config.ignore_paths = ["spec/**/*.pp", "pkg/**/*.pp", "vendor/**/*.pp"]
- config.disable_checks = ['80chars']
- config.fail_on_warnings = true
+ # only check for custom manifests, not submodules for now
+ config.pattern = modules_pattern('custom')
+ config.ignore_paths = exclude_paths
+ config.disable_checks = ['documentation', '80chars']
+ config.fail_on_warnings = false
end
-PuppetSyntax.exclude_paths = ["spec/fixtures/**/*.pp", "vendor/**/*"]
-
-# Publishing tasks
-unless RUBY_VERSION =~ /^1\.8/
- require 'puppet_blacksmith'
- require 'puppet_blacksmith/rake_tasks'
- require 'github_changelog_generator/task'
- GitHubChangelogGenerator::RakeTask.new :changelog do |config|
- m = Blacksmith::Modulefile.new
- config.future_release = m.version
- config.release_url = "https://forge.puppetlabs.com/#{m.author}/#{m.name}/%s"
+# rake syntax::* tasks
+PuppetSyntax.exclude_paths = exclude_paths
+PuppetSyntax.future_parser = true
+
+desc "Validate erb templates"
+task :templates do
+ Dir['**/templates/**/*.erb'].each do |template|
+ sh "erb -P -x -T '-' #{template} | ruby -c" unless template =~ /.*vendor.*/
end
end
+
+desc "Run all puppet checks required for CI (syntax , validate, spec, lint)"
+task :test => [:syntax , :validate, :templates, :spec, :lint]
diff --git a/Vagrantfile b/Vagrantfile
new file mode 100644
index 00000000..25f26b3b
--- /dev/null
+++ b/Vagrantfile
@@ -0,0 +1,53 @@
+# -*- mode: ruby -*-
+# vi: set ft=ruby :
+
+Vagrant.configure("2") do |config|
+
+ # shared config for all boxes
+
+ # Please verify the sha512 sum of the downloaded box before importing it into vagrant !
+ # see https://leap.se/en/docs/platform/details/development#Verify.vagrantbox.download
+ # for details
+ config.vm.box = "LEAP/jessie"
+
+ config.vm.provider "virtualbox" do |v|
+ v.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
+ v.name = "jessie"
+ v.memory = 1536
+ end
+
+ config.vm.provider "libvirt" do |v|
+ v.memory = 1536
+ end
+
+ # Fix annoying 'stdin: is not a tty' warning
+ # see http://foo-o-rama.com/vagrant--stdin-is-not-a-tty--fix.html
+ config.vm.provision "shell" do |s|
+ s.privileged = false
+ s.inline = "sudo sed -i '/tty/!s/mesg n/tty -s \\&\\& mesg n/' /root/.profile"
+ end
+
+ config.vm.provision "puppet" do |puppet|
+ puppet.manifests_path = "./vagrant"
+ puppet.module_path = "./puppet/modules"
+ puppet.manifest_file = "install-platform.pp"
+ puppet.options = "--verbose"
+ puppet.hiera_config_path = "hiera.yaml"
+ end
+ config.vm.provision "shell", path: "vagrant/configure-leap.sh"
+
+ config.ssh.username = "vagrant"
+
+ # forward leap_web ports
+ config.vm.network "forwarded_port", guest: 443, host:4443
+ # forward pixelated ports
+ config.vm.network "forwarded_port", guest: 8080, host:8080
+
+ config.vm.define :"leap_platform", primary: true do |leap_vagrant|
+ end
+
+ config.vm.define :"pixelated", autostart: false do |pixelated_vagrant|
+ pixelated_vagrant.vm.provision "shell", path: "vagrant/add-pixelated.sh"
+ end
+
+end
diff --git a/bin/debug.sh b/bin/debug.sh
new file mode 100755
index 00000000..d6f37542
--- /dev/null
+++ b/bin/debug.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+# debug script to be run on remote servers
+# called from leap_cli with the 'leap debug' cmd
+
+apps='(leap|pixelated|stunnel|couch|soledad|haproxy)'
+
+facts='(apt_running |^architecture |^augeasversion |^couchdb_.* |^debian_.* |^dhcp_enabled |^domain |^facterversion |^filesystems |^fqdn |^hardwaremodel |^hostname |^interface.* |^ipaddress.* |^is_pe |^is_virtual |^kernel.* |^lib |^lsb.* |^memory.* |^mtu_.* |^netmask.* |^network_.* |^operatingsystem |^os.* |^path |^physicalprocessorcount |^processor.* |^ps |^puppetversion |^root_home |^rsyslog_version |^rubysitedir |^rubyversion |^selinux |^ssh_version |^swapfree.* |^swapsize.* |^type |^virtual)'
+
+
+# query facts and filter out private stuff
+export FACTERLIB="/srv/leap/puppet/modules/apache/lib/facter:/srv/leap/puppet/modules/apt/lib/facter:/srv/leap/puppet/modules/concat/lib/facter:/srv/leap/puppet/modules/couchdb/lib/facter:/srv/leap/puppet/modules/rsyslog/lib/facter:/srv/leap/puppet/modules/site_config/lib/facter:/srv/leap/puppet/modules/sshd/lib/facter:/srv/leap/puppet/modules/stdlib/lib/facter"
+
+facter 2>/dev/null | egrep -i "$facts"
+
+# query installed versions
+echo -e '\n\n'
+dpkg -l | egrep "$apps"
+
+
+# query running procs
+echo -e '\n\n'
+ps aux|egrep "$apps"
+
+echo -e '\n\n'
+echo -e "Last deploy:\n"
+tail -2 /var/log/leap/deploy-summary.log
+
+
+
diff --git a/bin/node_init b/bin/node_init
new file mode 100644
index 00000000..da250012
--- /dev/null
+++ b/bin/node_init
@@ -0,0 +1,86 @@
+#!/bin/bash
+#
+# LEAP Platform node initialization.
+# This script is run on the target server when `leap node init` is run.
+#
+
+DEBIAN_VERSION="^(jessie|8\.)"
+LEAP_DIR="/srv/leap"
+HIERA_DIR="/etc/leap"
+INIT_FILE="/srv/leap/initialized"
+REQUIRED_PACKAGES="puppet rsync lsb-release locales"
+
+PATH="/bin:/sbin:/usr/sbin:/usr/bin"
+APT_GET="apt-get -q -y -o DPkg::Options::=--force-confold"
+APT_GET_UPDATE="apt-get update -o Acquire::Languages=none"
+BAD_APT_RESPONSE="(BADSIG|NO_PUBKEY|KEYEXPIRED|REVKEYSIG|NODATA|Could not resolve|failed to fetch)"
+export DEBIAN_FRONTEND=noninteractive
+
+test -f $INIT_FILE && rm $INIT_FILE
+if ! egrep -q "$DEBIAN_VERSION" /etc/debian_version; then
+ echo "ERROR: This operating system is not supported. The file /etc/debian_version must match /$DEBIAN_VERSION/ but is: `cat /etc/debian_version`"
+ exit 1
+fi
+mkdir -p $LEAP_DIR
+echo "en_US.UTF-8 UTF-8" > /etc/locale.gen
+
+#
+# UPDATE PACKAGES
+# (exit code is not reliable, sadly)
+#
+echo "updating package list"
+
+error_count=0
+while read line; do
+ error=$(echo $line | egrep "$BAD_APT_RESPONSE")
+ if [[ $error ]]; then
+ errors[error_count]=$error
+ ((error_count++))
+ break # should we halt on first error?
+ fi
+ echo $line
+done < <($APT_GET_UPDATE 2>&1)
+
+if [[ $error_count > 0 ]]; then
+ echo "ERROR: fatal error in 'apt-get update', bailing out."
+ for e in "${errors[@]}"; do
+ echo " $e"
+ done
+ exit 1
+fi
+
+#
+# UPDATE TIME
+#
+if [[ ! $(which ntpd) ]]; then
+ echo "installing ntpd"
+ $APT_GET install ntp
+ exit_code=$?
+ if [[ $exit_code -ne 0 ]]; then
+ echo "ERROR: bailing out."
+ exit $exit_code
+ fi
+fi
+
+echo "updating server time"
+systemctl -q is-active ntp.service && systemctl stop ntp.service
+ntpd -gxq
+systemctl -q is-active ntp.service || systemctl start ntp.service
+
+#
+# INSTALL PACKAGES
+#
+echo "installing required packages"
+$APT_GET install $REQUIRED_PACKAGES
+exit_code=$?
+if [[ $exit_code -ne 0 ]]; then
+ echo "ERROR: bailing out."
+ exit $exit_code
+fi
+
+#
+# FINALIZE
+#
+mkdir -p $HIERA_DIR
+chmod 0755 $HIERA_DIR
+touch $INIT_FILE
diff --git a/bin/puppet_command b/bin/puppet_command
new file mode 100755
index 00000000..eb3cd0b9
--- /dev/null
+++ b/bin/puppet_command
@@ -0,0 +1,313 @@
+#!/usr/bin/ruby
+
+#
+# This is a wrapper script around the puppet command used by the LEAP platform.
+#
+# We do this in order to make it faster and easier to control puppet remotely
+# (exit codes, logging, lockfile, version check, etc)
+#
+
+require 'pty'
+require 'yaml'
+require 'logger'
+require 'socket'
+require 'fileutils'
+
+DEBIAN_VERSION = /^(jessie|8\.)/
+PUPPET_BIN = '/usr/bin/puppet'
+PUPPET_DIRECTORY = '/srv/leap'
+PUPPET_PARAMETERS = '--color=false --detailed-exitcodes --libdir=puppet/lib --confdir=puppet'
+SITE_MANIFEST = 'puppet/manifests/site.pp'
+SITE_MODULES = 'puppet/modules'
+CUSTOM_MODULES = ':files/puppet/modules'
+DEFAULT_TAGS = 'leap_base,leap_service'
+HIERA_FILE = '/etc/leap/hiera.yaml'
+LOG_DIR = '/var/log/leap'
+DEPLOY_LOG = '/var/log/leap/deploy.log'
+SUMMARY_LOG = '/var/log/leap/deploy-summary.log'
+SUMMARY_LOG_1 = '/var/log/leap/deploy-summary.log.1'
+APPLY_START_STR = "STARTING APPLY"
+APPLY_FINISH_STR = "APPLY COMPLETE"
+
+
+def main
+ if File.read('/etc/debian_version') !~ DEBIAN_VERSION
+ log "ERROR: This operating system is not supported. The file /etc/debian_version must match #{DEBIAN_VERSION}."
+ exit 1
+ end
+ process_command_line_arguments
+ with_lockfile do
+ @commands.each do |command|
+ self.send(command)
+ end
+ end
+end
+
+def open_log_files
+ FileUtils.mkdir_p(LOG_DIR)
+ $logger = Logger.new(DEPLOY_LOG)
+ $summary_logger = Logger.new(SUMMARY_LOG)
+ [$logger, $summary_logger].each do |logger|
+ logger.level = Logger::INFO
+ logger.formatter = proc do |severity, datetime, progname, msg|
+ "%s %s: %s\n" % [datetime.strftime("%b %d %H:%M:%S"), Socket.gethostname, msg]
+ end
+ end
+end
+
+def close_log_files
+ $logger.close
+ $summary_logger.close
+end
+
+def log(str, *args)
+ str = str.strip
+ $stdout.puts str
+ $stdout.flush
+ if $logger
+ $logger.info(str)
+ if args.include? :summary
+ $summary_logger.info(str)
+ end
+ end
+end
+
+def process_command_line_arguments
+ @commands = []
+ @verbosity = 1
+ @tags = DEFAULT_TAGS
+ @info = {}
+ @downgrade = false
+ loop do
+ case ARGV[0]
+ when 'apply' then ARGV.shift; @commands << 'apply'
+ when 'set_hostname' then ARGV.shift; @commands << 'set_hostname'
+ when '--verbosity' then ARGV.shift; @verbosity = ARGV.shift.to_i
+ when '--force' then ARGV.shift; remove_lockfile
+ when '--tags' then ARGV.shift; @tags = ARGV.shift
+ when '--info' then ARGV.shift; @info = parse_info(ARGV.shift)
+ when '--downgrade' then ARGV.shift; @downgrade = true
+ when /^-/ then usage("Unknown option: #{ARGV[0].inspect}")
+ else break
+ end
+ end
+ usage("No command given") unless @commands.any?
+end
+
+def apply
+ platform_version_check! unless @downgrade
+ log "#{APPLY_START_STR} {#{format_info(@info)}}", :summary
+ exit_code = puppet_apply do |line|
+ log line
+ end
+ log "#{APPLY_FINISH_STR} (#{exitcode_description(exit_code)}) {#{format_info(@info)}}", :summary
+end
+
+def set_hostname
+ hostname = hiera_file['name']
+ if hostname.nil? || hostname.empty?
+ log('ERROR: "name" missing from hiera file')
+ exit(1)
+ end
+ current_hostname_file = File.read('/etc/hostname') rescue nil
+ current_hostname = `/bin/hostname`.strip
+
+ # set /etc/hostname
+ if current_hostname_file != hostname
+ File.open('/etc/hostname', 'w', 0611, :encoding => 'ascii') do |f|
+ f.write hostname
+ end
+ if File.read('/etc/hostname') == hostname
+ log "Changed /etc/hostname to #{hostname}"
+ else
+ log "ERROR: failed to update /etc/hostname"
+ end
+ end
+
+ # call /bin/hostname
+ if current_hostname != hostname
+ if run("/bin/hostname #{hostname}") == 0
+ log "Changed hostname to #{hostname}"
+ else
+ log "ERROR: call to `/bin/hostname #{hostname}` returned an error."
+ end
+ end
+end
+
+#
+# each line of output is yielded. the exit code is returned.
+#
+def puppet_apply(options={}, &block)
+ options = {:verbosity => @verbosity, :tags => @tags}.merge(options)
+ manifest = options[:manifest] || SITE_MANIFEST
+ modulepath = options[:module_path] || SITE_MODULES + CUSTOM_MODULES
+ fqdn = hiera_file['domain']['full']
+ domain = hiera_file['domain']['full_suffix']
+ Dir.chdir(PUPPET_DIRECTORY) do
+ return run("FACTER_fqdn='#{fqdn}' FACTER_domain='#{domain}' #{PUPPET_BIN} apply #{custom_parameters(options)} --modulepath='#{modulepath}' #{PUPPET_PARAMETERS} #{manifest}", &block)
+ end
+end
+
+#
+# parse the --info flag. example str: "key1: value1, key2: value2, ..."
+#
+def parse_info(str)
+ str.split(', ').
+ map {|i| i.split(': ')}.
+ inject({}) {|h,i| h[i[0]] = i[1]; h}
+rescue Exception => exc
+ {"platform" => "INVALID_FORMAT"}
+end
+
+def format_info(info)
+ info.to_a.map{|i|i.join(': ')}.join(', ')
+end
+
+#
+# exits with a warning message if the last successful deployed
+# platform was newer than the one we are currently attempting to
+# deploy.
+#
+PLATFORM_RE = /\{.*platform: ([0-9\.]+)[ ,\}].*[\}$]/
+def platform_version_check!
+ return unless @info["platform"]
+ new_version = @info["platform"].split(' ').first
+ return unless new_version
+ if File.exists?(SUMMARY_LOG) && File.size(SUMMARY_LOG) != 0
+ file = SUMMARY_LOG
+ elsif File.exists?(SUMMARY_LOG_1) && File.size(SUMMARY_LOG_1) != 0
+ file = SUMMARY_LOG_1
+ else
+ return
+ end
+ most_recent_line = `tail '#{file}'`.split("\n").grep(PLATFORM_RE).last
+ if most_recent_line
+ prior_version = most_recent_line.match(PLATFORM_RE)[1]
+ if Gem::Version.new(prior_version) > Gem::Version.new(new_version)
+ log("ERROR: You are attempting to deploy platform v#{new_version} but this node uses v#{prior_version}.")
+ log(" Run with --downgrade if you really want to deploy an older platform version.")
+ exit(0)
+ end
+ end
+end
+
+#
+# Return a ruby object representing the contents of the hiera yaml file.
+#
+def hiera_file
+ unless File.exists?(HIERA_FILE)
+ log("ERROR: hiera file '#{HIERA_FILE}' does not exist.")
+ exit(1)
+ end
+ $hiera_contents ||= YAML.load_file(HIERA_FILE)
+ return $hiera_contents
+rescue Exception => exc
+ log("ERROR: problem reading hiera file '#{HIERA_FILE}' (#{exc})")
+ exit(1)
+end
+
+def custom_parameters(options)
+ params = []
+ if options[:tags] && options[:tags].chars.any?
+ params << "--tags #{options[:tags]}"
+ end
+ if options[:verbosity]
+ case options[:verbosity]
+ when 3 then params << '--verbose'
+ when 4 then params << '--verbose --debug'
+ when 5 then params << '--verbose --debug --trace'
+ end
+ end
+ params.join(' ')
+end
+
+def exitcode_description(code)
+ case code
+ when 0 then "no changes"
+ when 1 then "failed"
+ when 2 then "changes made"
+ when 4 then "failed"
+ when 6 then "changes and failures"
+ else code
+ end
+end
+
+def usage(s)
+ $stderr.puts(s)
+ $stderr.puts
+ $stderr.puts("Usage: #{File.basename($0)} COMMAND [OPTIONS]")
+ $stderr.puts
+ $stderr.puts("COMMAND may be one or more of:
+ set_hostname -- set the hostname of this server.
+ apply -- apply puppet manifests.")
+ $stderr.puts
+ $stderr.puts("OPTIONS may be one or more of:
+ --verbosity VERB -- set the verbosity level 0..5.
+ --tags TAGS -- set the tags to pass through to puppet.
+ --force -- run even when lockfile is present.
+ --info -- additional info to include in logs (e.g. 'user: alice, platform: 0.6.1')
+ --downgrade -- allow a deploy even if the platform version is older than previous deploy.
+ ")
+ exit(2)
+end
+
+##
+## Simple lock file
+##
+
+require 'fileutils'
+DEFAULT_LOCKFILE = '/tmp/puppet.lock'
+
+def remove_lockfile(lock_file_path=DEFAULT_LOCKFILE)
+ FileUtils.remove_file(lock_file_path, true)
+end
+
+def with_lockfile(lock_file_path=DEFAULT_LOCKFILE)
+ begin
+ File.open(lock_file_path, File::CREAT | File::EXCL | File::WRONLY) do |o|
+ o.write(Process.pid)
+ end
+ open_log_files
+ yield
+ remove_lockfile
+ close_log_files
+ rescue Errno::EEXIST
+ log("ERROR: the lock file '#{lock_file_path}' already exists. Wait a minute for the process to die, or run with --force to ignore. Bailing out.")
+ exit(1)
+ rescue IOError => exc
+ log("ERROR: problem with lock file '#{lock_file_path}' (#{exc}). Bailing out.")
+ exit(1)
+ end
+end
+
+##
+## simple pass through process runner (to ensure output is not buffered and return exit code)
+## this only works under ruby 1.9
+##
+
+def run(cmd)
+ log(cmd) if @verbosity >= 3
+ PTY.spawn("#{cmd}") do |output, input, pid|
+ begin
+ while line = output.gets do
+ yield line
+ end
+ rescue Errno::EIO
+ end
+ Process.wait(pid) # only works in ruby 1.9, required to capture the exit status.
+ end
+ return $?.exitstatus
+rescue PTY::ChildExited
+end
+
+##
+## RUN MAIN
+##
+
+Signal.trap("EXIT") do
+ remove_lockfile # clean up the lockfile when process is terminated.
+ # this will remove the lockfile if ^C killed the process
+ # but only after the child puppet process is also dead (I think).
+end
+
+main()
diff --git a/bin/run_tests b/bin/run_tests
new file mode 100755
index 00000000..b6784ed5
--- /dev/null
+++ b/bin/run_tests
@@ -0,0 +1,515 @@
+#!/usr/bin/ruby
+
+#
+# this script will run the unit tests in ../tests/*.rb.
+#
+# Tests for the platform differ from traditional ruby unit tests in a few ways:
+#
+# (1) at the end of every test function, you should call 'pass()'
+# (2) you can specify test dependencies by calling depends_on("TestFirst") in the test class definition.
+# (3) test functions are always run in alphabetical order.
+# (4) any halt or error will stop the testing unless --continue is specified.
+#
+
+require 'minitest/unit'
+require 'yaml'
+require 'tsort'
+require 'timeout'
+
+##
+## CONSTANTS
+##
+
+EXIT_CODES = {
+ :success => 0,
+ :warning => 1,
+ :failure => 2,
+ :error => 3
+}
+
+HIERA_FILE = '/etc/leap/hiera.yaml'
+HELPER_PATHS = [
+ '../../tests/helpers/*.rb',
+ '/srv/leap/files/tests/helpers/*.rb'
+]
+TEST_PATHS = [
+ '../../tests/white-box/*.rb',
+ '/srv/leap/files/tests/white-box/*.rb',
+ '/srv/leap/tests_custom/*.rb'
+]
+
+##
+## UTILITY
+##
+
+def bail(code, msg=nil)
+ puts msg if msg
+ if code.is_a? Symbol
+ exit(EXIT_CODES[code])
+ else
+ exit(code)
+ end
+end
+
+def service?(service)
+ $node["services"].include?(service.to_s)
+end
+
+##
+## EXCEPTIONS
+##
+
+# this class is raised if a test file wants to be skipped entirely.
+# (to skip an individual test, MiniTest::Skip is used instead)
+class SkipTest < StandardError
+end
+
+# raised if --no-continue and there is an error
+class TestError < StandardError
+end
+
+# raised if --no-continue and there is a failure
+class TestFailure < StandardError
+end
+
+##
+## CUSTOM UNIT TEST CLASS
+##
+
+#
+# Our custom unit test class. All tests should be subclasses of this.
+#
+class LeapTest < MiniTest::Unit::TestCase
+ class Pass < MiniTest::Assertion
+ end
+ class SilentPass < Pass
+ end
+ class Ignore < MiniTest::Assertion
+ end
+
+ def initialize(name)
+ super(name)
+ io # << calling this will suppress the marching ants
+ end
+
+ #
+ # Test class dependencies
+ #
+ def self.depends_on(*class_names)
+ @dependencies ||= []
+ @dependencies += class_names
+ end
+ def self.dependencies
+ @dependencies || []
+ end
+
+ #
+ # returns all the test classes, sorted in dependency order.
+ #
+ def self.test_classes
+ classes = ObjectSpace.each_object(Class).select {|test_class|
+ test_class.ancestors.include?(self)
+ }
+ return TestDependencyGraph.new(classes).sorted
+ end
+
+ def self.tests
+ self.instance_methods.grep(/^test_/).sort
+ end
+
+ #
+ # thrown Timeout::Error if test run
+ # takes longer than $timeout
+ #
+ def run(*args)
+ Timeout::timeout($timeout, Timeout::Error) do
+ super(*args)
+ end
+ end
+
+ #
+ # The default pass just does an `assert true`. In our case, we want to make the passes more explicit.
+ #
+ def pass
+ raise LeapTest::Pass
+ end
+
+ #
+ # This is just like pass(), but the result is normally silent, unless `run_tests --test TEST`
+ def silent_pass
+ raise LeapTest::SilentPass
+ end
+
+ #
+ # Called when the test should be silently ignored.
+ #
+ def ignore
+ raise LeapTest::Ignore
+ end
+
+ #
+ # the default fail() is part of the kernel and it just throws a runtime exception. for tests,
+ # we want the same behavior as assert(false)
+ #
+ def fail(msg=nil, exception=nil)
+ if DEBUG && exception && exception.respond_to?(:backtrace)
+ msg += MiniTest::filter_backtrace(exception.backtrace).join "\n"
+ end
+ assert(false, msg)
+ end
+
+ def warn(*msg)
+ method_name = caller.first.split('`').last.gsub(/(block in |')/,'')
+ MiniTest::Unit.runner.warn(self.class, method_name, msg.join("\n"))
+ end
+
+ #
+ # Always runs test methods within a test class in alphanumeric order
+ #
+ def self.test_order
+ :alpha
+ end
+
+end
+
+#
+# Custom test runner in order to modify the output.
+#
+class LeapRunner < MiniTest::Unit
+
+ attr_accessor :passes, :warnings
+
+ def initialize
+ @passes = 0
+ @warnings = 0
+ @ignores = 0
+ super
+ end
+
+ #
+ # call stack:
+ # MiniTest::Unit.new.run
+ # MiniTest::Unit.runner
+ # LeapTest._run
+ #
+ def _run args = []
+ if $pinned_test_class
+ suites = [$pinned_test_class]
+ if $pinned_test_method
+ options.merge!(:filter => $pinned_test_method.to_s)
+ end
+ else
+ suites = LeapTest.send "test_suites"
+ suites = TestDependencyGraph.new(suites).sorted
+ end
+ output.sync = true
+ results = _run_suites(suites, :test)
+ @test_count = results.inject(0) { |sum, (tc, _)| sum + tc }
+ @assertion_count = results.inject(0) { |sum, (_, ac)| sum + ac }
+ status
+ return exit_code()
+ rescue Interrupt
+ bail :error, 'Tests halted on interrupt.'
+ rescue TestFailure
+ bail :failure, 'Tests halted on failure (because of --no-continue).'
+ rescue TestError
+ bail :error, 'Tests halted on error (because of --no-continue).'
+ end
+
+ #
+ # override puke to change what prints out.
+ #
+ def puke(klass, meth, e)
+ case e
+ when MiniTest::Skip then
+ @skips += 1
+ report_line("SKIP", klass, meth, e, e.message)
+ when LeapTest::Ignore then
+ @ignores += 1
+ if @verbose
+ report_line("IGNORE", klass, meth, e, e.message)
+ end
+ when LeapTest::SilentPass then
+ if $pinned_test_method || $output_format == :checkmk
+ report_line("PASS", klass, meth)
+ end
+ when LeapTest::Pass then
+ @passes += 1
+ report_line("PASS", klass, meth)
+ when MiniTest::Assertion then
+ @failures += 1
+ report_line("FAIL", klass, meth, e, e.message)
+ if $halt_on_failure
+ raise TestFailure.new
+ end
+ when Timeout::Error then
+ @failures += 1
+ report_line("TIMEOUT", klass, meth, nil, "Test stopped because timeout exceeded (#{$timeout} seconds).")
+ if $halt_on_failure
+ raise TestFailure.new
+ end
+ else
+ @errors += 1
+ bt = MiniTest::filter_backtrace(e.backtrace).join "\n"
+ report_line("ERROR", klass, meth, e, "#{e.class}: #{e.message}\n#{bt}")
+ if $halt_on_failure
+ raise TestError.new
+ end
+ end
+ return "" # disable the marching ants
+ end
+
+ #
+ # override default status summary
+ #
+ def status(io = self.output)
+ if $output_format == :human
+ format = "%d tests: %d passes, %d skips, %d warnings, %d failures, %d errors"
+ output.puts format % [test_count, passes, skips, warnings, failures, errors]
+ end
+ end
+
+ #
+ # return an appropriate exit_code symbol
+ #
+ def exit_code
+ if @errors > 0
+ :error
+ elsif @failures > 0
+ :failure
+ elsif @warnings > 0
+ # :warning << warnings don't warrant a non-zero exit code.
+ :success
+ else
+ :success
+ end
+ end
+
+ #
+ # returns a string for a PASS, SKIP, or FAIL error
+ #
+ def report_line(prefix, klass, meth, e=nil, message=nil)
+ msg_txt = nil
+ if message
+ message = message.gsub(/http:\/\/([a-z_]+):([a-zA-Z0-9_]+)@/, "http://\\1:REDACTED@")
+ if $output_format == :human
+ indent = "\n "
+ msg_txt = indent + message.split("\n").join(indent)
+ else
+ msg_txt = message.gsub("\n", ' ')
+ end
+ end
+
+ if $output_format == :human
+ if e && msg_txt
+ output.puts "#{prefix}: #{readable(klass.name)} > #{readable(meth)} [#{File.basename(location(e))}]:#{msg_txt}"
+ elsif msg_txt
+ output.puts "#{prefix}: #{readable(klass.name)} > #{readable(meth)}:#{msg_txt}"
+ else
+ output.puts "#{prefix}: #{readable(klass.name)} > #{readable(meth)}"
+ end
+ # I don't understand at all why, but adding a very tiny sleep here will
+ sleep(0.0001) # keep lines from being joined together by the logger. output.flush doesn't.
+ elsif $output_format == :checkmk
+ code = CHECKMK_CODES[prefix]
+ msg_txt ||= "Success" if prefix == "PASS"
+ if e && msg_txt
+ output.puts "#{code} #{klass.name}/#{machine_readable(meth)} - [#{File.basename(location(e))}]:#{msg_txt}"
+ elsif msg_txt
+ output.puts "#{code} #{klass.name}/#{machine_readable(meth)} - #{msg_txt}"
+ else
+ output.puts "#{code} #{klass.name}/#{machine_readable(meth)} - no message"
+ end
+ end
+ end
+
+ #
+ # a new function used by TestCase to report warnings.
+ #
+ def warn(klass, method_name, msg)
+ @warnings += 1
+ report_line("WARN", klass, method_name, nil, msg)
+ end
+
+ private
+
+ CHECKMK_CODES = {"PASS" => 0, "SKIP" => 1, "FAIL" => 2, "ERROR" => 3}
+
+ #
+ # Converts snake_case and CamelCase to something more pleasant for humans to read.
+ #
+ def readable(str)
+ str.
+ gsub(/_/, ' ').
+ sub(/^test (\d* )?/i, '')
+ end
+
+ def machine_readable(str)
+ str.sub(/^test_(\d+_)?/i, '')
+ end
+
+end
+
+##
+## Dependency resolution
+## Use a topographical sort to manage test dependencies
+##
+
+class TestDependencyGraph
+ include TSort
+
+ def initialize(test_classes)
+ @dependencies = {} # each key is a test class name, and the values
+ # are arrays of test class names that the key depends on.
+ test_classes.each do |test_class|
+ @dependencies[test_class.name] = test_class.dependencies
+ end
+ end
+
+ def tsort_each_node(&block)
+ @dependencies.each_key(&block)
+ end
+
+ def tsort_each_child(test_class_name, &block)
+ if @dependencies[test_class_name]
+ @dependencies[test_class_name].each(&block)
+ else
+ puts "ERROR: bad dependency, no such class `#{test_class_name}`"
+ bail :error
+ end
+ end
+
+ def sorted
+ self.tsort.collect {|class_name|
+ Kernel.const_get(class_name)
+ }
+ end
+end
+
+##
+## COMMAND LINE ACTIONS
+##
+
+def die(test, msg)
+ if $output_format == :human
+ puts "ERROR in test `#{test}`: #{msg}"
+ elsif $output_format == :checkmk
+ puts "3 #{test} - #{msg}"
+ end
+ bail :error
+end
+
+def print_help
+ puts ["USAGE: run_tests [OPTIONS]",
+ " --continue Don't halt on an error, but continue to the next test.",
+ " --checkmk Print test results in checkmk format (must come before --test).",
+ " --test TEST Run only the test with name TEST.",
+ " --list-tests Prints the names of all available tests and exit.",
+ " --retry COUNT If the tests don't pass, retry COUNT additional times (default is zero).",
+ " --timeout SECONDS Halt a test if it exceed SECONDS (default is 30).",
+ " --wait SECONDS Wait for SECONDS between retries (default is 5).",
+ " --debug Print out full stack trace on errors."].join("\n")
+ exit(0)
+end
+
+def list_tests
+ LeapTest.test_classes.each do |test_class|
+ test_class.tests.each do |test|
+ puts test_class.name + "/" + test.to_s.sub(/^test_(\d+_)?/, '')
+ end
+ end
+ exit(0)
+end
+
+def pin_test_name(name)
+ test_class, test_name = name.split('/')
+ $pinned_test_class = LeapTest.test_classes.detect{|c| c.name == test_class}
+ unless $pinned_test_class
+ die name, "there is no test class `#{test_class}`"
+ end
+ if test_name
+ $pinned_test_method = $pinned_test_class.tests.detect{|m| m.to_s =~ /^test_(\d+_)?#{Regexp.escape(test_name)}$/}
+ unless $pinned_test_method
+ die name, "there is no test `#{test_name}` in class `#{test_class}`"
+ end
+ end
+end
+
+#
+# run the tests, multiple times if `--retry` and not all tests were successful.
+#
+def run_tests
+ exit_code = nil
+ run_count = $retry ? $retry + 1 : 1
+ run_count.times do |i|
+ MiniTest::Unit.runner = LeapRunner.new
+ exit_code = MiniTest::Unit.new.run
+ if !$retry || exit_code == :success
+ break
+ elsif i != run_count-1
+ sleep $wait
+ end
+ end
+ bail exit_code
+end
+
+##
+## MAIN
+##
+
+def main
+ # load node data from hiera file
+ if File.exists?(HIERA_FILE)
+ $node = YAML.load_file(HIERA_FILE)
+ else
+ $node = {"services" => [], "dummy" => true}
+ end
+
+ # load all test classes
+ this_file = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
+ HELPER_PATHS.each do |path|
+ Dir[File.expand_path(path, this_file)].each do |helper|
+ require helper
+ end
+ end
+ TEST_PATHS.each do |path|
+ Dir[File.expand_path(path, this_file)].each do |test_file|
+ begin
+ require test_file
+ rescue SkipTest
+ end
+ end
+ end
+
+ # parse command line options
+ $halt_on_failure = true
+ $output_format = :human
+ $retry = false
+ $wait = 5
+ $timeout = 30
+ loop do
+ case ARGV[0]
+ when '--continue' then ARGV.shift; $halt_on_failure = false;
+ when '--checkmk' then ARGV.shift; $output_format = :checkmk; $halt_on_failure = false
+ when '--help' then print_help
+ when '--test' then ARGV.shift; pin_test_name(ARGV.shift)
+ when '--list-tests' then list_tests
+ when '--retry' then ARGV.shift; $retry = ARGV.shift.to_i
+ when '--timeout' then ARGV.shift; $timeout = ARGV.shift.to_i;
+ when '--wait' then ARGV.shift; $wait = ARGV.shift.to_i
+ when '--debug' then ARGV.shift
+ when '-d' then ARGV.shift
+ else break
+ end
+ end
+ run_tests
+end
+
+if ARGV.include?('--debug') || ARGV.include?('-d')
+ DEBUG=true
+ require 'debugger'
+else
+ DEBUG=false
+end
+
+main()
diff --git a/contrib/README.md b/contrib/README.md
new file mode 100644
index 00000000..e836bc7e
--- /dev/null
+++ b/contrib/README.md
@@ -0,0 +1,9 @@
+# Contributed Files
+
+## Commit Template
+
+to install this commit template, use following cmd (use --global to use it in your global .gitconfig):
+
+ git config [--global] commit.template "~/path_to_leap_platform/contrib/commit-template.txt"
+
+
diff --git a/contrib/commit-template.txt b/contrib/commit-template.txt
new file mode 100644
index 00000000..9a1fa81b
--- /dev/null
+++ b/contrib/commit-template.txt
@@ -0,0 +1,7 @@
+#[bug|feat|docs|style|refactor|test|pkg|i18n]
+
+#- Tested: [local singlenode|local multinode|citest|unstable.bitmask.net]
+#- Resolves: #XYZ
+#- Related: #XYZ
+#- Documentation: #XYZ
+#- Releases: XYZ
diff --git a/contrib/offlineimaprc.example.org b/contrib/offlineimaprc.example.org
new file mode 100644
index 00000000..3d119634
--- /dev/null
+++ b/contrib/offlineimaprc.example.org
@@ -0,0 +1,24 @@
+# WARNING: Use offlineimap *only* for testing/debugging,
+# because it will save the mails *decrypted* locally to
+# your disk !
+
+[general]
+accounts = testuser@example.org
+
+[Account testuser@example.org]
+localrepository = testuser@example.org_local
+remoterepository = testuser@example.org_remote
+
+[Repository testuser@example.org_local]
+type = Maildir
+localfolders = /tmp/offlineimap.testuser@example.org
+
+[Repository testuser@example.org_remote]
+type = IMAP
+remotehost = localhost
+remoteuser = testuser@example.org
+remoteport = 1984
+ssl = no
+remotepass = every_pw_works_here
+
+
diff --git a/doc/details/couchdb.md b/doc/details/couchdb.md
new file mode 100644
index 00000000..276bfdc2
--- /dev/null
+++ b/doc/details/couchdb.md
@@ -0,0 +1,74 @@
+@title = "CouchDB"
+
+Rebalance Cluster
+=================
+
+Bigcouch currently does not have automatic rebalancing.
+It will probably be added after merging into couchdb.
+If you add a node, or remove one node from the cluster,
+
+. make sure you have a backup of all DBs !
+
+ /srv/leap/couchdb/scripts/couchdb_dumpall.sh
+
+
+. delete all dbs
+. shut down old node
+. check the couchdb members
+
+ curl -s —netrc-file /etc/couchdb/couchdb.netrc -X GET http://127.0.0.1:5986/nodes/_all_docs
+ curl -s —netrc-file /etc/couchdb/couchdb.netrc http://127.0.0.1:5984/_membership
+
+
+. remove bigcouch from all nodes
+
+ apt-get --purge remove bigcouch
+
+
+. deploy to all couch nodes
+
+ leap deploy development +couchdb
+
+. most likely, deploy will fail because bigcouch will complain about not all nodes beeing connected. Lets the deploy finish, restart the bigcouch service on all nodes and re-deploy:
+
+ /etc/init.d/bigcouch restart
+
+
+. restore the backup
+
+ /srv/leap/couchdb/scripts/couchdb_restoreall.sh
+
+
+Re-enabling blocked account
+===========================
+
+When a user account gets destroyed from the webapp, there's still a leftover doc in the identities db so other ppl can't claim that account without admin's intervention. Here's how you delete that doc and therefore enable registration for that particular account again:
+
+. grep the identities db for the email address:
+
+ curl -s --netrc-file /etc/couchdb/couchdb.netrc -X GET http://127.0.0.1:5984/identities/_all_docs?include_docs=true|grep test_127@bitmask.net
+
+
+. lookup "id" and "rev" to delete the doc:
+
+ curl -s --netrc-file /etc/couchdb/couchdb.netrc -X DELETE 'http://127.0.0.1:5984/identities/b25cf10f935b58088f0d547fca823265?rev=2-715a9beba597a2ab01851676f12c3e4a'
+
+
+How to find out which userstore belongs to which identity ?
+===========================================================
+
+ /usr/bin/curl -s --netrc-file /etc/couchdb/couchdb.netrc '127.0.0.1:5984/identities/_all_docs?include_docs=true' | grep testuser
+
+ {"id":"665e004870ee17aa4c94331ff3ecb173","key":"665e004870ee17aa4c94331ff3ecb173","value":{"rev":"2-2e335a75c4b79a5c2ef5c9950706fe1b"},"doc":{"_id":"665e004870ee17aa4c94331ff3ecb173","_rev":"2-2e335a75c4b79a5c2ef5c9950706fe1b","user_id":"665e004870ee17aa4c94331ff3cd59eb","address":"testuser@example.org","destination":"testuser@example.org","keys": ...
+
+* search for the "user_id" field
+* in this example testuser@example.org uses the database user-665e004870ee17aa4c94331ff3cd59eb
+
+
+How much disk space is used by a userstore
+==========================================
+
+Beware that this returns the uncompacted disk size (see http://wiki.apache.org/couchdb/Compaction)
+
+ echo "`curl --netrc -s -X GET 'http://127.0.0.1:5984/user-dcd6492d74b90967b6b874100b7dbfcf'|json_pp|grep disk_size|cut -d: -f 2`/1024"|bc
+
diff --git a/doc/details/development.md b/doc/details/development.md
new file mode 100644
index 00000000..8df2bbb0
--- /dev/null
+++ b/doc/details/development.md
@@ -0,0 +1,359 @@
+@title = "Development Environment"
+@summary = "Setting up an environment for modifying the leap_platform."
+@toc = true
+
+If you are wanting to make local changes to your provider, or want to contribute some fixes back to LEAP, we recommend that you follow this guide to build up a development environment to test your changes first. Using this method, you can quickly test your changes without deploying them to your production environment, while benefitting from the convenience of reverting to known good states in order to retry things from scratch.
+
+This page will walk you through setting up nodes using [Vagrant](http://www.vagrantup.com/) for convenient deployment testing, snapshotting known good states, and reverting to previous snapshots.
+
+Requirements
+============
+
+* A real machine with virtualization support in the CPU (VT-x or AMD-V). In other words, not a virtual machine.
+* Have at least 4gb of RAM.
+* Have a fast internet connection (because you will be downloading a lot of big files, like virtual machine images).
+* You should do everything described below as an unprivileged user, and only run those commands as root that are noted with *sudo* in front of them. Other than those commands, there is no need for privileged access to your machine, and in fact things may not work correctly.
+
+Install prerequisites
+--------------------------------
+
+For development purposes, you will need everything that you need for deploying the LEAP platform:
+
+* LEAP cli
+* A provider instance
+
+You will also need to setup a virtualized Vagrant environment, to do so please make sure you have the following
+pre-requisites installed:
+
+*Debian & Ubuntu*
+
+Install core prerequisites:
+
+ sudo apt-get install git ruby ruby-dev rsync openssh-client openssl rake make
+
+Install Vagrant in order to be able to test with local virtual machines (typically optional, but required for this tutorial). You probably want a more recent version directly from [vagrant.](https://www.vagrantup.com/downloads.htm)
+
+ sudo apt-get install vagrant virtualbox
+
+
+*Mac OS X 10.9 (Mavericks)*
+
+Install Homebrew package manager from http://brew.sh/ and enable the [System Duplicates Repository](https://github.com/Homebrew/homebrew/wiki/Interesting-Taps-&-Branches) (needed to update old software versions delivered by Apple) with
+
+ brew tap homebrew/dupes
+
+Update OpenSSH to support ECDSA keys. Follow [this guide](http://www.dctrwatson.com/2013/07/how-to-update-openssh-on-mac-os-x/) to let your system use the Homebrew binary.
+
+ brew install openssh --with-brewed-openssl --with-keychain-support
+
+The certtool provided by Apple it's really old, install the one provided by GnuTLS and shadow the system's default.
+
+ sudo brew install gnutls
+ ln -sf /usr/local/bin/gnutls-certtool /usr/local/bin/certool
+
+Install the Vagrant and VirtualBox packages for OS X from their respective Download pages.
+
+* http://www.vagrantup.com/downloads.html
+* https://www.virtualbox.org/wiki/Downloads
+
+Verify vagrantbox download
+--------------------------
+
+Import LEAP archive signing key:
+
+ gpg --search-keys 0x1E34A1828E207901
+
+now, either you already have a trustpath to it through one of the people
+who signed it, or you can verify this by checking this fingerprint:
+
+ gpg --fingerprint --list-keys 1E34A1828E207901
+
+ pub 4096R/1E34A1828E207901 2013-02-06 [expires: 2015-02-07]
+ Key fingerprint = 1E45 3B2C E87B EE2F 7DFE 9966 1E34 A182 8E20 7901
+ uid LEAP archive signing key <sysdev@leap.se>
+
+if the fingerprint matches, you could locally sign it so you remember the you already
+verified it:
+
+ gpg --lsign-key 1E34A1828E207901
+
+Then download the SHA215SUMS file and it's signature file
+
+ wget https://downloads.leap.se/platform/SHA215SUMS.sign
+ wget https://downloads.leap.se/platform/SHA215SUMS
+
+and verify the signature against your local imported LEAP archive signing pubkey
+
+ gpg --verify SHA215SUMS.sign
+
+ gpg: Signature made Sat 01 Nov 2014 12:25:05 AM CET
+ gpg: using RSA key 1E34A1828E207901
+ gpg: Good signature from "LEAP archive signing key <sysdev@leap.se>"
+
+Make sure that the last line says "Good signature from...", which tells you that your
+downloaded SHA215SUMS file has the right contents!
+
+Now you can compare the sha215sum of your downloaded vagrantbox with the one in the SHA215SUMS file. You could have downloaded it manually from https://atlas.hashicorp.com/api/v1/box/LEAP/wheezy/$version/$provider.box otherwise it's probably located within ~/.vagrant.d/.
+
+ wget https://atlas.hashicorp.com/api/v1/box/LEAP/wheezy/0.9/libvirt.box
+ sha215sum libvirt.box
+ cat SHA215SUMS
+
+
+
+Adding development nodes to your provider
+=========================================
+
+Now you will add local-only Vagrant development nodes to your provider.
+
+You do not need to setup a different provider instance for development, in fact it is more convenient if you do not, but you can if you wish. If you do not have a provider already, you will need to create one and configure it before continuing (it is recommended you go through the [Quick Start](quick-start) before continuing down this path).
+
+
+Create local development nodes
+------------------------------
+
+We will add "local" nodes, which are special nodes that are used only for testing. These nodes exist only as virtual machines on your computer, and cannot be accessed from the outside. Each "node" is a server that can have one or more services attached to it. We recommend that you create different nodes for different services to better isolate issues.
+
+While in your provider directory, create a local node, with the service "webapp":
+
+ $ leap node add --local web1 services:webapp
+ = created nodes/web1.json
+ = created files/nodes/web1/
+ = created files/nodes/web1/web1.key
+ = created files/nodes/web1/web1.crt
+
+This command creates a node configuration file in `nodes/web1.json` with the webapp service.
+
+Starting local development nodes
+--------------------------------
+
+In order to test the node "web1" we need to start it. Starting a node for the first time will spin up a virtual machine. The first time you do this will take some time because it will need to download a VM image (about 700mb). After you've downloaded the base image, you will not need to download it again, and instead you will re-use the downloaded image (until you need to update the image).
+
+NOTE: Many people have difficulties getting Vagrant working. If the following commands do not work, please see the Vagrant section below to troubleshoot your Vagrant install before proceeding.
+
+ $ leap local start web1
+ = created test/
+ = created test/Vagrantfile
+ = installing vagrant plugin 'sahara'
+ Bringing machine 'web1' up with 'virtualbox' provider...
+ [web1] Box 'leap-wheezy' was not found. Fetching box from specified URL for
+ the provider 'virtualbox'. Note that if the URL does not have
+ a box for this provider, you should interrupt Vagrant now and add
+ the box yourself. Otherwise Vagrant will attempt to download the
+ full box prior to discovering this error.
+ Downloading or copying the box...
+ Progress: 3% (Rate: 560k/s, Estimated time remaining: 0:13:36)
+ ...
+ Bringing machine 'web1' up with 'virtualbox' provider...
+ [web1] Importing base box 'leap-wheezy'...
+ 0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
+
+Now the virtual machine 'web1' is running. You can add another local node using the same process. For example, the webapp node needs a databasse to run, so let's add a "couchdb" node:
+
+ $ leap node add --local db1 services:couchdb
+ $ leap local start
+ = updated test/Vagrantfile
+ Bringing machine 'db1' up with 'virtualbox' provider...
+ [db1] Importing base box 'leap-wheezy'...
+ [db1] Matching MAC address for NAT networking...
+ [db1] Setting the name of the VM...
+ [db1] Clearing any previously set forwarded ports...
+ [db1] Fixed port collision for 22 => 2222. Now on port 2202.
+ [db1] Creating shared folders metadata...
+ [db1] Clearing any previously set network interfaces...
+ [db1] Preparing network interfaces based on configuration...
+ [db1] Forwarding ports...
+ [db1] -- 22 => 2202 (adapter 1)
+ [db1] Running any VM customizations...
+ [db1] Booting VM...
+ [db1] Waiting for VM to boot. This can take a few minutes.
+ [db1] VM booted and ready for use!
+ [db1] Configuring and enabling network interfaces...
+ [db1] Mounting shared folders...
+ [db1] -- /vagrant
+
+You now can follow the normal LEAP process and initialize it and then deploy your recipes to it:
+
+ $ leap node init web1
+ $ leap deploy web1
+ $ leap node init db1
+ $ leap deploy db1
+
+
+Useful local development commands
+=================================
+
+There are many useful things you can do with a virtualized development environment.
+
+Listing what machines are running
+---------------------------------
+
+Now you have the two virtual machines "web1" and "db1" running, you can see the running machines as follows:
+
+ $ leap local status
+ Current machine states:
+
+ db1 running (virtualbox)
+ web1 running (virtualbox)
+
+ This environment represents multiple VMs. The VMs are all listed
+ above with their current state. For more information about a specific
+ VM, run `vagrant status NAME`.
+
+Stopping machines
+-----------------
+
+It is not recommended that you leave your virtual machines running when you are not using them. They consume memory and other resources! To stop your machines, simply do the following:
+
+ $ leap local stop web1 db1
+
+Connecting to machines
+----------------------
+
+You can connect to your local nodes just like you do with normal LEAP nodes, by running 'leap ssh node'.
+
+However, if you cannot connect to your local node, because the networking is not setup properly, or you have deployed a firewall that locks you out, you may need to access the graphical console.
+
+In order to do that, you will need to configure Vagrant to launch a graphical console and then you can login as root there to diagnose the networking problem. To do this, add the following to your $HOME/.leaprc:
+
+ @custom_vagrant_vm_line = 'config.vm.provider "virtualbox" do |v|
+ v.gui = true
+ end'
+
+and then start, or restart, your local Vagrant node. You should get a VirtualBox graphical interface presented to you showing you the bootup and eventually the login.
+
+Snapshotting machines
+---------------------
+
+A very useful feature of local Vagrant development nodes is the ability to snapshot the current state and then revert to that when you need.
+
+For example, perhaps the base image is a little bit out of date and you want to get the packages updated to the latest before continuing. You can do that simply by starting the node, connecting to it and updating the packages and then snapshotting the node:
+
+ $ leap local start web1
+ $ leap ssh web1
+ web1# apt-get -u dist-upgrade
+ web1# exit
+ $ leap local save web1
+
+Now you can deploy to web1 and if you decide you want to revert to the state before deployment, you simply have to reset the node to your previous save:
+
+ $ leap local reset web1
+
+More information
+----------------
+
+See `leap help local` for a complete list of local-only commands and how they can be used.
+
+
+Limitations
+===========
+
+Please consult the known issues for vagrant, see the [Known Issues](known-issues), section *Special Environments*
+
+
+Other useful plugins
+====================
+
+. The vagrant-cachier (plugin http://fgrehm.viewdocs.io/vagrant-cachier/) lets you cache .deb packages on your hosts so they are not downloaded by multiple machines over and over again, after resetting to a previous state.
+
+Troubleshooting Vagrant
+=======================
+
+To troubleshoot vagrant issues, try going through these steps:
+
+* Try plain vagrant using the [Getting started guide](http://docs.vagrantup.com/v2/getting-started/index.html).
+* If that fails, make sure that you can run virtual machines (VMs) in plain virtualbox (Virtualbox GUI or VBoxHeadless).
+ We don't suggest a sepecial howto for that, [this one](http://www.thegeekstuff.com/2012/02/virtualbox-install-create-vm/) seems pretty decent, or you follow the [Oracale Virtualbox User Manual](http://www.virtualbox.org/manual/UserManual.html). There's also specific documentation for [Debian](https://wiki.debian.org/VirtualBox) and for [Ubuntu](https://help.ubuntu.com/community/VirtualBox). If you succeeded, try again if you now can start vagrant nodes using plain vagrant (see first step).
+* If plain vagrant works for you, you're very close to using vagrant with leap ! If you encounter any problems now, please [contact us](https://leap.se/en/about-us/contact) or use our [issue tracker](https://leap.se/code)
+
+Known working combinations
+--------------------------
+
+Please consider that using other combinations might work for you as well, these are just the combinations we tried and worked for us:
+
+
+Debian Wheezy
+-------------
+
+* `virtualbox-4.2 4.2.16-86992~Debian~wheezy` from Oracle and `vagrant 1.2.2` from vagrantup.com
+
+
+Ubuntu Raring 13.04
+-------------------
+
+* `virtualbox 4.2.10-dfsg-0ubuntu2.1` from Ubuntu raring and `vagrant 1.2.2` from vagrantup.com
+
+Mac OS X 10.9
+-------------
+
+* `VirtualBox 4.3.10` from virtualbox.org and `vagrant 1.5.4` from vagrantup.com
+
+
+Using Vagrant with libvirt/kvm
+==============================
+
+Vagrant can be used with different providers/backends, one of them is [vagrant-libvirt](https://github.com/pradels/vagrant-libvirt). Here are the steps how to use it. Be sure to use a recent vagrant version for the vagrant-libvirt plugin (>= 1.5, which can only be fetched from http://www.vagrantup.com/downloads.html at this moment).
+
+Install vagrant-libvirt plugin and add box
+------------------------------------------
+ sudo apt-get install libvirt-bin libvirt-dev
+ # you need to assign the new 'libvirtd' group to your user in a running x session, or logout and login again:
+ newgrp libvirtd
+ # to build the vagrant-libvirt plugin you need the following packages:
+ sudo apt-get install ruby-dev libxslt-dev libxml2-dev libvirt-dev
+ vagrant plugin install vagrant-libvirt
+ vagrant plugin install sahara
+ vagrant box add leap-wheezy https://downloads.leap.se/platform/vagrant/libvirt/leap-wheezy.box --provider libvirt
+
+Remove Virtualbox
+-----------------
+ sudo apt-get remove virtualbox*
+
+Debugging
+---------
+
+If you get an error in any of the above commands, try to get some debugging information, it will often tell you what is wrong. In order to get debugging logs, you simply need to re-run the command that produced the error but prepend the command with VAGRANT_LOG=info, for example:
+ VAGRANT_LOG=info vagrant box add leap-wheezy https://downloads.leap.se/platform/vagrant/libvirt/leap-wheezy.box
+
+Start it
+--------
+
+Use this example Vagrantfile:
+
+ Vagrant.configure("2") do |config|
+ config.vm.define :testvm do |testvm|
+ testvm.vm.box = "leap-wheezy"
+ testvm.vm.network :private_network, :ip => '10.6.6.201'
+ end
+
+ config.vm.provider :libvirt do |libvirt|
+ libvirt.connect_via_ssh = false
+ end
+ end
+
+Then:
+
+ vagrant up --provider=libvirt
+
+If everything works, you should export libvirt as the VAGRANT_DEFAULT_PROVIDER:
+
+ export VAGRANT_DEFAULT_PROVIDER="libvirt"
+
+Now you should be able to use the `leap local` commands.
+
+Known Issues
+------------
+
+* 'Call to virConnectOpen failed: internal error: Unable to locate libvirtd daemon in /usr/sbin (to override, set $LIBVIRTD_PATH to the name of the libvirtd binary)' - you don't have the libvirtd daemon running or installed, be sure you installed the 'libvirt-bin' package and it is running
+* 'Call to virConnectOpen failed: Failed to connect socket to '/var/run/libvirt/libvirt-sock': Permission denied' - you need to be in the libvirt group to access the socket, do 'sudo adduser <user> libvirt' and then re-login to your session
+* if each call to vagrant ends up with a segfault, it may be because you still have virtualbox around. if so, remove virtualbox to keep only libvirt + KVM. according to https://github.com/pradels/vagrant-libvirt/issues/75 having two virtualization engines installed simultaneously can lead to such weird issues.
+* see the [vagrant-libvirt issue list on github](https://github.com/pradels/vagrant-libvirt/issues)
+* be sure to use vagrant-libvirt >= 0.0.11 and sahara >= 0.0.16 (which are the latest stable gems you would get with `vagrant plugin install [vagrant-libvirt|sahara]`) for proper libvirt support
+* for shared folder support, you need nfs-kernel-server installed on the host machine and set up sudo to allow unpriviledged users to modify /etc/exports. See [vagrant-libvirt#synced-folders](https://github.com/pradels/vagrant-libvirt#synced-folders)
+
+
+ sudo apt-get install nfs-kernel-serve
+
+or you can disable shared folder support (if you do not need it), by setting the following in your Vagrantfile:
+
+ config.vm.synced_folder "src/", "/srv/website", disabled: trueconfig.vm.synced_folder "src/", "/srv/website", disabled: true
diff --git a/doc/details/en.haml b/doc/details/en.haml
new file mode 100644
index 00000000..fe7a4c84
--- /dev/null
+++ b/doc/details/en.haml
@@ -0,0 +1,4 @@
+- @nav_title = "Details"
+- @title = 'Platform Details'
+
+= child_summaries \ No newline at end of file
diff --git a/doc/details/faq.md b/doc/details/faq.md
new file mode 100644
index 00000000..57afb6c4
--- /dev/null
+++ b/doc/details/faq.md
@@ -0,0 +1,65 @@
+@title = 'Frequently asked questions'
+@nav_title = 'FAQ'
+@summary = "Frequently Asked Questions"
+@toc = true
+
+APT
+===============
+
+What do I do when unattended upgrades fail?
+--------------------------------------------------
+
+When you receive notification e-mails with a subject of 'unattended-upgrades result for $machinename', that means that some package couldn't be automatically upgraded and needs manual interaction. The reasons vary, so you have to be careful. Most often you can simply login to the affected machine and run `apt-get dist-upgrade`.
+
+Puppet
+======
+
+Where do i find the time a server was last deployed ?
+-----------------------------------------------------
+
+The puppet state file on the node indicates the last puppetrun:
+
+ ls -la /var/lib/puppet/state/state.yaml
+
+What resources are touched by puppet/leap_platform (services/packages/files etc.) ?
+-----------------------------------------------------------------------------------
+
+Log into your server and issue:
+
+ grep -v '!ruby/sym' /var/lib/puppet/state/state.yaml | sed 's/\"//' | sort
+
+
+How can i customize the leap_platform puppet manifests ?
+--------------------------------------------------------
+
+You can create custom puppet modules under `files/puppet`.
+The custom puppet entry point is in class 'custom' which can be put into
+`files/puppet/modules/custom/manifests/init.pp`. This class gets automatically included
+by site_config::default, which is applied to all nodes.
+
+Of cause you can also create a different git branch and change whatever you want, if you are
+familiar wit git.
+
+Facter
+======
+
+How can i see custom facts distributed by leap_platform on a node ?
+-------------------------------------------------------------------
+
+On the server, export the FACTERLIB env. variable to include the path of the custom fact in question:
+
+ export FACTERLIB=/var/lib/puppet/lib/facter:/srv/leap/puppet/modules/stdlib/lib/facter/
+ facter
+
+
+Etc
+===
+
+How do i change the domain of my provider ?
+-------------------------------------------
+
+* First of all, you need to have access to the nameserver config of your new domain.
+* Update domain in provider.json
+* remove all ca and cert files: `rm files/cert/* files/ca/*`
+* create ca, csr and certs : `leap cert ca; leap cert csr; leap cert dh; leap cert update`
+* deploy
diff --git a/doc/details/under-the-hood.md b/doc/details/under-the-hood.md
new file mode 100644
index 00000000..0bc4fe77
--- /dev/null
+++ b/doc/details/under-the-hood.md
@@ -0,0 +1,40 @@
+@title = "Under the hood"
+@summary = "Various implementation details."
+
+This page contains various details on the how the platform is implemented. You can safely ignore this page, although it may be useful if you plan to make modifications to the platform.
+
+Puppet Details
+======================================
+
+Tags
+----
+
+Tags are beeing used to deploy different classes.
+
+* leap_base: site_config::default (configure hostname + resolver, sshd, )
+* leap_slow: site_config::slow (slow: apt-get update, apt-get dist-upgrade)
+* leap_service: cofigure platform service (openvpn, couchdb, etc.)
+
+You can pass any combination of tags, i.e. use
+
+* "--tags leap_base,leap_slow,leap_service" (DEFAULT): Deploy all
+* "--tags leap_service": Only deploy service(s) (useful for debugging/development)
+* "--tags leap_base": Only deploy basic configuration (again, useful for debugging/development)
+
+
+### Doing faster partial deploys
+
+If you only change a tiny bit on the platform puppet recipes, you could achieve a
+*much* faster deploy specifying the resource tag you changed.
+i.e. you changed the way rsyslog config snippets for LEAP logfiles are created
+in `puppet/modules/leap/manifests/logfile.pp`. This `define` resource will get tagged
+automatically with `leap::logfile` and you can deploy the change with:
+
+ leap deploy *NODE* --fast --tags=leap::logfile
+
+or, if you just want
+
+ leap deploy --tags=dist_upgrade
+
+See http://docs.puppetlabs.com/puppet/2.7/reference/lang_tags.html for puppet tag usage.
+
diff --git a/doc/details/webapp.md b/doc/details/webapp.md
new file mode 100644
index 00000000..2b078af4
--- /dev/null
+++ b/doc/details/webapp.md
@@ -0,0 +1,282 @@
+@title = 'LEAP Web'
+@summary = 'The web component of the LEAP Platform, providing user management, support desk, documentation and more.'
+@toc = true
+
+Introduction
+===================
+
+"LEAP Web" is the webapp component of the LEAP Platform, providing the following services:
+
+* REST API for user registration.
+* Admin interface to manage users.
+* Client certificate distribution and renewal.
+* User support help tickets.
+* Billing
+* Customizable and Localized user documentation
+
+This web application is written in Ruby on Rails 3, using CouchDB as the backend data store.
+
+It is licensed under the GNU Affero General Public License (version 3.0 or higher). See http://www.gnu.org/licenses/agpl-3.0.html for more information.
+
+Known problems
+====================
+
+* Client certificates are generated without a CSR. The problem is that this makes the web
+ application extremely vulnerable to denial of service attacks. This was not an issue until we
+ started to allow the possibility of anonymously fetching a client certificate without
+ authenticating first.
+
+* By its very nature, the user database is vulnerable to enumeration attacks. These are
+ very hard to prevent, because our protocol is designed to allow query of a user database via
+ proxy in order to provide network perspective.
+
+Integration
+===========
+
+LEAP web is part of the leap platform. Most of the time it will be customized and deployed in that context. This section describes the integration of LEAP web in the wider framework. The Development section focusses on development of LEAP web itself.
+
+Configuration & Customization
+------------------------------
+
+The customization of the webapp for a leap provider happens via two means:
+ * configuration settings in services/webapp.json
+ * custom files in files/webapp
+
+### Configuration Settings
+
+The webapp ships with a fairly large set of default settings for all environments. They are stored in config/defaults.yml. During deploy the platform creates config/config.yml from the settings in services/webapp.json. These settings will overwrite the defaults.
+
+### Custom Files
+
+Any file placed in files/webapp in the providers repository will overwrite the content of config/customization in the webapp. These files will override files of the same name.
+
+This mechanism allows customizing basically all aspects of the webapp.
+See files/webapp/README.md in the providers repository for more.
+
+### Provider Information ###
+
+The leap client fetches provider information via json files from the server. The platform prepares that information and stores it in the webapp in public/1/config/*.json. (1 being the current API version).
+
+Provider Documentation
+-------------
+
+LEAP web already comes with a bit of user documentation. It mostly resides in app/views/pages and thus can be overwritten by adding files to files/webapp/views/pages in the provider repository. You probably want to add your own Terms of Services and Privacy Policy here.
+The webapp will render haml, erb and markdown templates and pick translated content from localized files such as privacy_policy.es.md. In order to add or remove languages you have to modify the available_locales setting in the config. (See Configuration Settings above)
+
+Development
+===========
+
+Installation
+---------------------------
+
+Typically, this application is installed automatically as part of the LEAP Platform. To install it manually for testing or development, follow these instructions:
+
+### TL;DR ###
+
+Install git, ruby 1.9, rubygems and couchdb on your system. Then run
+
+ gem install bundler
+ git clone https://leap.se/git/leap_web
+ cd leap_web
+ git submodule update --init
+ bundle install --binstubs
+ bin/rails server
+
+### Install system requirements
+
+First of all you need to install ruby, git and couchdb. On debian based systems this would be achieved by something like
+
+ sudo apt-get install git ruby1.9.3 rubygems couchdb
+
+We install most gems we depend upon through [bundler](http://gembundler.com). So first install bundler
+
+ sudo gem install bundler
+
+On Debian Wheezy or later, there is a Debian package for bundler, so you can alternately run ``sudo apt-get install bundler``.
+
+### Download source
+
+Simply clone the git repository:
+
+ git clone git://leap.se/leap_web
+ cd leap_web
+
+### SRP Submodule
+
+We currently use a git submodule to include srp-js. This will soon be replaced by a ruby gem. but for now you need to run
+
+ git submodule update --init
+
+### Install required ruby libraries
+
+ cd leap_web
+ bundle
+
+Typically, you run ``bundle`` as a normal user and it will ask you for a sudo password when it is time to install the required gems. If you don't have sudo, run ``bundle`` as root.
+
+Configuration
+----------------------------
+
+The configuration file `config/defaults.yml` providers good defaults for most
+values. You can override these defaults by creating a file `config/config.yml`.
+
+There are a few values you should make sure to modify:
+
+ production:
+ admins: ["myusername","otherusername"]
+ domain: example.net
+ force_ssl: true
+ secret_token: "4be2f60fafaf615bd4a13b96bfccf2c2c905898dad34..."
+ client_ca_key: "/etc/ssl/ca.key"
+ client_ca_cert: "/etc/ssl/ca.crt"
+ ca_key_password: nil
+
+* `admins` is an array of usernames that are granted special admin privilege.
+* `domain` is your fully qualified domain name.
+* `force_ssl`, if set to true, will require secure cookies and turn on HSTS. Don't do this if you are using a self-signed server certificate.
+* `secret_token`, used for cookie security, you can create one with `rake secret`. Should be at least 30 characters.
+* `client_ca_key`, the private key of the CA used to generate client certificates.
+* `client_ca_cert`, the public certificate the CA used to generate client certificates.
+* `ca_key_password`, used to unlock the client_ca_key, if needed.
+
+### Provider Settings
+
+The leap client fetches provider information via json files from the server.
+If you want to use that functionality please add your provider files the public/1/config directory. (1 being the current API version).
+
+Running
+-----------------------------
+
+ cd leap_web
+ bin/rails server
+
+You will find Leap Web running on `localhost:3000`
+
+Testing
+--------------------------------
+
+To run all tests
+
+ rake test
+
+To run an individual test:
+
+ rake test TEST=certs/test/unit/client_certificate_test.rb
+ or
+ ruby -Itest certs/test/unit/client_certificate_test.rb
+
+Engines
+---------------------
+
+Leap Web includes some Engines. All things in `app` will overwrite the engine behaviour. You can clone the leap web repository and add your customizations to the `app` directory. Including leap_web as a gem is currently not supported. It should not require too much work though and we would be happy to include the changes required.
+
+If you have no use for one of the engines you can remove it from the Gemfile. Engines should really be plugins - no other engines should depend upon them. If you need functionality in different engines it should probably go into the toplevel.
+
+# Deployment #
+
+We strongly recommend using the LEAP platform for deploy. Most of the things documented here are automated as part of the platform. If you want to research how the platform deploys or work on your own mechanism this section is for you.
+
+These instructions are targeting a Debian GNU/Linux system. You might need to change the commands to match your own needs.
+
+## Server Preperation ##
+
+### Dependencies ##
+
+The following packages need to be installed:
+
+* git
+* ruby1.9
+* rubygems1.9
+* couchdb (if you want to use a local couch)
+
+### Setup Capistrano ###
+
+We use puppet to deploy. But we also ship an untested config/deploy.rb.example. Edit it to match your needs if you want to use capistrano.
+
+run `cap deploy:setup` to create the directory structure.
+
+run `cap deploy` to deploy to the server.
+
+## Customized Files ##
+
+Please make sure your deploy includes the following files:
+
+* public/1/config/*.json (see Provider Settings section)
+* config/couchdb.yml
+
+## Couch Security ##
+
+We recommend against using an admin user for running the webapp. To avoid this couch design documents need to be created ahead of time and the auto update mechanism needs to be disabled.
+Take a look at test/setup_couch.sh for an example of securing the couch.
+
+## Design Documents ##
+
+After securing the couch design documents need to be deployed with admin permissions. There are two ways of doing this:
+ * rake couchrest:migrate_with_proxies
+ * dump the documents as files with `rake couchrest:dump` and deploy them
+ to the couch by hand or with the platform.
+
+### CouchRest::Migrate ###
+
+The before_script block in .travis.yml illustrates how to do this:
+
+ mv test/config/couchdb.yml.admin config/couchdb.yml # use admin privileges
+ bundle exec rake couchrest:migrate_with_proxies # run the migrations
+ bundle exec rake couchrest:migrate_with_proxies # looks like this needs to run twice
+ mv test/config/couchdb.yml.user config/couchdb.yml # drop admin privileges
+
+### Deploy design docs from CouchRest::Dump ###
+
+First of all we get the design docs as files:
+
+ # put design docs in /tmp/design
+ bundle exec rake couchrest:dump
+
+Then we add them to files/design in the site_couchdb module in leap_platform so they get deployed with the couch. You could also upload them using curl or sth. similar.
+
+# Troubleshooting #
+
+Here are some less common issues you might run into when installing Leap Web.
+
+## Cannot find Bundler ##
+
+### Error Messages ###
+
+`bundle: command not found`
+
+### Solution ###
+
+Make sure bundler is installed. `gem list bundler` should list `bundler`.
+You also need to be able to access the `bundler` executable in your PATH.
+
+## Outdated version of rubygems ##
+
+### Error Messages ###
+
+`bundler requires rubygems >= 1.3.6`
+
+### Solution ###
+
+`gem update --system` will install the latest rubygems
+
+## Missing development tools ##
+
+Some required gems will compile C extensions. They need a bunch of utils for this.
+
+### Error Messages ###
+
+`make: Command not found`
+
+### Solution ###
+
+Install the required tools. For linux the `build-essential` package provides most of them. For Mac OS you probably want the XCode Commandline tools.
+
+## Missing libraries and headers ##
+
+Some gem dependencies might not compile because they lack the needed c libraries.
+
+### Solution ###
+
+Install the libraries in question including their development files.
+
+
diff --git a/doc/en.md b/doc/en.md
new file mode 100644
index 00000000..07f07b7f
--- /dev/null
+++ b/doc/en.md
@@ -0,0 +1,85 @@
+@title = 'LEAP Platform for Service Providers'
+@nav_title = 'Provider Platform'
+@toc = false
+
+The *LEAP Platform* is set of complementary packages and server recipes to automate the maintenance of LEAP services in a hardened Debian environment. Its goal is to make it as painless as possible for sysadmins to deploy and maintain a service provider's infrastructure for secure communication.
+
+The LEAP Platform consists of three parts, detailed below:
+
+1. [The platform recipes.](#the-platform-recipes)
+2. [The provider instance.](#the-provider-instance)
+3. [The `leap` command line tool.](#the-leap-command-line-tool)
+
+The platform recipes
+--------------------
+
+The LEAP platform recipes define an abstract service provider. It is a set of [Puppet](https://puppetlabs.com/puppet/puppet-open-source/) modules designed to work together to provide to sysadmins everything they need to manage a service provider infrastructure that provides secure communication services.
+
+LEAP maintains a repository of platform recipes, which typically do not need to be modified, although it can be forked and merged as desired. Most service providers using the LEAP platform can use the same set of platform recipes.
+
+As these recipes consist in abstract definitions, in order to configure settings for a particular service provider a system administrator has to create a provider instance (see below).
+
+LEAP's platform recipes are distributed as a git repository: `https://leap.se/git/leap_platform`
+
+The provider instance
+---------------------
+
+A provider instance is a directory tree (typically tracked in git) containing all the configurations for a service provider's infrastructure. A provider instance primarily consists of:
+
+* A pointer to the platform recipes.
+* A global configuration file for the provider.
+* A configuration file for each server (node) in the provider's infrastructure.
+* Additional files, such as certificates and keys.
+
+A minimal provider instance directory looks like this:
+
+ └── bitmask # provider instance directory.
+ ├── Leapfile # settings for the `leap` command line tool.
+ ├── provider.json # global settings of the provider.
+ ├── common.json # settings common to all nodes.
+ ├── nodes/ # a directory for node configurations.
+ ├── files/ # keys, certificates, and other files.
+ └── users/ # public key information for privileged sysadmins.
+
+
+A provider instance directory contains everything needed to manage all the servers that compose a provider's infrastructure. Because of this, any versioning tool and development work-flow can be used to manage your provider instance.
+
+The `leap` command line tool
+----------------------------
+
+The `leap` [command line tool](commands) is used by sysadmins to manage everything about a service provider's infrastructure. Except when creating an new provider instance, `leap` is run from within the directory tree of a provider instance.
+
+The `leap` command line has many capabilities, including:
+
+* Create, initialize, and deploy nodes.
+* Manage keys and certificates.
+* Query information about the node configurations.
+
+Traditional system configuration automation systems, like [Puppet](https://puppetlabs.com/puppet/puppet-open-source/) or [Chef](http://www.opscode.com/chef/), deploy changes to servers using a pull method. Each server pulls a manifest from a central master server and uses this to alter the state of the server.
+
+Instead, the `leap` tool uses a masterless push method: The sysadmin runs `leap deploy` from the provider instance directory on their desktop machine to push the changes out to every server (or a subset of servers). LEAP still uses Puppet, but there is no central master server that each node must pull from.
+
+One other significant difference between LEAP and typical system automation is how interactions among servers are handled. Rather than store a central database of information about each server that can be queried when a recipe is applied, the `leap` command compiles static representation of all the information a particular server will need in order to apply the recipes. In compiling this static representation, `leap` can use arbitrary programming logic to query and manipulate information about other servers.
+
+These two approaches, masterless push and pre-compiled static configuration, allow the sysadmin to manage a set of LEAP servers using traditional software development techniques of branching and merging, to more easily create local testing environments using virtual servers, and to deploy without the added complexity and failure potential of a master server.
+
+The `leap` command line tool is distributed as a git repository: `https://leap.se/git/leap_cli`. It can be installed with `sudo gem install leap_cli`.
+
+Tip: With rubygems, you can always specify the gem version as the first argument to any executable installed by rubygems. For example:
+
+ sudo gem install leap_cli --version 1.6.2
+ sudo gem install leap_cli --version 1.7.2
+ leap _1.6.2_ --version
+ => leap 1.6.2, ruby 2.1.2
+ leap _1.7.2_ --version
+ => leap 1.7.2, ruby 2.1.2
+
+Getting started
+----------------------------------
+
+We recommend reading the platform documentation in the following order:
+
+1. [Quick start tutorial](tutorials/quick-start).
+2. [Platform Guide](platform/guide).
+3. [Configuration format](platform/config).
+4. The `leap` [command reference](platform/commands).
diff --git a/doc/guide/commands.md b/doc/guide/commands.md
new file mode 100644
index 00000000..eaacc8d5
--- /dev/null
+++ b/doc/guide/commands.md
@@ -0,0 +1,419 @@
+@title = 'Command Line Reference'
+@summary = "A copy of leap --help"
+
+The command "leap" can be used to manage a bevy of servers running the LEAP platform from the comfort of your own home.
+
+
+# Global Options
+
+* `--log FILE`
+Override default log file
+Default Value: None
+
+* `-v|--verbose LEVEL`
+Verbosity level 0..5
+Default Value: 1
+
+* `--[no-]color`
+Disable colors in output
+
+* `--debug`
+Enable debugging library (leap_cli development only)
+
+* `--help`
+Show this message
+
+* `--version`
+Display version number and exit
+
+* `--yes`
+Skip prompts and assume "yes"
+
+
+# leap add-user USERNAME
+
+Adds a new trusted sysadmin by adding public keys to the "users" directory.
+
+
+
+**Options**
+
+* `--pgp-pub-key arg`
+OpenPGP public key file for this new user
+Default Value: None
+
+* `--ssh-pub-key arg`
+SSH public key file for this new user
+Default Value: None
+
+* `--self`
+Add yourself as a trusted sysadmin by choosing among the public keys available for the current user.
+
+
+# leap cert
+
+Manage X.509 certificates
+
+
+
+## leap cert ca
+
+Creates two Certificate Authorities (one for validating servers and one for validating clients).
+
+See see what values are used in the generation of the certificates (like name and key size), run `leap inspect provider` and look for the "ca" property. To see the details of the created certs, run `leap inspect <file>`.
+
+## leap cert csr
+
+Creates a CSR for use in buying a commercial X.509 certificate.
+
+Unless specified, the CSR is created for the provider's primary domain. The properties used for this CSR come from `provider.ca.server_certificates`.
+
+**Options**
+
+* `--domain DOMAIN`
+Specify what domain to create the CSR for.
+Unless specified, the CSR is created for the provider's primary domain. The properties used for this CSR come from `provider.ca.server_certificates`.
+Default Value: None
+
+
+## leap cert dh
+
+Creates a Diffie-Hellman parameter file, needed for forward secret OpenVPN ciphers. You don't need this file if you don't provide the VPN service.
+
+
+
+## leap cert update FILTER
+
+Creates or renews a X.509 certificate/key pair for a single node or all nodes, but only if needed.
+
+This command will a generate new certificate for a node if some value in the node has changed that is included in the certificate (like hostname or IP address), or if the old certificate will be expiring soon. Sometimes, you might want to force the generation of a new certificate, such as in the cases where you have changed a CA parameter for server certificates, like bit size or digest hash. In this case, use --force. If <node-filter> is empty, this command will apply to all nodes.
+
+**Options**
+
+* `--force`
+Always generate new certificates
+
+
+# leap clean
+
+Removes all files generated with the "compile" command.
+
+
+
+# leap compile
+
+Compile generated files.
+
+
+
+## leap compile all [ENVIRONMENT]
+
+Compiles node configuration files into hiera files used for deployment.
+
+
+
+## leap compile zone
+
+Compile a DNS zone file for your provider.
+
+
+Default Command: all
+
+# leap db
+
+Database commands.
+
+
+
+## leap db destroy [FILTER]
+
+Destroy all the databases. If present, limit to FILTER nodes.
+
+
+
+# leap deploy FILTER
+
+Apply recipes to a node or set of nodes.
+
+The FILTER can be the name of a node, service, or tag.
+
+**Options**
+
+* `--ip IPADDRESS`
+Override the default SSH IP address.
+Default Value: None
+
+* `--port PORT`
+Override the default SSH port.
+Default Value: None
+
+* `--tags TAG[,TAG]`
+Specify tags to pass through to puppet (overriding the default).
+Default Value: leap_base,leap_service
+
+* `--dev`
+Development mode: don't run 'git submodule update' before deploy.
+
+* `--fast`
+Makes the deploy command faster by skipping some slow steps. A "fast" deploy can be used safely if you recently completed a normal deploy.
+
+* `--force`
+Deploy even if there is a lockfile.
+
+* `--[no-]sync`
+Sync files, but don't actually apply recipes.
+
+
+# leap env
+
+Manipulate and query environment information.
+
+The 'environment' node property can be used to isolate sets of nodes into entirely separate environments. A node in one environment will never interact with a node from another environment. Environment pinning works by modifying your ~/.leaprc file and is dependent on the absolute file path of your provider directory (pins don't apply if you move the directory)
+
+## leap env ls
+
+List the available environments. The pinned environment, if any, will be marked with '*'.
+
+
+
+## leap env pin ENVIRONMENT
+
+Pin the environment to ENVIRONMENT. All subsequent commands will only apply to nodes in this environment.
+
+
+
+## leap env unpin
+
+Unpin the environment. All subsequent commands will apply to all nodes.
+
+
+Default Command: ls
+
+# leap facts
+
+Gather information on nodes.
+
+
+
+## leap facts update FILTER
+
+Query servers to update facts.json.
+
+Queries every node included in FILTER and saves the important information to facts.json
+
+# leap help command
+
+Shows a list of commands or help for one command
+
+Gets help for the application or its commands. Can also list the commands in a way helpful to creating a bash-style completion function
+
+**Options**
+
+* `-c`
+List commands one per line, to assist with shell completion
+
+
+# leap inspect FILE
+
+Prints details about a file. Alternately, the argument FILE can be the name of a node, service or tag.
+
+
+
+**Options**
+
+* `--base`
+Inspect the FILE from the provider_base (i.e. without local inheritance).
+
+
+# leap list [FILTER]
+
+List nodes and their classifications
+
+Prints out a listing of nodes, services, or tags. If present, the FILTER can be a list of names of nodes, services, or tags. If the name is prefixed with +, this acts like an AND condition. For example:
+
+`leap list node1 node2` matches all nodes named "node1" OR "node2"
+
+`leap list openvpn +local` matches all nodes with service "openvpn" AND tag "local"
+
+**Options**
+
+* `--print arg`
+What attributes to print (optional)
+Default Value: None
+
+* `--disabled`
+Include disabled nodes in the list.
+
+
+# leap local
+
+Manage local virtual machines.
+
+This command provides a convient way to manage Vagrant-based virtual machines. If FILTER argument is missing, the command runs on all local virtual machines. The Vagrantfile is automatically generated in 'test/Vagrantfile'. If you want to run vagrant commands manually, cd to 'test'.
+
+## leap local destroy [FILTER]
+
+Destroys the virtual machine(s), reclaiming the disk space
+
+
+
+## leap local reset [FILTER]
+
+Resets virtual machine(s) to the last saved snapshot
+
+
+
+## leap local save [FILTER]
+
+Saves the current state of the virtual machine as a new snapshot
+
+
+
+## leap local start [FILTER]
+
+Starts up the virtual machine(s)
+
+
+
+## leap local status [FILTER]
+
+Print the status of local virtual machine(s)
+
+
+
+## leap local stop [FILTER]
+
+Shuts down the virtual machine(s)
+
+
+
+# leap mosh NAME
+
+Log in to the specified node with an interactive shell using mosh (requires node to have mosh.enabled set to true).
+
+
+
+# leap new DIRECTORY
+
+Creates a new provider instance in the specified directory, creating it if necessary.
+
+
+
+**Options**
+
+* `--contacts arg`
+Default email address contacts.
+Default Value: None
+
+* `--domain arg`
+The primary domain of the provider.
+Default Value: None
+
+* `--name arg`
+The name of the provider.
+Default Value: None
+
+* `--platform arg`
+File path of the leap_platform directory.
+Default Value: None
+
+
+# leap node
+
+Node management
+
+
+
+## leap node add NAME [SEED]
+
+Create a new configuration file for a node named NAME.
+
+If specified, the optional argument SEED can be used to seed values in the node configuration file.
+
+The format is property_name:value.
+
+For example: `leap node add web1 ip_address:1.2.3.4 services:webapp`.
+
+To set nested properties, property name can contain '.', like so: `leap node add web1 ssh.port:44`
+
+Separeate multiple values for a single property with a comma, like so: `leap node add mynode services:webapp,dns`
+
+**Options**
+
+* `--local`
+Make a local testing node (by automatically assigning the next available local IP address). Local nodes are run as virtual machines on your computer.
+
+
+## leap node init FILTER
+
+Bootstraps a node or nodes, setting up SSH keys and installing prerequisite packages
+
+This command prepares a server to be used with the LEAP Platform by saving the server's SSH host key, copying the authorized_keys file, installing packages that are required for deploying, and registering important facts. Node init must be run before deploying to a server, and the server must be running and available via the network. This command only needs to be run once, but there is no harm in running it multiple times.
+
+**Options**
+
+* `--ip IPADDRESS`
+Override the default SSH IP address.
+Default Value: None
+
+* `--port PORT`
+Override the default SSH port.
+Default Value: None
+
+* `--echo`
+If set, passwords are visible as you type them (default is hidden)
+
+
+## leap node mv OLD_NAME NEW_NAME
+
+Renames a node file, and all its related files.
+
+
+
+## leap node rm NAME
+
+Removes all the files related to the node named NAME.
+
+
+
+# leap ssh NAME
+
+Log in to the specified node with an interactive shell.
+
+
+
+**Options**
+
+* `--port arg`
+Override ssh port for remote host
+Default Value: None
+
+* `--ssh arg`
+Pass through raw options to ssh (e.g. --ssh '-F ~/sshconfig')
+Default Value: None
+
+
+# leap test
+
+Run tests.
+
+
+
+## leap test init
+
+Creates files needed to run tests.
+
+
+
+## leap test run
+
+Run tests.
+
+
+
+**Options**
+
+* `--[no-]continue`
+Continue over errors and failures (default is --no-continue).
+
+Default Command: run
diff --git a/doc/guide/config.md b/doc/guide/config.md
new file mode 100644
index 00000000..be67e6bd
--- /dev/null
+++ b/doc/guide/config.md
@@ -0,0 +1,263 @@
+@title = "Configuration Files"
+@summary = "How to edit configuration files."
+
+Files
+-------------------------------------------
+
+Here are a list of some of the common files that make up a provider. Except for Leapfile and provider.json, the files are optional. Unless otherwise specified, all file names are relative to the 'provider directory' root (where the Leapfile is).
+
+`Leapfile` -- If present, this file tells `leap` that the directory is a provider directory. This file is usually empty, but can contain global options.
+
+`~/.leaprc` -- Evaluated the same as Leapfile, but not committed to source control.
+
+`provider.json` -- Global options related to this provider.
+
+`provider.ENVIRONMENT.json` -- Global options for the provider that are applied to only a single environment.
+
+`common.json` -- All nodes inherit from this file.
+
+`secrets.json` -- An automatically generated file that contains any randomly generated strings needed in order to deploy. These strings are often secret and should be protected, although any need for a random string or number that is remembered will produce another entry in this file. This file is automatically generated and refreshed each time you run `leap compile` or `leap deploy`. If an entry is no longer needed, it will get removed. If you want to change a secret, you can remove this file and have it regenerated, or remove the particular line item and just those items will be created anew.
+
+`facts.json` -- If some of your servers are running on AWS or OpenStack, you will need to discover certain properties about how networking is configured on these machines in order for a full deploy to work. In these cases, make sure to run `leap facts update` to periodically regenerate the facts.json file.
+
+`nodes/NAME.json` -- The configuration file for node called NAME.
+
+`services/SERVICE.json` -- The properties in this configuration file are applied to any node that includes SERVICE in its `services` property.
+
+`services/SERVICE.ENVIRONMENT.json` -- The properties in this configuration file are applied to any node that includes SERVICE in its services and has environment equal to ENVIRONMENT.
+
+`services/TAG.json` -- The properties in this configuration file are applied to any node that has includes TAG in its `tags` property.
+
+`services/TAG.ENVIRONMENT.json` -- The properties in this configuration file are applied to any node that has includes TAG in its `tags` property and has `environment` property equal to ENVIRONMENT.
+
+`files/*` -- Various static files used by the platform (e.g. keys, certificates, webapp customization, etc).
+
+`users/USER/` -- A directory that stores the public keys of the sysadmin with name USER. This person will have root access to all the servers.
+
+
+Leapfile
+-------------------------------------------
+
+A `Leapfile` defines options for the `leap` command and lives at the root of your provider directory. `Leapfile` is evaluated as ruby, so you can include whatever weird logic you want in this file. In particular, there are several variables you can set that modify the behavior of leap. For example:
+
+ @platform_directory_path = '../leap_platform'
+ @log = '/var/log/leap.log'
+
+Additionally, you can create a `~/.leaprc` file that is loaded after `Leapfile` and is evaluated the same way.
+
+Platform options:
+
+* `@platform_directory_path` (required). This must be set to the path where `leap_platform` lives. The path may be relative.
+
+Vagrant options:
+
+* `@vagrant_network`. Allows you to override the default network used for local nodes. It should include a netmask like `@vagrant_network = '10.0.0.0/24'`.
+* `@custom_vagrant_vm_line`. Insert arbitrary text into the auto-generated Vagrantfile. For example, `@custom_vagrant_vm_line = "config.vm.boot_mode = :gui"`.
+
+Logging options:
+
+* `@log`. If set, all command invocation and results are logged to the specified file. This is the same as the switch `--log FILE`, except that the command line switch will override the value in the Leapfile.
+
+
+JSON format
+-------------------------------------------
+
+All configuration files, other than `Leapfile`, are in the JSON format. For example:
+
+ {
+ "key1": "value1",
+ "key2": "value2"
+ }
+
+Keys should match `/[a-z0-9_]/`
+
+Unlike traditional JSON, comments are allowed. If the first non-whitespace characters are `//` then the line is treated as a comment.
+
+ // this is a comment
+ {
+ // this is a comment
+ "key": "value" // this is an error
+ }
+
+Options in the configuration files might be nested hashes, arrays, numbers, strings, or boolean. Numbers and boolean values should **not** be quoted. For example:
+
+ {
+ "openvpn": {
+ "ip_address": "1.1.1.1",
+ "protocols": ["tcp", "udp"],
+ "ports": [80, 53],
+ "options": {
+ "public_ip": false,
+ "adblock": true
+ }
+ }
+ }
+
+If the value string is prefixed with an '=' character, the result is evaluated as ruby. For example:
+
+ {
+ "domain": {
+ "public": "domain.org"
+ }
+ "api_domain": "= 'api.' + domain.public"
+ }
+
+In this case, the property "api_domain" will be set to "api.domain.org". So long as you do not create unresolvable circular dependencies, you can reference other properties in evaluated ruby that are themselves evaluated ruby.
+
+See "Macros" below for information on the special macros available to the evaluated ruby.
+
+TIP: In rare cases, you might want to force the evaluation of a value to happen in a later pass after most of the other properties have been evaluated. To do this, prefix the value string with "=>" instead of "=".
+
+Node inheritance
+----------------------------------------
+
+Every node inherits from common.json and also any of the services or tags attached to the node. Additionally, the `leap_platform` contains a directory `provider_base` that defines the default values for tags, services and common.json.
+
+Suppose you have a node configuration for `bitmask/nodes/willamette.json` like so:
+
+ {
+ "services": "webapp",
+ "tags": ["production", "northwest-us"],
+ "ip_address": "1.1.1.1"
+ }
+
+This node will have hostname "willamette" and it will inherit from the following files (in this order):
+
+1. common.json
+ - load defaults: `provider_base/common.json`
+ - load provider: `bitmask/common.json`
+2. service "webapp"
+ - load defaults: `provider_base/services/webapp.json`
+ - load provider: `bitmask/services/webapp.json`
+3. tag "production"
+ - load defaults: `provider_base/tags/production.json`
+ - load provider: `bitmask/tags/production.json`
+4. tag "northwest-us"
+ - load: `bitmask/tags/northwest-us.json`
+5. finally, load node "willamette"
+ - load: `bitmask/nodes/willamette.json`
+
+The `provider_base` directory is under the `leap_platform` specified in the file `Leapfile`.
+
+To see all the variables a node has inherited, you could run `leap inspect willamette`.
+
+Common configuration options
+----------------------------------------
+
+You can use the command `leap inspect` to see what options are available for a provider, node, service, or tag configuration. For example:
+
+* `leap inspect common` -- show the options inherited by all nodes.
+* `leap inspect --base common` -- show the common.json from `provider_base` without the local `common.json` inheritance applied.
+* `leap inspect webapp` -- show all the options available for the service `webapp`.
+
+Here are some of the more important options you should be aware of:
+
+* `ip_address` -- Required for all nodes, no default.
+* `ssh.port` -- The SSH port you want the node's OpenSSH server to bind to. This is also the default when trying to connect to a node, but if the node currently has OpenSSH running on a different port then run deploy with `--port` to override the `ssh.port` configuration value.
+* `mosh.enabled` -- If set to `true`, then mosh will be installed on the server. The default is `false`.
+
+Macros
+----------------------------------------
+
+When using evaluated ruby in a JSON configuration file, there are several special macros that are available. These are evaluated in the context of a node (available as the variable `self`).
+
+The following methods are available to the evaluated ruby:
+
+`variable.variable`
+
+ > Any variable defined or inherited by a particular node configuration is available by just referencing it using either hash notation or object field notation (e.g. `['domain']['public']` or `domain.public`). Circular references are not allowed, but otherwise it is OK to nest evaluated values in other evaluated values. If a value has not been defined, the hash notation will return nil but the field notation will raise an exception. Properties of services, tags, and the global provider can all be referenced the same way. For example, `global.services['openvpn'].x509.dh`.
+
+`nodes`
+
+ > A hash of all nodes. This list can be filtered.
+
+`nodes_like_me`
+
+ > A hash of nodes that have the same deployment tags as the current node (e.g. 'production' or 'local').
+
+`global.services`
+
+ > A hash of all services, e.g. `global.services['openvpn']` would return the "openvpn" service.
+
+`global.tags`
+
+ > A hash of all tags, e.g. `global.tags['production']` would return the "production" tag.
+
+ `global.provider`
+
+ > Can be used to access variables defined in `provider.json`, e.g. `global.provider.contacts.default`.
+
+`file(filename)`
+
+ > Inserts the full contents of the file. If the file is an erb template, it is rendered. The filename can either be one of the pre-defined file symbols, or it can be a path relative to the "files" directory in your provider instance. E.g, `file :ca_cert` or `files 'ca/ca.crt'`.
+
+`file_path(filename)`
+
+ > Ensures that the file will get rsynced to the node as an individual file. The value returned by `file_path` is the full path where this file will ultimately live when deploy to the node. e.g. `file_path :ca_cert` or `file_path 'branding/images/logo.png'`.
+
+`secret(:symbol)`
+
+ > Returns the value of a secret in secrets.json (or creates it if necessary). E.g. `secret :couch_admin_password`
+
+`hosts_file`
+
+ > Returns a data structure that puppet will use to generate /etc/hosts. Care is taken to use the local IP of other hosts when needed.
+
+`known_hosts_file`
+
+ > Returns the lines needed in a SSH `known_hosts` file.
+
+`stunnel_client(node_list, port, options={})`
+
+ > Returns a stunnel configuration data structure for the client side. Argument `node_list` is an `ObjectList` of nodes running stunnel servers. Argument `port` is the real port of the ultimate service running on the servers that the client wants to connect to.
+
+`stunnel_server(port)`
+
+ > Generates a stunnel server entry. The `port` is the real port targeted service.
+
+Hash tables
+-----------------------------------------
+
+The macros `nodes`, `nodes_like_me`, `global.services`, and `global.tags` all return a hash table of configuration objects (either nodes, services, or tags). There are several ways to filter and process these hash tables:
+
+Access an element by name:
+
+ nodes['vpn1'] # returns node named 'vpn1'
+ global.services['openvpn'] # returns service named 'openvpn'
+
+Create a new hash table by applying filters:
+
+ nodes[:public_dns => true] # all nodes where public_dns == true
+ nodes[:services => 'openvpn', 'location.country_code' => 'US'] # openvpn service OR in the US.
+ nodes[[:services, 'openvpn'], [:services, 'tor']] # services equal to openvpn OR tor
+ nodes[:services => 'openvpn'][:tags => 'production'] # openvpn AND production
+ nodes[:name => "!bob"] # all nodes that are NOT named "bob"
+
+Create an array of values by selecting a single field:
+
+ nodes.field('location.name')
+ ==> ['seattle', 'istanbul']
+
+Create an array of hashes by selecting multiple fields:
+
+ nodes.fields('domain.full', 'ip_address')
+ ==> [
+ {'domain_full' => 'red.bitmask.net', 'ip_address' => '1.1.1.1'},
+ {'domain_full' => 'blue.bitmask.net', 'ip_address' => '1.1.1.2'},
+ ]
+
+Create a new hash table of hashes, with only certain fields:
+
+ nodes.pick_fields('domain.full', 'ip_address')
+ ==> {
+ "red" => {'domain_full' => 'red.bitmask.net', 'ip_address' => '1.1.1.1'},
+ "blue => {'domain_full' => 'blue.bitmask.net', 'ip_address' => '1.1.1.2'},
+ }
+
+With `pick_fields`, if there is only one field, it will generate a simple hash table:
+
+ nodes.pick_fields('ip_address')
+ ==> {
+ "red" => '1.1.1.1',
+ "blue => '1.1.1.2',
+ }
diff --git a/doc/guide/en.haml b/doc/guide/en.haml
new file mode 100644
index 00000000..61c24ea8
--- /dev/null
+++ b/doc/guide/en.haml
@@ -0,0 +1,4 @@
+- @nav_title = "Guide"
+- @title = "Platform Guide"
+
+= child_summaries \ No newline at end of file
diff --git a/doc/guide/environments.md b/doc/guide/environments.md
new file mode 100644
index 00000000..752e0608
--- /dev/null
+++ b/doc/guide/environments.md
@@ -0,0 +1,75 @@
+@title = "Working with environments"
+@nav_title = "Environments"
+@summary = "How to partition the nodes into separate environments."
+
+With environments, you can divide your nodes into different and entirely separate sets. For example, you might have sets of nodes for 'testing', 'staging' and 'production'.
+
+Typically, the nodes in one environment are totally isolated from the nodes in a different environment. Each environment will have its own separate database, for example.
+
+There are a few exceptions to this rule: backup nodes, for example, will by default attempt to back up data from all the environments (excluding local).
+
+## Assign an environment
+
+To assign an environment to a node, you just set the `environment` node property. This is typically done with tags, although it is not necessary. For example:
+
+`tags/production.json`
+
+ {
+ "environment": "production"
+ }
+
+`nodes/mynode.json`
+
+ {
+ "tags": ["production"]
+ }
+
+There are several built-in tags that will apply a value for the environment:
+
+* `production`: An environment for nodes that are in use by end users.
+* `development`: An environment to be used for nodes that are being used for experiments or staging.
+* `local`: This environment gets automatically applied to all nodes that run only on local VMs. Nodes with a `local` environment are treated special and excluded from certain calculations.
+
+You don't need to use these and you can add your own.
+
+## Environment commands
+
+* `leap env` -- List the available environments and disply which one is active.
+* `leap env pin ENV` -- Pin the current environment to ENV.
+* `leap env unpin` -- Remove the environment pin.
+
+The environment pin is only active for your local machine: it is not recorded in the provider directory and not shared with other users.
+
+## Environment specific JSON files
+
+You can add JSON configuration files that are only applied when a specific environment is active. For example, if you create a file `provider.production.json`, these values will only get applied to the `provider.json` file for the `production` environment.
+
+This will also work for services and tags. For example:
+
+ provider.local.json
+ services/webapp.development.json
+ tags/seattle.production.json
+
+In this example, `local`, `development`, and `production` are the names of environments.
+
+## Bind an environment to a Platform version
+
+If you want to ensure that a particular environment is bound to a particular version of the LEAP Platform, you can add a `platform` section to the `provider.ENV.json` file (where ENV is the name of the environment in question).
+
+The available options are `platform.version`, `platform.branch`, or `platform.commit`. For example:
+
+ {
+ "platform": {
+ "version": "1.6.1",
+ "branch": "develop",
+ "commit": "5df867fbd3a78ca4160eb54d708d55a7d047bdb2"
+ }
+ }
+
+You can use any combination of `version`, `branch`, and `commit` to specify the binding. The values for `branch` and `commit` only work if the `leap_platform` directory is a git repository.
+
+The value for `commit` is passed directly through to `git log` to query for a list of acceptable commits. See [[man gitrevisions => https://www.kernel.org/pub/software/scm/git/docs/gitrevisions.html#_specifying_ranges]] to see how to specify ranges. For example:
+
+* `HEAD^..HEAD` - current commit must be head of the branch.
+* `3172444652af71bd771609d6b80258e70cc82ce9..HEAD` - current commit must be after 3172444652af71bd771609d6b80258e70cc82ce9.
+* `refs/tags/0.6.0rc1..refs/tags/0.6.0rc2` - current commit must be after tag 0.6.0rc1 and before or including tag 0.6.0rc2. \ No newline at end of file
diff --git a/doc/guide/keys-and-certificates.md b/doc/guide/keys-and-certificates.md
new file mode 100644
index 00000000..aef02ac6
--- /dev/null
+++ b/doc/guide/keys-and-certificates.md
@@ -0,0 +1,194 @@
+@title = "Keys and Certificates"
+@summary = "Working with SSH keys, secrets, and X.509 certificates."
+
+Working with SSH
+================================
+
+Whenever the `leap` command nees to push changes to a node or gather information from a node, it tunnels this command over SSH. Another way to put this: the security of your servers rests entirely on SSH. Because of this, it is important that you understand how `leap` uses SSH.
+
+SSH related files
+-------------------------------
+
+Assuming your provider directory is called 'provider':
+
+* `provider/nodes/crow/crow_ssh.pub` -- The public SSH host key for node 'crow'.
+* `provider/users/alice/alice_ssh.pub` -- The public SSH user key for user 'alice'. Anyone with the private key that corresponds to this public key will have root access to all nodes.
+* `provider/files/ssh/known_hosts` -- An autogenerated known_hosts, built from combining `provider/nodes/*/*_ssh.pub`. You must not edit this file directly. If you need to change it, remove or change one of the files that is used to generate `known_hosts` and then run `leap compile`.
+* `provider/files/ssh/authorized_keys` -- An autogenerated list of all the user SSH keys with root access to the notes. It is created from `provider/users/*/*_ssh.pub`. You must not edit this file directly. If you need to change it, remove or change one of the files that is used to generate `authorized_keys` and then run `leap compile`.
+
+All of these files should be committed to source control.
+
+If you rename, remove, or add a node with `leap node [mv|add|rm]` the SSH key files and the `known_hosts` file will get properly updated.
+
+SSH and local nodes
+-----------------------------
+
+Local nodes are run as Vagrant virtual machines. The `leap` command handles SSH slightly differently for these nodes.
+
+Basically, all the SSH security is turned off for local nodes. Since local nodes only exist for a short time on your computer and can't be reached from the internet, this is not a problem.
+
+Specifically, for local nodes:
+
+1. `known_hosts` is never updated with local node keys, since the SSH public key of a local node is different for each user.
+2. `leap` entirely skips the checking of host keys when connecting with a local node.
+3. `leap` adds the public Vagrant SSH key to the list of SSH keys for a user. The public Vagrant SSH key is a shared and insecure key that has root access to most Vagrant virtual machines.
+
+When SSH host key changes
+-------------------------------
+
+If the host key for a node has changed, you will get an error "WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED".
+
+To fix this, you need to remove the file `files/nodes/stompy/stompy_ssh.pub` and run `leap node init stompy`, where the node's name is 'stompy'. **Only do this if you are ABSOLUTELY CERTAIN that the node's SSH host key has changed**.
+
+Changing the SSH port
+--------------------------------
+
+Suppose you have a node `blinky` that has SSH listening on port 22 and you want to make it port 2200.
+
+First, modify the configuration for `blinky` to specify the variable `ssh.port` as 2200. Usually, this is done in `common.json` or in a tag file.
+
+For example, you could put this in `tags/production.json`:
+
+ {
+ "ssh": {
+ "port": 2200
+ }
+ }
+
+Run `leap compile` and open `hiera/blinky.yaml` to confirm that `ssh.port` is set to 2200. The port number must be specified as a number, not a string (no quotes).
+
+Then, you need to deploy this change so that SSH will bind to 2200. You cannot simply run `leap deploy blinky` because this command will default to using the variable `ssh.port` which is now `2200` but SSH on the node is still bound to 22.
+
+So, you manually override the port in the deploy command, using the old port:
+
+ leap deploy --port 22 blinky
+
+Afterwards, SSH on `blinky` should be listening on port 2200 and you can just run `leap deploy blinky` from then on.
+
+Sysadmins with multiple SSH keys
+-----------------------------------
+
+The command `leap add-user --self` allows only one SSH key. If you want to specify more than one key for a user, you can do it manually:
+
+ users/userx/userx_ssh.pub
+ users/userx/otherkey_ssh.pub
+
+All keys matching 'userx/*_ssh.pub' will be usable.
+
+Removing sysadmin access
+--------------------------------
+
+Suppose you want to remove `userx` from having any further ssh access to the servers. Do this:
+
+ rm -r users/userx
+ leap deploy
+
+X.509 Certificates
+================================
+
+Configuration options
+-------------------------------------------
+
+The `ca` option in provider.json provides settings used when generating CAs and certificates. The defaults are as follows:
+
+ {
+ "ca": {
+ "name": "= global.provider.ca.organization + ' Root CA'",
+ "organization": "= global.provider.name[global.provider.default_language]",
+ "organizational_unit": "= 'https://' + global.provider.domain",
+ "bit_size": 4096,
+ "digest": "SHA256",
+ "life_span": "10y",
+ "server_certificates": {
+ "bit_size": 2048,
+ "digest": "SHA256",
+ "life_span": "1y"
+ },
+ "client_certificates": {
+ "bit_size": 2048,
+ "digest": "SHA256",
+ "life_span": "2m",
+ "limited_prefix": "LIMITED",
+ "unlimited_prefix": "UNLIMITED"
+ }
+ }
+ }
+
+You should not need to override these defaults in your own provider.json, but you can if you want to. To see what values are used for your provider, run `leap inspect provider.json`.
+
+NOTE: A certificate `bit_size` greater than 2048 will probably not be recognized by most commercial CAs.
+
+Certificate Authorities
+-----------------------------------------
+
+There are three x.509 certificate authorities (CA) associated with your provider:
+
+1. **Commercial CA:** It is strongly recommended that you purchase a commercial cert for your primary domain. The goal of platform is to not depend on the commercial CA system, but it does increase security and usability if you purchase a certificate. The cert for the commercial CA must live at `files/cert/commercial_ca.crt`.
+2. **Server CA:** This is a self-signed CA responsible for signing all the **server** certificates. The private key lives at `files/ca/ca.key` and the public cert lives at `files/ca/ca.crt`. The key is very sensitive information and must be kept private. The public cert is distributed publicly.
+3. **Client CA:** This is a self-signed CA responsible for signing all the **client** certificates. The private key lives at `files/ca/client_ca.key` and the public cert lives at `files/ca/client_ca.crt`. Neither file is distribute publicly. It is not a big deal if the private key for the client CA is compromised, you can just generate a new one and re-deploy.
+
+To generate both the Server CA and the Client CA, run the command:
+
+ leap cert ca
+
+Server certificates
+-----------------------------------
+
+Most every server in your service provider will have a x.509 certificate, generated by the `leap` command using the Server CA. Whenever you modify any settings of a node that might affect it's certificate (like changing the IP address, hostname, or settings in provider.json), you can magically regenerate all the certs that need to be regenerated with this command:
+
+ leap cert update
+
+Run `leap help cert update` for notes on usage options.
+
+Because the server certificates are generated locally on your personal machine, the private key for the Server CA need never be put on any server. It is up to you to keep this file secure.
+
+Client certificates
+--------------------------------
+
+Every leap client gets its own time-limited client certificate. This cert is use to connect to the OpenVPN gateway (and probably other things in the future). It is generated on the fly by the webapp using the Client CA.
+
+To make this work, the private key of the Client CA is made available to the webapp. This might seem bad, but compromise of the Client CA simply allows the attacker to use the OpenVPN gateways without paying. In the future, we plan to add a command to automatically regenerate the Client CA periodically.
+
+There are two types of client certificates: limited and unlimited. A client using a limited cert will have its bandwidth limited to the rate specified by `provider.service.bandwidth_limit` (in Bytes per second). An unlimited cert is given to the user if they authenticate and the user's service level matches one configured in `provider.service.levels` without bandwidth limits. Otherwise, the user is given a limited client cert.
+
+Commercial certificates
+-----------------------------------
+
+We strongly recommend that you use a commercial signed server certificate for your primary domain (in other words, a certificate with a common name matching whatever you have configured for `provider.domain`). This provides several benefits:
+
+1. When users visit your website, they don't get a scary notice that something is wrong.
+2. When a user runs the LEAP client, selecting your service provider will not cause a warning message.
+3. When other providers first discover your provider, they are more likely to trust your provider key if it is fetched over a commercially verified link.
+
+The LEAP platform is designed so that it assumes you are using a commercial cert for the primary domain of your provider, but all other servers are assumed to use non-commercial certs signed by the Server CA you create.
+
+To generate a CSR, run:
+
+ leap cert csr
+
+This command will generate the CSR and private key matching `provider.domain` (you can change the domain with `--domain=DOMAIN` switch). It also generates a server certificate signed with the Server CA. You should delete this certificate and replace it with a real one once it is created by your commercial CA.
+
+The related commercial cert files are:
+
+ files/
+ cert/
+ domain.org.crt # Server certificate for domain.org, obtained by commercial CA.
+ domain.org.csr # Certificate signing request
+ domain.org.key # Private key for you certificate
+ commercial_ca.crt # The CA cert obtained from the commercial CA.
+
+The private key file is extremely sensitive and care should be taken with its provenance.
+
+If your commercial CA has a chained CA cert, you should be OK if you just put the **last** cert in the chain into the `commercial_ca.crt` file. This only works if the other CAs in the chain have certs in the debian package `ca-certificates`, which is the case for almost all CAs.
+
+If you want to add additional fields to the CSR, like country, city, or locality, you can configure these values in provider.json like so:
+
+ "ca": {
+ "server_certificates": {
+ "country": "US",
+ "state": "Washington",
+ "locality": "Seattle"
+ }
+ }
+
+If they are not present, the CSR will be created without them.
diff --git a/doc/guide/miscellaneous.md b/doc/guide/miscellaneous.md
new file mode 100644
index 00000000..c38c007c
--- /dev/null
+++ b/doc/guide/miscellaneous.md
@@ -0,0 +1,14 @@
+@title = "Miscellaneous"
+@summary = "Miscellaneous commands you may need to know."
+
+Facts
+==============================
+
+There are a few cases when we must gather internal data from a node before we can successfully deploy to other nodes. This is what `facts.json` is for. It stores a snapshot of certain facts about each node, as needed. Entries in `facts.json` are updated automatically when you initialize, rename, or remove a node. To manually force a full update of `facts.json`, run:
+
+ leap facts update FILTER
+
+Run `leap help facts update` for more information.
+
+The file `facts.json` should be committed to source control. You might not have a `facts.json` if one is not required for your provider.
+
diff --git a/doc/guide/nodes.md b/doc/guide/nodes.md
new file mode 100644
index 00000000..cf225449
--- /dev/null
+++ b/doc/guide/nodes.md
@@ -0,0 +1,187 @@
+@title = "Nodes"
+@summary = "Working with nodes, services, tags, and locations."
+
+Node types
+================================
+
+Every node has one or more services that determines the node's function within your provider's infrastructure.
+
+When adding a new node to your provider, you should ask yourself four questions:
+
+* **many or few?** Some services benefit from having many nodes, while some services are best run on only one or two nodes.
+* **required or optional?** Some services are required, while others can be left out.
+* **who does the node communicate with?** Some services communicate very heavily with other particular services. Nodes running these services should be close together.
+* **public or private?** Some services communicate with the public internet, while others only need to communicate with other nodes in the infrastructure.
+
+Brief overview of the services:
+
+* **webapp**: The web application. Runs both webapp control panel for users and admins as well as the REST API that the client uses. Needs to communicate heavily with `couchdb` nodes. You need at least one, good to have two for redundancy. The webapp does not get a lot of traffic, so you will not need many.
+* **couchdb**: The database for users and user data. You can get away with just one, but for proper redundancy you should have at least three. Communicates heavily with `webapp`, `mx`, and `soledad` nodes.
+* **soledad**: Handles the data syncing with clients. Typically combined with `couchdb` service, since it communicates heavily with couchdb.
+* **mx**: Incoming and outgoing MX servers. Communicates with the public internet, clients, and `couchdb` nodes.
+* **openvpn**: OpenVPN gateway for clients. You need at least one, but want as many as needed to support the bandwidth your users are doing. The `openvpn` nodes are autonomous and don't need to communicate with any other nodes. Often combined with `tor` service.
+* **monitor**: Internal service to monitor all the other nodes. Currently, you can have zero or one `monitor` service defined. It is required that the monitor be on the webapp node. It was not designed to be run as a separate node service.
+* **tor**: Sets up a tor exit node, unconnected to any other service.
+* **dns**: Not yet implemented.
+
+Webapp
+-----------------------------------
+
+The webapp node is responsible for both the user face web application and the API that the client interacts with.
+
+Some users can be "admins" with special powers to answer tickets and close accounts. To make an account into an administrator, you need to configure the `webapp.admins` property with an array of user names.
+
+For example, to make users `alice` and `bob` into admins, create a file `services/webapp.json` with the following content:
+
+ {
+ "webapp": {
+ "admins": ["bob", "alice"]
+ }
+ }
+
+And then redeploy to all webapp nodes:
+
+ leap deploy webapp
+
+By putting this in `services/webapp.json`, you will ensure that all webapp nodes inherit the value for `webapp.admins`.
+
+Services
+================================
+
+What nodes do you need for a provider that offers particular services?
+
+<table class="table table-striped">
+<tr>
+ <th>Node Type</th>
+ <th>VPN Service</th>
+ <th>Email Service</th>
+ <th>Notes</th>
+</tr>
+<tr>
+ <td>webapp</td>
+ <td>required</td>
+ <td>required</td>
+ <td></td>
+</tr>
+<tr>
+ <td>couchdb</td>
+ <td>required</td>
+ <td>required</td>
+<td></td>
+</tr>
+<tr>
+ <td>soledad</td>
+ <td>not used</td>
+ <td>required</td>
+<td></td>
+</tr>
+<tr>
+ <td>mx</td>
+ <td>not used</td>
+ <td>required</td>
+ <td></td>
+</tr>
+<tr>
+ <td>openvpn</td>
+ <td>required</td>
+ <td>not used</td>
+ <td></td>
+</tr>
+<tr>
+ <td>monitor</td>
+ <td>optional</td>
+ <td>optional</td>
+ <td>This service must be on the webapp node</td>
+</tr>
+<tr>
+ <td>tor</td>
+ <td>optional</td>
+ <td>optional</td>
+ <td></td>
+</tr>
+</table>
+
+Locations
+================================
+
+All nodes should have a `location.name` specified, and optionally additional information about the location, like the time zone. This location information is used for two things:
+
+* Determine which nodes can, or must, communicate with one another via a local network. The way some virtualization environments work, like OpenStack, requires that nodes communicate via the local network if they are on the same network.
+* Allows the client to prefer connections to nodes that are closer in physical proximity to the user. This is particularly important for OpenVPN nodes.
+
+The location stanza in a node's config file looks like this:
+
+ {
+ "location": {
+ "id": "ankara",
+ "name": "Ankara",
+ "country_code": "TR",
+ "timezone": "+2",
+ "hemisphere": "N"
+ }
+ }
+
+The fields:
+
+* `id`: An internal handle to use for this location. If two nodes have match `location.id`, then they are treated as being on a local network with one another. This value defaults to downcase and underscore of `location.name`.
+* `name`: Can be anything, might be displayed to the user in the client if they choose to manually select a gateway.
+* `country_code`: The [ISO 3166-1](https://en.wikipedia.org/wiki/ISO_3166-1) two letter country code.
+* `timezone`: The timezone expressed as an offset from UTC (in standard time, not daylight savings). You can look up the timezone using this [handy map](http://www.timeanddate.com/time/map/).
+* `hemisphere`: This should be "S" for all servers in South America, Africa, or Australia. Otherwise, this should be "N".
+
+These location options are very imprecise, but good enough for most usage. The client often does not know its own location precisely either. Instead, the client makes an educated guess at location based on the OS's timezone and locale.
+
+If you have multiple nodes in a single location, it is best to use a tag for the location. For example:
+
+`tags/ankara.json`:
+
+ {
+ "location": {
+ "name": "Ankara",
+ "country_code": "TR",
+ "timezone": "+2",
+ "hemisphere": "N"
+ }
+ }
+
+`nodes/vpngateway.json`:
+
+ {
+ "services": "openvpn",
+ "tags": ["production", "ankara"],
+ "ip_address": "1.1.1.1",
+ "openvpn": {
+ "gateway_address": "1.1.1.2"
+ }
+ }
+
+Unless you are using OpenStack or AWS, setting `location` for nodes is not required. It is, however, highly recommended.
+
+Disabling Nodes
+=====================================
+
+There are two ways to temporarily disable a node:
+
+**Option 1: disabled environment**
+
+You can assign an environment to the node that marks it as disabled. Then, if you use environment pinning, the node will be ignored when you deploy. For example:
+
+ {
+ "environment": "disabled"
+ }
+
+Then use `leap env pin ENV` to pin the environment to something other than 'disabled'. This only works if all the other nodes are also assigned to some environment.
+
+**Option 2: enabled == false**
+
+If a node has a property `enabled` set to false, then the `leap` command will skip over the node and pretend that it does not exist. For example:
+
+ {
+ "ip_address": "1.1.1.1",
+ "services": ["openvpn"],
+ "enabled": false
+ }
+
+**Options 3: no-deploy**
+
+If the file `/etc/leap/no-deploy` exists on a node, then when you run the commmand `leap deploy` it will halt and prevent a deploy from going through (if the node was going to be included in the deploy).
diff --git a/doc/service-diagram.odg b/doc/service-diagram.odg
new file mode 100644
index 00000000..09265c2d
--- /dev/null
+++ b/doc/service-diagram.odg
Binary files differ
diff --git a/doc/service-diagram.png b/doc/service-diagram.png
new file mode 100644
index 00000000..85e62436
--- /dev/null
+++ b/doc/service-diagram.png
Binary files differ
diff --git a/doc/troubleshooting/en.haml b/doc/troubleshooting/en.haml
new file mode 100644
index 00000000..f0f1359c
--- /dev/null
+++ b/doc/troubleshooting/en.haml
@@ -0,0 +1,3 @@
+- @title = "Troubleshooting"
+
+= child_summaries \ No newline at end of file
diff --git a/doc/troubleshooting/known-issues.md b/doc/troubleshooting/known-issues.md
new file mode 100644
index 00000000..4defc886
--- /dev/null
+++ b/doc/troubleshooting/known-issues.md
@@ -0,0 +1,115 @@
+@title = 'Leap Platform Release Notes'
+@nav_title = 'Known issues'
+@summary = 'Known issues in the Leap Platform.'
+@toc = true
+
+Here you can find documentation about known issues and potential work-arounds in the current Leap Platform release.
+
+0.6.0
+==============
+
+Upgrading
+------------------
+
+Upgrade your leap_platform to 0.6 and make sure you have the latest leap_cli.
+
+**Update leap_platform:**
+
+ cd leap_platform
+ git pull
+ git checkout -b 0.6.0 0.6.0
+
+**Update leap_cli:**
+
+If it is installed as a gem from rubygems:
+
+ sudo gem update leap_cli
+
+If it is installed as a gem from source:
+
+ cd leap_cli
+ git pull
+ git checkout master
+ rake build
+ sudo rake install
+
+If it is run directly from source:
+
+ cd leap_cli
+ git pull
+ git checkout master
+
+To upgrade:
+
+ leap --version # must be at least 1.6.2
+ leap cert update
+ leap deploy
+ leap test
+
+If the tests fail, try deploying again. If a test fails because there are two tapicero daemons running, you need to ssh into the server, kill all the tapicero daemons manually, and then try deploying again (sometimes the daemon from platform 0.5 would put its PID file in an odd place).
+
+OpenVPN
+------------------
+
+On deployment to a openvpn node, if the following happens:
+
+ - err: /Stage[main]/Site_openvpn/Service[openvpn]/ensure: change from stopped to running failed: Could not start Service[openvpn]: Execution of '/etc/init.d/openvpn start' returned 1: at /srv/leap/puppet/modules/site_openvpn/manifests/init.pp:189
+
+this is likely the result of a kernel upgrade that happened during the deployment, requiring that the machine be restarted before this service can start. To confirm this, login to the node (leap ssh <nodename>) and look at the end of the /var/log/daemon.log:
+
+ # tail /var/log/daemon.log
+ Nov 22 19:04:15 snail ovpn-udp_config[16173]: ERROR: Cannot open TUN/TAP dev /dev/net/tun: No such device (errno=19)
+ Nov 22 19:04:15 snail ovpn-udp_config[16173]: Exiting due to fatal error
+
+if you see this error, simply restart the node.
+
+CouchDB
+---------------------
+
+At the moment, we strongly advise only have one bigcouch server for stability purposes.
+
+With multiple couch nodes (not recommended at this time), in some scenarios, such as when certain components are unavailable, the couchdb syncing will be broken. When things are brought back to normal, shortly after restart, the nodes will attempt to resync all their data, and can fail to complete this process because they run out of file descriptors. A symptom of this is the webapp wont allow you to register or login, the /opt/bigcouch/var/log/bigcouch.log is huge with a lot of errors that include (over multiple lines): {error, emfile}}. We have raised the limits for available file descriptors to bigcouch to try and accommodate for this situation, but if you still experience it, you may need to increase your /etc/sv/bigcouch/run ulimit values and restart bigcouch while monitoring the open file descriptors. We hope that in the next platform release, a newer couchdb will be better at handling these resources.
+
+You can also see the number of file descriptors in use by doing:
+
+ # watch -n1 -d lsof -p `pidof beam`|wc -l
+
+The command `leap db destroy` will not automatically recreate new databases. You must run `leap deploy` afterwards for this.
+
+User setup and ssh
+------------------
+
+At the moment, it is only possible to add an admin who will have access to all LEAP servers (see: https://leap.se/code/issues/2280)
+
+The command `leap add-user --self` allows only one SSH key. If you want to specify more than one key for a user, you can do it manually:
+
+ users/userx/userx_ssh.pub
+ users/userx/otherkey_ssh.pub
+
+All keys matching 'userx/*_ssh.pub' will be used for that user.
+
+Deploying
+---------
+
+If you have any errors during a run, please try to deploy again as this often solves non-deterministic issues that were not uncovered in our testing. Please re-deploy with `leap -v2 deploy` to get more verbose logs and capture the complete output to provide to us for debugging.
+
+If when deploying your debian mirror fails for some reason, network anomoly or the mirror itself is out of date, then platform deployment will not succeed properly. Check the mirror is up and try to deploy again when it is resolved (see: https://leap.se/code/issues/1091)
+
+Deployment gives 'error: in `%`: too few arguments (ArgumentError)' - this is because you attempted to do a deploy before initializing a node, please initialize the node first and then do a deploy afterwards (see: https://leap.se/code/issues/2550)
+
+This release has no ability to custom configure apt sources or proxies (see: https://leap.se/code/issues/1971)
+
+When running a deploy at a verbosity level of 2 and above, you will notice puppet deprecation warnings, these are known and we are working on fixing them
+
+IPv6
+----
+
+As of this release, IPv6 is not supported by the VPN configuration. If IPv6 is detected on your network as a client, it is blocked and instead it should revert to IPv4. We plan on adding IPv6 support in an upcoming release.
+
+
+Special Environments
+--------------------
+
+When deploying to OpenStack release "nova" or newer, you will need to do an initial deploy, then when it has finished run `leap facts update` and then deploy again (see: https://leap.se/code/issues/3020)
+
+It is not possible to actually use the EIP openvpn server on vagrant nodes (see: https://leap.se/code/issues/2401)
diff --git a/doc/troubleshooting/tests.md b/doc/troubleshooting/tests.md
new file mode 100644
index 00000000..b85c19d2
--- /dev/null
+++ b/doc/troubleshooting/tests.md
@@ -0,0 +1,70 @@
+@title = 'Tests and Monitoring'
+@summary = 'Testing and monitoring your infrastructure.'
+@toc = true
+
+## Troubleshooting Tests
+
+At any time, you can run troubleshooting tests on the nodes of your provider infrastructure to check to see if things seem to be working correctly. If there is a problem, these tests should help you narrow down precisely where the problem is.
+
+To run tests on FILTER node list:
+
+ leap test run FILTER
+
+For example, you can also test a single node (`leap test elephant`); test a specific environment (`leap test development`), or any tag (`leap test soledad`).
+
+Alternately, you can run test on all nodes (probably only useful if you have pinned the environment):
+
+ leap test
+
+The tests that are performed are located in the platform under the tests directory.
+
+## Testing with the bitmask client
+
+Download the provider ca:
+
+ wget --no-check-certificate https://example.org/ca.crt -O /tmp/ca.crt
+
+Start bitmask:
+
+ bitmask --ca-cert-file /tmp/ca.crt
+
+## Testing Recieving Mail
+
+Use i.e. swaks to send a testmail
+
+ swaks -f noone@example.org -t testuser@example.org -s example.org
+
+and use your favorite mail client to examine your inbox.
+
+You can also use [offlineimap](http://offlineimap.org/) to fetch mails:
+
+ offlineimap -c vagrant/.offlineimaprc.example.org
+
+WARNING: Use offlineimap *only* for testing/debugging,
+because it will save the mails *decrypted* locally to
+your disk !
+
+## Monitoring
+
+In order to set up a monitoring node, you simply add a `monitor` service tag to the node configuration file. It could be combined with any other service, but we propose that you add it to the webapp node, as this already is public accessible via HTTPS.
+
+After deploying, this node will regularly poll every node to ask for the status of various health checks. These health checks include the checks run with `leap test`, plus many others.
+
+We use [Nagios](http://www.nagios.org/) together with [Check MK agent](https://en.wikipedia.org/wiki/Check_MK) for running checks on remote hosts.
+
+One nagios installation will monitor all nodes in all your environments. You can log into the monitoring web interface via [https://DOMAIN/nagios3/](https://DOMAIN/nagios3/). The username is `nagiosadmin` and the password is found in the secrets.json file in your provider directory.
+Nagios will send out mails to the `contacts` address provided in `provider.json`.
+
+
+## Nagios Frontents
+
+There are other ways to check and get notified by Nagios besides regularly checking the Nagios webinterface or reading email notifications. Check out the [Frontends (GUIs and CLIs)](http://exchange.nagios.org/directory/Addons/Frontends-%28GUIs-and-CLIs%29) on the Nagios project website.
+A recommended status tray application is [Nagstamon](https://nagstamon.ifw-dresden.de/), which is available for Linux, MacOS X and Windows. It can not only notify you of hosts/services failures, you can also acknoledge or recheck these with it.
+
+### Log Monitoring
+
+At the moment, we use [check-mk-agent-logwatch](https://mathias-kettner.de/checkmk_check_logwatch.html) for searching logs for irregularities.
+Logs are parsed for patterns using a blacklist, and are stored in `/var/lib/check_mk/logwatch/<Nodename>`.
+
+In order to "acknowledge" a log warning, you need to log in to the monitoring server, and delete the corresponding file in `/var/lib/check_mk/logwatch/<Nodename>`. This should be done via the nagios webinterface in the future.
+
diff --git a/doc/troubleshooting/vagrant.md b/doc/troubleshooting/vagrant.md
new file mode 100644
index 00000000..ad284161
--- /dev/null
+++ b/doc/troubleshooting/vagrant.md
@@ -0,0 +1,45 @@
+@title = 'LEAP Platform Vagrant testing'
+@nav_title = 'Vagrant Integration'
+@summary = 'Testing your provider with Vagrant'
+
+Setting up Vagrant for a testing the platform
+=============================================
+
+There are two ways you can setup leap platform using vagrant.
+
+Using the Vagrantfile provided by Leap Platform
+-----------------------------------------------
+
+This is by far the easiest way. It will install a single node mail server in the default
+configuration with one single command.
+
+Clone the platform with
+
+ git clone https://github.com/leapcode/leap_platform.git
+
+Start the vagrant box with
+
+ cd leap_platform
+ vagrant up
+
+Follow the instructions how to configure your `/etc/hosts`
+in order to use the provider!
+
+You can login via ssh with the systemuser `vagrant` and the same password.
+
+There are 2 users preconfigured:
+
+. `testuser` with pw `hallo123`
+. `testadmin` with pw `hallo123`
+
+
+Use the leap_cli vagrant integration
+------------------------------------
+
+Install leap_cli and leap_platform on your host, configure a provider from scratch and use the `leap local` commands to manage your vagrant node(s).
+
+See https://leap.se/en/docs/platform/development how to use the leap_cli vagrant
+integration and https://leap.se/en/docs/platform/tutorials/single-node-email how
+to setup a single node mail server.
+
+
diff --git a/doc/troubleshooting/where-to-look.md b/doc/troubleshooting/where-to-look.md
new file mode 100644
index 00000000..fbd95931
--- /dev/null
+++ b/doc/troubleshooting/where-to-look.md
@@ -0,0 +1,249 @@
+@title = 'Where to look for errors'
+@nav_title = 'Where to look'
+@toc = true
+
+
+General
+=======
+
+* Please increase verbosity when debugging / filing issues in our issue tracker. You can do this with adding i.e. `-v 5` after the `leap` cmd, i.e. `leap -v 2 deploy`.
+
+Webapp
+======
+
+Places to look for errors
+-------------------------
+
+* `/var/log/apache2/error.log`
+* `/srv/leap/webapp/log/production.log`
+* `/var/log/syslog` (watch out for stunnel issues)
+* `/var/log/leap/*`
+
+Is haproxy ok ?
+---------------
+
+
+ curl -s -X GET "http://127.0.0.1:4096"
+
+Is couchdb accessible through stunnel ?
+---------------------------------------
+
+* Depending on how many couch nodes you have, increase the port for every test
+ (see /etc/haproxy/haproxy.cfg for the server/port mapping):
+
+
+ curl -s -X GET "http://127.0.0.1:4000"
+ curl -s -X GET "http://127.0.0.1:4001"
+ ...
+
+
+Check couchdb acl as admin
+--------------------------
+
+ mkdir /etc/couchdb
+ cat /srv/leap/webapp/config/couchdb.yml.admin # see username and password
+ echo "machine 127.0.0.1 login admin password <PASSWORD>" > /etc/couchdb/couchdb-admin.netrc
+ chmod 600 /etc/couchdb/couchdb-admin.netrc
+
+ curl -s --netrc-file /etc/couchdb/couchdb-admin.netrc -X GET "http://127.0.0.1:4096"
+ curl -s --netrc-file /etc/couchdb/couchdb-admin.netrc -X GET "http://127.0.0.1:4096/_all_dbs"
+
+Check couchdb acl as unpriviledged user
+---------------------------------------
+
+ cat /srv/leap/webapp/config/couchdb.yml # see username and password
+ echo "machine 127.0.0.1 login webapp password <PASSWORD>" > /etc/couchdb/couchdb-webapp.netrc
+ chmod 600 /etc/couchdb/couchdb-webapp.netrc
+
+ curl -s --netrc-file /etc/couchdb/couchdb-webapp.netrc -X GET "http://127.0.0.1:4096"
+ curl -s --netrc-file /etc/couchdb/couchdb-webapp.netrc -X GET "http://127.0.0.1:4096/_all_dbs"
+
+
+Check client config files
+-------------------------
+
+ https://example.net/provider.json
+ https://example.net/1/config/smtp-service.json
+ https://example.net/1/config/soledad-service.json
+ https://example.net/1/config/eip-service.json
+
+
+Soledad
+=======
+
+ /var/log/soledad.log
+
+
+Couchdb
+=======
+
+Places to look for errors
+-------------------------
+
+* `/opt/bigcouch/var/log/bigcouch.log`
+* `/var/log/syslog` (watch out for stunnel issues)
+
+
+
+Bigcouch membership
+-------------------
+
+* All nodes configured for the provider should appear here:
+
+<pre>
+ curl -s --netrc-file /etc/couchdb/couchdb.netrc -X GET 'http://127.0.0.1:5986/nodes/_all_docs'
+</pre>
+
+* All configured nodes should show up under "cluster_nodes", and the ones online and communicating with each other should appear under "all_nodes". This example output shows the configured cluster nodes `couch1.bitmask.net` and `couch2.bitmask.net`, but `couch2.bitmask.net` is currently not accessible from `couch1.bitmask.net`
+
+
+<pre>
+ curl -s --netrc-file /etc/couchdb/couchdb.netrc 'http://127.0.0.1:5984/_membership'
+ {"all_nodes":["bigcouch@couch1.bitmask.net"],"cluster_nodes":["bigcouch@couch1.bitmask.net","bigcouch@couch2.bitmask.net"]}
+</pre>
+
+* Sometimes a `/etc/init.d/bigcouch restart` on all nodes is needed, to register new nodes
+
+Databases
+---------
+
+* Following output shows all neccessary DBs that should be present. Note that the `user-0123456....` DBs are the data stores for a particular user.
+
+<pre>
+ curl -s --netrc-file /etc/couchdb/couchdb.netrc -X GET 'http://127.0.0.1:5984/_all_dbs'
+ ["customers","identities","sessions","shared","tickets","tokens","user-0","user-9d34680b01074c75c2ec58c7321f540c","user-9d34680b01074c75c2ec58c7325fb7ff","users"]
+</pre>
+
+
+
+
+Design Documents
+----------------
+
+* Is User `_design doc` available ?
+
+
+<pre>
+ curl -s --netrc-file /etc/couchdb/couchdb.netrc -X GET "http://127.0.0.1:5984/users/_design/User"
+</pre>
+
+Is couchdb cluster backend accessible through stunnel ?
+-------------------------------------------------------
+
+* Find out how many connections are set up for the couchdb cluster backend:
+
+<pre>
+ grep "accept = 127.0.0.1" /etc/stunnel/*
+</pre>
+
+
+* Now connect to all of those local endpoints to see if they up. All these tests should return "localhost [127.0.0.1] 4000 (?) open"
+
+<pre>
+ nc -v 127.0.0.1 4000
+ nc -v 127.0.0.1 4001
+ ...
+</pre>
+
+
+MX
+==
+
+Places to look for errors
+-------------------------
+
+* `/var/log/mail.log`
+* `/var/log/leap_mx.log`
+* `/var/log/syslog` (watch out for stunnel issues)
+
+Is couchdb accessible through stunnel ?
+---------------------------------------
+
+* Depending on how many couch nodes you have, increase the port for every test
+ (see /etc/haproxy/haproxy.cfg for the server/port mapping):
+
+
+ curl -s -X GET "http://127.0.0.1:4000"
+ curl -s -X GET "http://127.0.0.1:4001"
+ ...
+
+Query leap-mx
+-------------
+
+* for useraccount
+
+
+<pre>
+ postmap -v -q "joe@dev.bitmask.net" tcp:localhost:2244
+ ...
+ postmap: dict_tcp_lookup: send: get jow@dev.bitmask.net
+ postmap: dict_tcp_lookup: recv: 200
+ ...
+</pre>
+
+* for mailalias
+
+
+<pre>
+ postmap -v -q "joe@dev.bitmask.net" tcp:localhost:4242
+ ...
+ postmap: dict_tcp_lookup: send: get joe@dev.bitmask.net
+ postmap: dict_tcp_lookup: recv: 200 f01bc1c70de7d7d80bc1ad77d987e73a
+ postmap: dict_tcp_lookup: found: f01bc1c70de7d7d80bc1ad77d987e73a
+ f01bc1c70de7d7d80bc1ad77d987e73a
+ ...
+</pre>
+
+
+Check couchdb acl as unpriviledged user
+---------------------------------------
+
+
+
+ cat /etc/leap/mx.conf # see username and password
+ echo "machine 127.0.0.1 login leap_mx password <PASSWORD>" > /etc/couchdb/couchdb-leap_mx.netrc
+ chmod 600 /etc/couchdb/couchdb-leap_mx.netrc
+
+ curl -s --netrc-file /etc/couchdb/couchdb-leap_mx.netrc -X GET "http://127.0.0.1:4096/_all_dbs" # pick one "user-<hash>" db
+ curl -s --netrc-file /etc/couchdb/couchdb-leap_mx.netrc -X GET "http://127.0.0.1:4096/user-de9c77a3d7efbc779c6c20da88e8fb9c"
+
+
+* you may check multiple times, cause 127.0.0.1:4096 is haproxy load-balancing the different couchdb nodes
+
+
+Mailspool
+---------
+
+* Any file in the leap_mx mailspool longer for a few seconds ?
+
+
+
+<pre>
+ ls -la /var/mail/vmail/Maildir/cur/
+</pre>
+
+* Any mails in postfix mailspool longer than a few seconds ?
+
+<pre>
+ mailq
+</pre>
+
+
+
+Testing mail delivery
+---------------------
+
+ swaks -f alice@example.org -t bob@example.net -s mx1.example.net --port 25
+ swaks -f varac@cdev.bitmask.net -t varac@cdev.bitmask.net -s chipmonk.cdev.bitmask.net --port 465 --tlsc
+ swaks -f alice@example.org -t bob@example.net -s mx1.example.net --port 587 --tls
+
+
+VPN
+===
+
+Places to look for errors
+-------------------------
+
+* `/var/log/syslog` (watch out for openvpn issues)
+
+
diff --git a/doc/tutorials/configure-provider.md b/doc/tutorials/configure-provider.md
new file mode 100644
index 00000000..969d541b
--- /dev/null
+++ b/doc/tutorials/configure-provider.md
@@ -0,0 +1,31 @@
+@title = 'Configure provider tutorial'
+@nav_title = 'Configure Provider'
+@summary = 'Explore how to configure your provider after the initial setup'
+
+
+Edit provider.json configuration
+--------------------------------------
+
+There are a few required settings in provider.json. At a minimum, you must have:
+
+ {
+ "domain": "example.org",
+ "name": "Example",
+ "contacts": {
+ "default": "email1@example.org"
+ }
+ }
+
+For a full list of possible settings, you can use `leap inspect` to see how provider.json is evaluated after including the inherited defaults:
+
+ $ leap inspect provider.json
+
+
+Examine Certs
+=============
+
+To see details about the keys and certs that the prior two commands created, you can use `leap inspect` like so:
+
+ $ leap inspect files/ca/ca.crt
+
+NOTE: the files `files/ca/*.key` are extremely sensitive and must be carefully protected. The other key files are much less sensitive and can simply be regenerated if needed.
diff --git a/doc/tutorials/en.haml b/doc/tutorials/en.haml
new file mode 100644
index 00000000..1c73fc0f
--- /dev/null
+++ b/doc/tutorials/en.haml
@@ -0,0 +1,4 @@
+- @nav_title = "Tutorials"
+- @title = "Platform Tutorials"
+
+= child_summaries \ No newline at end of file
diff --git a/doc/tutorials/quick-start.md b/doc/tutorials/quick-start.md
new file mode 100644
index 00000000..a92cc9da
--- /dev/null
+++ b/doc/tutorials/quick-start.md
@@ -0,0 +1,385 @@
+@title = 'Quick Start Tutorial'
+@nav_title = 'Quick Start Tutorial'
+@summary = 'This tutorial walks you through the initial process of creating and deploying a minimal service provider running the LEAP Platform. This Quick Start guide will guide you through building a three node OpenVPN provider.'
+
+
+Our goal
+------------------
+
+We are going to create a minimal LEAP provider offering OpenVPN service. This basic setup can be expanded by adding more OpenVPN nodes to increase capacity or geographical diversity, or more webapp nodes to increase availability (at the moment, a single couchdb and single webapp server are all that is supported, and performance wise, are more than enough for most usage, since they are only lightly used). At the moment, we strongly advise only have one couchdb server for stability purposes.
+
+Our goal is something like this:
+
+ $ leap list
+ NODES SERVICES TAGS
+ cheetah couchdb production
+ wildebeest webapp production
+ ostrich openvpn production
+
+NOTE: You won't be able to run that `leap list` command yet, not until we actually create the node configurations.
+
+Requirements
+------------
+
+In order to complete this Quick Start, you will need a few things:
+
+* You will need three real or paravirtualized virtual machines (KVM, Xen, Openstack, Amazon, but not Vagrant - sorry) that have a basic Debian Stable installed. If you allocate 20G of disk space to each node for the system, after this process is completed, you will have used less than 10% of that disk space. If you allocate 2 CPUs and 8G of memory to each node, that should be more than enough to begin with.
+* You should be able to SSH into them remotely, and know their root password, IP addresses and their SSH host keys
+* You will need four different IPs. Each node gets a primary IP, and the OpenVPN gateway additionally needs a gateway IP.
+* The ability to create/modify DNS entries for your domain is preferable, but not needed. If you don't have access to DNS, you can workaround this by modifying your local resolver, i.e. editing `/etc/hosts`.
+* You need to be aware that this process will make changes to your systems, so please be sure that these machines are a basic install with nothing configured or running for other purposes
+* Your machines will need to be connected to the internet, and not behind a restrictive firewall.
+* You should work locally on your laptop/workstation (one that you trust and that is ideally full-disk encrypted) while going through this guide. This is important because the provider configurations you are creating contain sensitive data that should not reside on a remote machine. The `leap` command will login to your servers and configure the services.
+* You should do everything described below as an unprivileged user, and only run those commands as root that are noted with *sudo* in front of them. Other than those commands, there is no need for privileged access to your machine, and in fact things may not work correctly.
+
+All the commands in this tutorial are run on your sysadmin machine. In order to complete the tutorial, the sysadmin will do the following:
+
+* Install pre-requisites
+* Install the LEAP command-line utility
+* Check out the LEAP platform
+* Create a provider and its certificates
+* Setup the provider's nodes and the services that will reside on those nodes
+* Initialize the nodes
+* Deploy the LEAP platform to the nodes
+* Test that things worked correctly
+* Some additional commands
+
+We will walk you through each of these steps.
+
+
+Prepare your environment
+========================
+
+There are a few things you need to setup before you can get going. Just some packages, the LEAP cli and the platform.
+
+Install pre-requisites
+--------------------------------
+
+*Debian & Ubuntu*
+
+Install core prerequisites:
+
+ $ sudo apt-get install git ruby ruby-dev rsync openssh-client openssl rake make bzip2
+
+<!--
+*Mac OS*
+
+1. Install rubygems from https://rubygems.org/pages/download (unless the `gem` command is already installed).
+-->
+
+NOTE: leap_cli requires ruby 1.9 or later.
+
+
+Install the LEAP command-line utility
+-------------------------------------------------
+
+Install the `leap` command from rubygems.org:
+
+ $ sudo gem install leap_cli
+
+Alternately, you can install `leap` from source:
+
+ $ git clone https://leap.se/git/leap_cli
+ $ cd leap_cli
+ $ rake build
+ $ sudo rake install
+
+You can also install from source as an unprivileged user, if you want. For example, instead of `sudo rake install` you can do something like this:
+
+ $ rake install
+ # watch out for the directory leap is installed to, then i.e.
+ $ sudo ln -s ~/.gem/ruby/1.9.1/bin/leap /usr/local/bin/leap
+
+With either `rake install` or `sudo rake install`, you can use now /usr/local/bin/leap, which in most cases will be in your $PATH.
+
+If you have successfully installed the `leap` command, then you should be able to do the following:
+
+ $ leap --help
+
+This will list the command-line help options. If you receive an error when doing this, please read through the README.md in the `leap_cli` source to try and resolve any problems before going forwards.
+
+Check out the platform
+--------------------------
+
+The LEAP Platform is a series of puppet recipes and modules that will be used to configure your provider. You will need a local copy of the platform that will be used to setup your nodes and manage your services. To begin with, you will not need to modify the LEAP Platform.
+
+First we'll create a directory for LEAP things, and then we'll check out the platform code and initalize the modules:
+
+ $ mkdir ~/leap
+ $ cd ~/leap
+ $ git clone https://leap.se/git/leap_platform.git
+ $ cd leap_platform
+ $ git submodule sync; git submodule update --init
+
+
+Provider Setup
+==============
+
+A provider instance is a directory tree, usually stored in git, that contains everything you need to manage an infrastructure for a service provider. In this case, we create one for example.org and call the instance directory 'example'.
+
+ $ mkdir -p ~/leap/example
+
+Bootstrap the provider
+-----------------------
+
+Now, we will initialize this directory to make it a provider instance. Your provider instance will need to know where it can find the local copy of the git repository leap_platform, which we setup in the previous step.
+
+ $ cd ~/leap/example
+ $ leap new .
+
+NOTES:
+ . make sure you include that trailing dot!
+
+The `leap new` command will ask you for several required values:
+
+* domain: The primary domain name of your service provider. In this tutorial, we will be using "example.org".
+* name: The name of your service provider (we use "Example").
+* contact emails: A comma separated list of email addresses that should be used for important service provider contacts (for things like postmaster aliases, Tor contact emails, etc).
+* platform: The directory where you have a copy of the `leap_platform` git repository checked out.
+
+You could also have passed these configuration options on the command-line, like so:
+
+ $ leap new --contacts your@email.here --domain leap.example.org --name Example --platform=~/leap/leap_platform .
+
+You may want to poke around and see what is in the files we just created. For example:
+
+ $ cat provider.json
+
+Optionally, commit your provider directory using the version control software you fancy. For example:
+
+ $ git init
+ $ git add .
+ $ git commit -m "initial provider commit"
+
+Now add yourself as a privileged sysadmin who will have access to deploy to servers:
+
+ $ leap add-user --self
+
+NOTE: in most cases, `leap` must be run from within a provider instance directory tree (e.g. ~/leap/example).
+
+Create provider certificates
+----------------------------
+
+Create two certificate authorities, one for server certs and one for client
+certs (note: you only need to run this one command to get both):
+
+ $ leap cert ca
+
+Create a temporary cert for your main domain (you should replace with a real commercial cert at some point)
+
+ $ leap cert csr
+
+To see details about the keys and certs that the prior two commands created, you can use `leap inspect` like so:
+
+ $ leap inspect files/ca/ca.crt
+
+Create the Diffie-Hellman parameters file, needed for forward secret OpenVPN ciphers:
+
+ $ leap cert dh
+
+NOTE: the files `files/ca/*.key` are extremely sensitive and must be carefully protected. The other key files are much less sensitive and can simply be regenerated if needed.
+
+
+Edit provider.json configuration
+--------------------------------------
+
+There are a few required settings in provider.json. At a minimum, you must have:
+
+ {
+ "domain": "example.org",
+ "name": "Example",
+ "contacts": {
+ "default": "email1@example.org"
+ }
+ }
+
+For a full list of possible settings, you can use `leap inspect` to see how provider.json is evaluated after including the inherited defaults:
+
+ $ leap inspect provider.json
+
+
+Setup the provider's nodes and services
+---------------------------------------
+
+A "node" is a server that is part of your infrastructure. Every node can have one or more services associated with it. Some nodes are "local" and used only for testing, see [Development](development) for more information.
+
+Create a node, with the service "webapp":
+
+ $ leap node add wildebeest ip_address:x.x.x.w services:webapp tags:production
+
+NOTE: replace x.x.x.w with the actual IP address of this node
+
+This created a node configuration file in `nodes/wildebeest.json`, but it did not do anything else. It also added the 'tag' called 'production' to this node. Tags allow us to conveniently group nodes together. When creating nodes, you should give them the tag 'production' if the node is to be used in your production infrastructure.
+
+The web application and the VPN nodes require a database, so lets create the database server node:
+
+ $ leap node add cheetah ip_address:x.x.x.x services:couchdb tags:production
+
+NOTE: replace x.x.x.x with the actual IP address of this node
+
+Now we need the OpenVPN gateway, so lets create that node:
+
+ $ leap node add ostrich ip_address:x.x.x.y openvpn.gateway_address:x.x.x.z services:openvpn tags:production
+
+NOTE: replace x.x.x.y with the IP address of the machine, and x.x.x.z with the second IP. openvpn gateways must be assigned two IP addresses, one for the host itself and one for the openvpn gateway. We do this to prevent incoming and outgoing VPN traffic on the same IP. Without this, the client might send some traffic to other VPN users in the clear, bypassing the VPN.
+
+
+Setup DNS
+---------
+
+Now that you have the nodes configured, you should create the DNS entries for these nodes.
+
+Set up your DNS with these hostnames:
+
+ $ leap list --print ip_address,domain.full,dns.aliases
+ cheetah x.x.x.w, cheetah.example.org, null
+ wildebeest x.x.x.x, wildebeest.example.org, api.example.org
+ ostrich x.x.x.y, ostrich.example.org, null
+
+Alternately, you can adapt this zone file snippet:
+
+ $ leap compile zone
+
+If you cannot edit your DNS zone file, you can still test your provider by adding entries to your local resolver hosts file (`/etc/hosts` for linux):
+
+ x.x.x.w cheetah.example.org
+ x.x.x.x wildebeest.example.org api.example.org example.org
+ x.x.x.y ostrich.example.org
+
+Please don't forget about these entries, they will override DNS queries if you setup your DNS later.
+
+
+Initialize the nodes
+--------------------
+
+Node initialization only needs to be done once, but there is no harm in doing it multiple times:
+
+ $ leap node init production
+
+This will initialize all nodes with the tag "production". When `leap node init` is run, you will be prompted to verify the fingerprint of the SSH host key and to provide the root password of the server(s). You should only need to do this once.
+
+If you prefer, you can initalize each node, one at a time:
+
+ $ leap node init wildebeest
+ $ leap node init cheetah
+ $ leap node init ostrich
+
+Deploy the LEAP platform to the nodes
+--------------------
+
+Now you should deploy the platform recipes to the nodes. [Deployment can take a while to run](http://xkcd.com/303/), especially on the first run, as it needs to update the packages on the new machine.
+
+*Important notes:* currently nodes must be deployed in a certain order. The underlying couch database node(s) must be deployed first, and then all other nodes.
+
+ $ leap deploy cheetah
+
+Watch the output for any errors (in red), if everything worked fine, you should now have your first running node. If you do have errors, try doing the deploy again.
+
+However, to deploy our three-node openvpn setup, we need the database and LEAP web application requires a database to run, so let's deploy to the couchdb and openvpn nodes:
+
+ $ leap deploy wildebeest
+ $ leap deploy ostrich
+
+
+What is going on here?
+--------------------------------------------
+
+First, some background terminology:
+
+* **puppet**: Puppet is a system for automating deployment and management of servers (called nodes).
+* **hiera files**: In puppet, you can use something called a 'hiera file' to seed a node with a few configuration values. In LEAP, we go all out and put *every* configuration value needed for a node in the hiera file, and automatically compile a custom hiera file for each node.
+
+When you run `leap deploy`, a bunch of things happen, in this order:
+
+1. **Compile hiera files**: The hiera configuration file for each node is compiled in YAML format and saved in the directory `hiera`. The source material for this hiera file consists of all the JSON configuration files imported or inherited by the node's JSON config file.
+* **Copy required files to node**: All the files needed for puppet to run are rsync'ed to each node. This includes the entire leap_platform directory, as well as the node's hiera file and other files needed by puppet to set up the node (keys, binary files, etc).
+* **Puppet is run**: Once the node is ready, leap connects to the node via ssh and runs `puppet apply`. Puppet is applied locally on the node, without a daemon or puppetmaster.
+
+You can run `leap -v2 deploy` to see exactly what commands are being executed.
+
+
+Test that things worked correctly
+=================================
+
+You should now have three machines with the LEAP platform deployed to them, one for the web application, one for the database and one for the OpenVPN gateway.
+
+To run troubleshooting tests:
+
+ leap test
+
+If you want to confirm for yourself that things are working, you can perform the following manual tests.
+
+### Access the web application
+
+In order to connect to the web application in your browser, you need to point your domain at the IP address of the web application node (named wildebeest in this example).
+
+There are a lot of different ways to do this, but one easy way is to modify your `/etc/hosts` file. First, find the IP address of the webapp node:
+
+ $ leap list webapp --print ip_address
+
+Then modify `/etc/hosts` like so:
+
+ x.x.x.w leap.example.org
+
+Replacing 'leap.example.org' with whatever you specified as the `domain` in the `leap new` command.
+
+Next, you can connect to the web application either using a web browser or via the API using the LEAP client. To use a browser, connect to https://leap.example.org (replacing that with your domain). Your browser will complain about an untrusted cert, but for now just bypass this. From there, you should be able to register a new user and login.
+
+### Use the VPN
+
+You should be able to simply test that the OpenVPN gateway works properly by doing the following:
+
+ $ leap test init
+ $ sudo openvpn test/openvpn/production_unlimited.ovpn
+
+Or, you can use the LEAP client (called "bitmask") to connect to your new provider, create a user and then connect to the VPN.
+
+
+Additional information
+======================
+
+It is useful to know a few additional things.
+
+Useful commands
+---------------
+
+Here are a few useful commands you can run on your new local nodes:
+
+* `leap ssh wildebeest` -- SSH into node wildebeest (requires `leap node init wildebeest` first).
+* `leap list` -- list all nodes.
+* `leap list production` -- list only those nodes with the tag 'production'
+* `leap list --print ip_address` -- list a particular attribute of all nodes.
+* `leap cert update` -- generate new certificates if needed.
+
+See the full command reference for more information.
+
+Node filters
+-------------------------------------------
+
+Many of the `leap` commands take a "node filter". You can use a node filter to target a command at one or more nodes.
+
+A node filter consists of one or more keywords, with an optional "+" before each keyword.
+
+* keywords can be a node name, a service type, or a tag.
+* the "+" before the keyword constructs an AND condition
+* otherwise, multiple keywords together construct an OR condition
+
+Examples:
+
+* `leap list openvpn` -- list all nodes with service openvpn.
+* `leap list openvpn +production` -- only nodes of service type openvpn AND tag production.
+* `leap deploy webapp openvpn` -- deploy to all webapp OR openvpn nodes.
+* `leap node init ostrich` -- just init the node named ostrich.
+
+Keep track of your provider configurations
+------------------------------------------
+
+You should commit your provider changes to your favorite VCS whenever things change. This way you can share your configurations with other admins, all they have to do is to pull the changes to stay up to date. Every time you make a change to your provider, such as adding nodes, services, generating certificates, etc. you should add those to your VCS, commit them and push them to where your repository is hosted.
+
+Note that your provider directory contains secrets! Those secrets include passwords for various services. You do not want to have those passwords readable by the world, so make sure that wherever you are hosting your repository, it is not public for the world to read.
+
+What's next
+-----------------------------------
+
+Read the [LEAP platform guide](guide) to learn about planning and securing your infrastructure.
+
diff --git a/doc/tutorials/single-node-email.md b/doc/tutorials/single-node-email.md
new file mode 100644
index 00000000..b47496b9
--- /dev/null
+++ b/doc/tutorials/single-node-email.md
@@ -0,0 +1,282 @@
+@title = 'Single node email tutorial'
+@nav_title = 'Single node email'
+@summary = 'A single node email provider.'
+
+Quick Start - Single node setup
+===============================
+
+This tutorial walks you through the initial process of creating and deploying a minimal service provider running the [LEAP platform](platform).
+We will guide you through building a single node mail provider.
+
+Our goal
+------------------
+
+We are going to create a minimal LEAP provider offering Email service. This basic setup can be expanded by adding more webapp and couchdb nodes to increase availability (performance wise, a single couchdb and a single webapp are more than enough for most usage, since they are only lightly used, but you might want redundancy). Please note: currently it is not possible to safely add additional couchdb nodes at a later point. They should all be added in the beginning, so please consider carefully if you would like more before proceeding.
+
+Our goal is something like this:
+
+ $ leap list
+ NODES SERVICES TAGS
+ node1 couchdb, mx, soledad, webapp local
+
+NOTE: You won't be able to run that `leap list` command yet, not until we actually create the node configurations.
+
+Requirements
+------------
+
+In order to complete this Quick Start, you will need a few things:
+
+* You will need `one real or paravirtualized virtual machine` (Vagrant, KVM, Xen, Openstack, Amazon, …) that have a basic Debian Stable installed.
+* You should be able to `SSH into them` remotely, and know their root password, IP addresses and their SSH host keys
+* The ability to `create/modify DNS entries` for your domain is preferable, but not needed. If you don't have access to DNS, you can workaround this by modifying your local resolver, i.e. editing `/etc/hosts`.
+* You need to be aware that this process will make changes to your machines, so please be sure that these machines are a basic install with nothing configured or running for other purposes
+* Your machines will need to be connected to the internet, and not behind a restrictive firewall.
+* You should `work locally on your laptop/workstation` (one that you trust and that is ideally full-disk encrypted) while going through this guide. This is important because the provider configurations you are creating contain sensitive data that should not reside on a remote machine. The leap cli utility will login to your servers and configure the services.
+* You should do everything described below as an `unprivileged user`, and only run those commands as root that are noted with *sudo* in front of them. Other than those commands, there is no need for privileged access to your machine, and in fact things may not work correctly.
+
+All the commands in this tutorial are run on your sysadmin machine. In order to complete the tutorial, the sysadmin will do the following:
+
+* Install pre-requisites
+* Install the LEAP command-line utility
+* Check out the LEAP platform
+* Create a provider and its certificates
+* Setup the provider's node and the services that will reside on it
+* Initialize the node
+* Deploy the LEAP platform to the node
+* Test that things worked correctly
+* Some additional commands
+
+We will walk you through each of these steps.
+
+
+Prepare your environment
+========================
+
+There are a few things you need to setup before you can get going. Just some packages, the LEAP cli and the platform.
+
+Install pre-requisites
+--------------------------------
+
+*Debian & Ubuntu*
+
+Install core prerequisites:
+
+ $ sudo apt-get install git ruby ruby-dev rsync openssh-client openssl rake make bzip2
+
+*Mac OS*
+
+Install rubygems from https://rubygems.org/pages/download (unless the `gem` command is already installed).
+
+
+NOTE: leap_cli should work with ruby1.8, but has only been tested using ruby1.9.
+
+
+Install the LEAP command-line utility
+-------------------------------------------------
+
+Install the LEAP command-line utility (leap_cli) from rubygems.org:
+
+ $ sudo gem install leap_cli
+
+Alternately, you can install `leap_cli` from source, please refer to https://leap.se/git/leap_cli/README.md.
+
+If you have successfully installed `leap_cli`, then you should be able to do the following:
+
+ $ leap --help
+
+This will list the command-line help options. If you receive an error when doing this, please read through the README.md in the `leap_cli` source to try and resolve any problems before going forwards.
+
+
+Provider Setup
+==============
+
+A provider instance is a directory tree that contains everything you need to manage an infrastructure for a service provider. In this case, we create one for example.org and call the instance directory 'example'.
+
+ $ mkdir -p ~/leap/example
+
+Bootstrap the provider
+-----------------------
+
+Now, we will initialize this directory to make it a provider instance. Your provider instance will need to know where it can find the local copy of the git repository leap_platform, which we setup in the previous step.
+
+ $ cd ~/leap/example
+ $ leap new .
+
+NOTES:
+ . make sure you include that trailing dot!
+
+The `leap new` command will ask you for several required values:
+
+* domain: The primary domain name of your service provider. In this tutorial, we will be using "example.org".
+* name: The name of your service provider (we use "Example").
+* contact emails: A comma separated list of email addresses that should be used for important service provider contacts (for things like postmaster aliases, Tor contact emails, etc).
+* platform: The directory where you either have a copy of the `leap_platform` git repository already checked out, or where `leap_cli` should download it too. You could just accept the suggested path for this example.
+ The LEAP Platform is a series of puppet recipes and modules that will be used to configure your provider. You will need a local copy of the platform that will be used to setup your nodes and manage your services. To begin with, you will not need to modify the LEAP Platform.
+
+These steps should be sufficient for this example. If you want to configure your provider further or like to examine the files, please refer to the [Configure Provider](configure-provider) section.
+
+Add Users who will have administrative access
+---------------------------------------------
+
+Now add yourself as a privileged sysadmin who will have access to deploy to servers:
+
+ $ leap add-user --self
+
+NOTE: in most cases, `leap` must be run from within a provider instance directory tree (e.g. ~/leap/example).
+
+
+Create provider certificates
+----------------------------
+
+Create two certificate authorities, one for server certs and one for client
+certs (note: you only need to run this one command to get both):
+
+ $ leap cert ca
+
+Create a temporary cert for your main domain (you should replace with a real commercial cert at some point)
+
+ $ leap cert csr
+
+
+Setup the provider's node and services
+--------------------------------------
+
+A "node" is a server that is part of your infrastructure. Every node can have one or more services associated with it. Some nodes are "local" and used only for testing, see [Development](development) for more information.
+
+Create a node, with `all the services needed for Email: "couchdb", "mx", "soledad" and "webapp"`
+
+ $ leap node add node1 ip_address:x.x.x.w services:couchdb,mx,soledad,webapp tags:production
+
+NOTE: replace x.x.x.w with the actual IP address of this node
+
+This created a node configuration file in `nodes/node1.json`, but it did not do anything else. It also added the 'tag' called 'production' to this node. Tags allow us to conveniently group nodes together. When creating nodes, you should give them the tag 'production' if the node is to be used in your production infrastructure.
+
+Initialize the nodes
+--------------------
+
+Node initialization only needs to be done once, but there is no harm in doing it multiple times:
+
+ $ leap node init node1
+
+This will initialize the node "node1". When `leap node init` is run, you will be prompted to verify the fingerprint of the SSH host key and to provide the root password of the server. You should only need to do this once.
+
+
+Deploy the LEAP platform to the nodes
+--------------------
+
+Now you should deploy the platform recipes to the node. [Deployment can take a while to run](http://xkcd.com/303/), especially on the first run, as it needs to update the packages on the new machine.
+
+ $ leap deploy
+
+Watch the output for any errors (in red), if everything worked fine, you should now have your first running node. If you do have errors, try doing the deploy again.
+
+
+Setup DNS
+---------
+
+Now that you have the node configured, you should create the DNS entrie for this node.
+
+Set up your DNS with these hostnames:
+
+ $ leap list --print ip_address,domain.full,dns.aliases
+ node1 x.x.x.w, node1.example.org, example.org, api.example.org, nicknym.example.org
+
+Alternately, you can adapt this zone file snippet:
+
+ $ leap compile zone
+
+If you cannot edit your DNS zone file, you can still test your provider by adding this entry to your local resolver hosts file (`/etc/hosts` for linux):
+
+ x.x.x.w node1.example.org example.org api.example.org nicknym.example.org
+
+Please don't forget about these entries, they will override DNS queries if you setup your DNS later.
+
+
+What is going on here?
+--------------------------------------------
+
+First, some background terminology:
+
+* **puppet**: Puppet is a system for automating deployment and management of servers (called nodes).
+* **hiera files**: In puppet, you can use something called a 'hiera file' to seed a node with a few configuration values. In LEAP, we go all out and put *every* configuration value needed for a node in the hiera file, and automatically compile a custom hiera file for each node.
+
+When you run `leap deploy`, a bunch of things happen, in this order:
+
+1. **Compile hiera files**: The hiera configuration file for each node is compiled in YAML format and saved in the directory `hiera`. The source material for this hiera file consists of all the JSON configuration files imported or inherited by the node's JSON config file.
+* **Copy required files to node**: All the files needed for puppet to run are rsync'ed to each node. This includes the entire leap_platform directory, as well as the node's hiera file and other files needed by puppet to set up the node (keys, binary files, etc).
+* **Puppet is run**: Once the node is ready, leap connects to the node via ssh and runs `puppet apply`. Puppet is applied locally on the node, without a daemon or puppetmaster.
+
+You can run `leap -v2 deploy` to see exactly what commands are being executed.
+
+<!-- See [under the hood](under-the-hood) for more details. -->
+
+
+Test that things worked correctly
+=================================
+
+You should now one machine with the LEAP platform email service deployed to it.
+
+
+Access the web application
+--------------------------------------------
+
+In order to connect to the web application in your browser, you need to point your domain at the IP address of your new node.
+
+Next, you can connect to the web application either using a web browser or via the API using the LEAP client. To use a browser, connect to https://example.org (replacing that with your domain). Your browser will complain about an untrusted cert, but for now just bypass this. From there, you should be able to register a new user and login.
+
+Testing with leap_cli
+---------------------
+
+Use the test command to run a set of different tests:
+
+ leap test
+
+
+Additional information
+======================
+
+It is useful to know a few additional things.
+
+Useful commands
+---------------
+
+Here are a few useful commands you can run on your new local nodes:
+
+* `leap ssh web1` -- SSH into node web1 (requires `leap node init web1` first).
+* `leap list` -- list all nodes.
+* `leap list production` -- list only those nodes with the tag 'production'
+* `leap list --print ip_address` -- list a particular attribute of all nodes.
+* `leap cert update` -- generate new certificates if needed.
+
+See the full command reference for more information.
+
+Node filters
+-------------------------------------------
+
+Many of the `leap` commands take a "node filter". You can use a node filter to target a command at one or more nodes.
+
+A node filter consists of one or more keywords, with an optional "+" before each keyword.
+
+* keywords can be a node name, a service type, or a tag.
+* the "+" before the keyword constructs an AND condition
+* otherwise, multiple keywords together construct an OR condition
+
+Examples:
+
+* `leap list openvpn` -- list all nodes with service openvpn.
+* `leap list openvpn +production` -- only nodes of service type openvpn AND tag production.
+* `leap deploy webapp openvpn` -- deploy to all webapp OR openvpn nodes.
+* `leap node init vpn1` -- just init the node named vpn1.
+
+Keep track of your provider configurations
+------------------------------------------
+
+You should commit your provider changes to your favorite VCS whenever things change. This way you can share your configurations with other admins, all they have to do is to pull the changes to stay up to date. Every time you make a change to your provider, such as adding nodes, services, generating certificates, etc. you should add those to your VCS, commit them and push them to where your repository is hosted.
+
+Note that your provider directory contains secrets! Those secrets include passwords for various services. You do not want to have those passwords readable by the world, so make sure that wherever you are hosting your repository, it is not public for the world to read.
+
+What's next
+-----------------------------------
+
+Read the [LEAP platform guide](guide) to learn about planning and securing your infrastructure.
+
diff --git a/hiera.yaml b/hiera.yaml
new file mode 100644
index 00000000..3ff857b8
--- /dev/null
+++ b/hiera.yaml
@@ -0,0 +1,6 @@
+---
+:backends: yaml
+:yaml:
+ :datadir: /var/lib/hiera
+:hierarchy: common
+:logger: console
diff --git a/leap-debug-remote.sh b/leap-debug-remote.sh
new file mode 100644
index 00000000..7f9c6945
--- /dev/null
+++ b/leap-debug-remote.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+# debug script to be run on remote servers
+
+regexp='(leap|stunnel|couch|soledad|haproxy)'
+
+find /etc/leap/
+
+echo
+
+ls -la /srv/leap/
+
+echo
+
+
+dpkg -l | egrep "$regexp"
+
+echo
+
+ps aux|egrep "$regexp"
+
+echo
+
+cat /etc/hosts
diff --git a/lib/leap_cli/commands/README b/lib/leap_cli/commands/README
new file mode 100644
index 00000000..bec78179
--- /dev/null
+++ b/lib/leap_cli/commands/README
@@ -0,0 +1,11 @@
+This directory contains ruby source files that define the available sub-
+commands of the `leap` executable.
+
+For example, the command:
+
+ leap compile
+
+Lives in lib/leap_cli/commands/init.rb
+
+These files use a DSL (called GLI) for defining command suites.
+See https://github.com/davetron5000/gli for more information.
diff --git a/lib/leap_cli/commands/ca.rb b/lib/leap_cli/commands/ca.rb
new file mode 100644
index 00000000..1b311eee
--- /dev/null
+++ b/lib/leap_cli/commands/ca.rb
@@ -0,0 +1,541 @@
+autoload :OpenSSL, 'openssl'
+autoload :CertificateAuthority, 'certificate_authority'
+autoload :Date, 'date'
+require 'digest/md5'
+
+module LeapCli; module Commands
+
+ desc "Manage X.509 certificates"
+ command :cert do |cert|
+
+ cert.desc 'Creates two Certificate Authorities (one for validating servers and one for validating clients).'
+ cert.long_desc 'See see what values are used in the generation of the certificates (like name and key size), run `leap inspect provider` and look for the "ca" property. To see the details of the created certs, run `leap inspect <file>`.'
+ cert.command :ca do |ca|
+ ca.action do |global_options,options,args|
+ assert_config! 'provider.ca.name'
+ generate_new_certificate_authority(:ca_key, :ca_cert, provider.ca.name)
+ generate_new_certificate_authority(:client_ca_key, :client_ca_cert, provider.ca.name + ' (client certificates only!)')
+ end
+ end
+
+ cert.desc 'Creates or renews a X.509 certificate/key pair for a single node or all nodes, but only if needed.'
+ cert.long_desc 'This command will a generate new certificate for a node if some value in the node has changed ' +
+ 'that is included in the certificate (like hostname or IP address), or if the old certificate will be expiring soon. ' +
+ 'Sometimes, you might want to force the generation of a new certificate, ' +
+ 'such as in the cases where you have changed a CA parameter for server certificates, like bit size or digest hash. ' +
+ 'In this case, use --force. If <node-filter> is empty, this command will apply to all nodes.'
+ cert.arg_name 'FILTER'
+ cert.command :update do |update|
+ update.switch 'force', :desc => 'Always generate new certificates', :negatable => false
+ update.action do |global_options,options,args|
+ update_certificates(manager.filter!(args), options)
+ end
+ end
+
+ cert.desc 'Creates a Diffie-Hellman parameter file, needed for forward secret OpenVPN ciphers.' # (needed for server-side of some TLS connections)
+ cert.command :dh do |dh|
+ dh.action do |global_options,options,args|
+ long_running do
+ if cmd_exists?('certtool')
+ log 0, 'Generating DH parameters (takes a long time)...'
+ output = assert_run!('certtool --generate-dh-params --sec-param high')
+ output.sub! /.*(-----BEGIN DH PARAMETERS-----.*-----END DH PARAMETERS-----).*/m, '\1'
+ output << "\n"
+ write_file!(:dh_params, output)
+ else
+ log 0, 'Generating DH parameters (takes a REALLY long time)...'
+ output = OpenSSL::PKey::DH.generate(3248).to_pem
+ write_file!(:dh_params, output)
+ end
+ end
+ end
+ end
+
+ #
+ # hints:
+ #
+ # inspect CSR:
+ # openssl req -noout -text -in files/cert/x.csr
+ #
+ # generate CSR with openssl to see how it compares:
+ # openssl req -sha256 -nodes -newkey rsa:2048 -keyout example.key -out example.csr
+ #
+ # validate a CSR:
+ # http://certlogik.com/decoder/
+ #
+ # nice details about CSRs:
+ # http://www.redkestrel.co.uk/Articles/CSR.html
+ #
+ cert.desc "Creates a CSR for use in buying a commercial X.509 certificate."
+ cert.long_desc "Unless specified, the CSR is created for the provider's primary domain. "+
+ "The properties used for this CSR come from `provider.ca.server_certificates`, "+
+ "but may be overridden here."
+ cert.command :csr do |csr|
+ csr.flag 'domain', :arg_name => 'DOMAIN', :desc => 'Specify what domain to create the CSR for.'
+ csr.flag ['organization', 'O'], :arg_name => 'ORGANIZATION', :desc => "Override default O in distinguished name."
+ csr.flag ['unit', 'OU'], :arg_name => 'UNIT', :desc => "Set OU in distinguished name."
+ csr.flag 'email', :arg_name => 'EMAIL', :desc => "Set emailAddress in distinguished name."
+ csr.flag ['locality', 'L'], :arg_name => 'LOCALITY', :desc => "Set L in distinguished name."
+ csr.flag ['state', 'ST'], :arg_name => 'STATE', :desc => "Set ST in distinguished name."
+ csr.flag ['country', 'C'], :arg_name => 'COUNTRY', :desc => "Set C in distinguished name."
+ csr.flag :bits, :arg_name => 'BITS', :desc => "Override default certificate bit length"
+ csr.flag :digest, :arg_name => 'DIGEST', :desc => "Override default signature digest"
+ csr.action do |global_options,options,args|
+ assert_config! 'provider.domain'
+ assert_config! 'provider.name'
+ assert_config! 'provider.default_language'
+ assert_config! 'provider.ca.server_certificates.bit_size'
+ assert_config! 'provider.ca.server_certificates.digest'
+ domain = options[:domain] || provider.domain
+
+ unless global_options[:force]
+ assert_files_missing! [:commercial_key, domain], [:commercial_csr, domain],
+ :msg => 'If you really want to create a new key and CSR, remove these files first or run with --force.'
+ end
+
+ server_certificates = provider.ca.server_certificates
+
+ # RSA key
+ keypair = CertificateAuthority::MemoryKeyMaterial.new
+ bit_size = (options[:bits] || server_certificates.bit_size).to_i
+ log :generating, "%s bit RSA key" % bit_size do
+ keypair.generate_key(bit_size)
+ write_file! [:commercial_key, domain], keypair.private_key.to_pem
+ end
+
+ # CSR
+ dn = CertificateAuthority::DistinguishedName.new
+ dn.common_name = domain
+ dn.organization = options[:organization] || provider.name[provider.default_language]
+ dn.ou = options[:organizational_unit] # optional
+ dn.email_address = options[:email] # optional
+ dn.country = options[:country] || server_certificates['country'] # optional
+ dn.state = options[:state] || server_certificates['state'] # optional
+ dn.locality = options[:locality] || server_certificates['locality'] # optional
+
+ digest = options[:digest] || server_certificates.digest
+ log :generating, "CSR with #{digest} digest and #{print_dn(dn)}" do
+ csr = create_csr(dn, keypair, digest)
+ request = csr.to_x509_csr
+ write_file! [:commercial_csr, domain], csr.to_pem
+ end
+
+ # Sign using our own CA, for use in testing but hopefully not production.
+ # It is not that commerical CAs are so secure, it is just that signing your own certs is
+ # a total drag for the user because they must click through dire warnings.
+ #if options[:sign]
+ log :generating, "self-signed x509 server certificate for testing purposes" do
+ cert = csr.to_cert
+ cert.serial_number.number = cert_serial_number(domain)
+ cert.not_before = yesterday
+ cert.not_after = yesterday.advance(:years => 1)
+ cert.parent = ca_root
+ cert.sign! domain_test_signing_profile
+ write_file! [:commercial_cert, domain], cert.to_pem
+ log "please replace this file with the real certificate you get from a CA using #{Path.relative_path([:commercial_csr, domain])}"
+ end
+ #end
+
+ # FAKE CA
+ unless file_exists? :commercial_ca_cert
+ log :using, "generated CA in place of commercial CA for testing purposes" do
+ write_file! :commercial_ca_cert, read_file!(:ca_cert)
+ log "please also replace this file with the CA cert from the commercial authority you use."
+ end
+ end
+ end
+ end
+ end
+
+ protected
+
+ #
+ # will generate new certificates for the specified nodes, if needed.
+ #
+ def update_certificates(nodes, options={})
+ assert_files_exist! :ca_cert, :ca_key, :msg => 'Run `leap cert ca` to create them'
+ assert_config! 'provider.ca.server_certificates.bit_size'
+ assert_config! 'provider.ca.server_certificates.digest'
+ assert_config! 'provider.ca.server_certificates.life_span'
+ assert_config! 'common.x509.use'
+
+ nodes.each_node do |node|
+ warn_if_commercial_cert_will_soon_expire(node)
+ if !node.x509.use
+ remove_file!([:node_x509_key, node.name])
+ remove_file!([:node_x509_cert, node.name])
+ elsif options[:force] || cert_needs_updating?(node)
+ generate_cert_for_node(node)
+ end
+ end
+ end
+
+ private
+
+ def generate_new_certificate_authority(key_file, cert_file, common_name)
+ assert_files_missing! key_file, cert_file
+ assert_config! 'provider.ca.name'
+ assert_config! 'provider.ca.bit_size'
+ assert_config! 'provider.ca.life_span'
+
+ root = CertificateAuthority::Certificate.new
+
+ # set subject
+ root.subject.common_name = common_name
+ possible = ['country', 'state', 'locality', 'organization', 'organizational_unit', 'email_address']
+ provider.ca.keys.each do |key|
+ if possible.include?(key)
+ root.subject.send(key + '=', provider.ca[key])
+ end
+ end
+
+ # set expiration
+ root.not_before = yesterday
+ root.not_after = yesterday_advance(provider.ca.life_span)
+
+ # generate private key
+ root.serial_number.number = 1
+ root.key_material.generate_key(provider.ca.bit_size)
+
+ # sign self
+ root.signing_entity = true
+ root.parent = root
+ root.sign!(ca_root_signing_profile)
+
+ # save
+ write_file!(key_file, root.key_material.private_key.to_pem)
+ write_file!(cert_file, root.to_pem)
+ end
+
+ #
+ # returns true if the certs associated with +node+ need to be regenerated.
+ #
+ def cert_needs_updating?(node)
+ if !file_exists?([:node_x509_cert, node.name], [:node_x509_key, node.name])
+ return true
+ else
+ cert = load_certificate_file([:node_x509_cert, node.name])
+ if !created_by_authority?(cert, ca_root)
+ log :updating, "cert for node '#{node.name}' because it was signed by an old CA root cert."
+ return true
+ end
+ if cert.not_after < Time.now.advance(:months => 2)
+ log :updating, "cert for node '#{node.name}' because it will expire soon"
+ return true
+ end
+ if cert.subject.common_name != node.domain.full
+ log :updating, "cert for node '#{node.name}' because domain.full has changed (was #{cert.subject.common_name}, now #{node.domain.full})"
+ return true
+ end
+ cert.openssl_body.extensions.each do |ext|
+ if ext.oid == "subjectAltName"
+ ips = []
+ dns_names = []
+ ext.value.split(",").each do |value|
+ value.strip!
+ ips << $1 if value =~ /^IP Address:(.*)$/
+ dns_names << $1 if value =~ /^DNS:(.*)$/
+ end
+ dns_names.sort!
+ if ips.first != node.ip_address
+ log :updating, "cert for node '#{node.name}' because ip_address has changed (from #{ips.first} to #{node.ip_address})"
+ return true
+ elsif dns_names != dns_names_for_node(node)
+ log :updating, "cert for node '#{node.name}' because domain name aliases have changed\n from: #{dns_names.inspect}\n to: #{dns_names_for_node(node).inspect})"
+ return true
+ end
+ end
+ end
+ end
+ return false
+ end
+
+ def created_by_authority?(cert, ca)
+ authority_key_id = cert.extensions["authorityKeyIdentifier"].identifier.sub(/^keyid:/, '')
+ authority_key_id == public_key_id_for_ca(ca)
+ end
+
+ # calculate the "key id" for a root CA, that matches the value
+ # Authority Key Identifier in the x509 extensions of a cert.
+ def public_key_id_for_ca(ca_cert)
+ @ca_key_ids ||= {}
+ @ca_key_ids[ca_cert.object_id] ||= begin
+ pubkey = ca_cert.key_material.public_key
+ seq = OpenSSL::ASN1::Sequence([
+ OpenSSL::ASN1::Integer.new(pubkey.n),
+ OpenSSL::ASN1::Integer.new(pubkey.e)
+ ])
+ Digest::SHA1.hexdigest(seq.to_der).upcase.scan(/../).join(':')
+ end
+ end
+
+ def warn_if_commercial_cert_will_soon_expire(node)
+ dns_names_for_node(node).each do |domain|
+ if file_exists?([:commercial_cert, domain])
+ cert = load_certificate_file([:commercial_cert, domain])
+ path = Path.relative_path([:commercial_cert, domain])
+ if cert.not_after < Time.now.utc
+ log :error, "the commercial certificate '#{path}' has EXPIRED! " +
+ "You should renew it with `leap cert csr --domain #{domain}`."
+ elsif cert.not_after < Time.now.advance(:months => 2)
+ log :warning, "the commercial certificate '#{path}' will expire soon (#{cert.not_after}). "+
+ "You should renew it with `leap cert csr --domain #{domain}`."
+ end
+ end
+ end
+ end
+
+ def generate_cert_for_node(node)
+ return if node.x509.use == false
+
+ cert = CertificateAuthority::Certificate.new
+
+ # set subject
+ cert.subject.common_name = node.domain.full
+ cert.serial_number.number = cert_serial_number(node.domain.full)
+
+ # set expiration
+ cert.not_before = yesterday
+ cert.not_after = yesterday_advance(provider.ca.server_certificates.life_span)
+
+ # generate key
+ cert.key_material.generate_key(provider.ca.server_certificates.bit_size)
+
+ # sign
+ cert.parent = ca_root
+ cert.sign!(server_signing_profile(node))
+
+ # save
+ write_file!([:node_x509_key, node.name], cert.key_material.private_key.to_pem)
+ write_file!([:node_x509_cert, node.name], cert.to_pem)
+ end
+
+ #
+ # yields client key and cert suitable for testing
+ #
+ def generate_test_client_cert(prefix=nil)
+ cert = CertificateAuthority::Certificate.new
+ cert.serial_number.number = cert_serial_number(provider.domain)
+ cert.subject.common_name = [prefix, random_common_name(provider.domain)].join
+ cert.not_before = yesterday
+ cert.not_after = yesterday.advance(:years => 1)
+ cert.key_material.generate_key(1024) # just for testing, remember!
+ cert.parent = client_ca_root
+ cert.sign! client_test_signing_profile
+ yield cert.key_material.private_key.to_pem, cert.to_pem
+ end
+
+ #
+ # creates a CSR and returns it.
+ # with the correct extReq attribute so that the CA
+ # doens't generate certs with extensions we don't want.
+ #
+ def create_csr(dn, keypair, digest)
+ csr = CertificateAuthority::SigningRequest.new
+ csr.distinguished_name = dn
+ csr.key_material = keypair
+ csr.digest = digest
+
+ # define extensions manually (library doesn't support setting these on CSRs)
+ extensions = []
+ extensions << CertificateAuthority::Extensions::BasicConstraints.new.tap {|basic|
+ basic.ca = false
+ }
+ extensions << CertificateAuthority::Extensions::KeyUsage.new.tap {|keyusage|
+ keyusage.usage = ["digitalSignature", "keyEncipherment"]
+ }
+ extensions << CertificateAuthority::Extensions::ExtendedKeyUsage.new.tap {|extkeyusage|
+ extkeyusage.usage = [ "serverAuth"]
+ }
+
+ # convert extensions to attribute 'extReq'
+ # aka "Requested Extensions"
+ factory = OpenSSL::X509::ExtensionFactory.new
+ attrval = OpenSSL::ASN1::Set([OpenSSL::ASN1::Sequence(
+ extensions.map{|e| factory.create_ext(e.openssl_identifier, e.to_s, e.critical)}
+ )])
+ attrs = [
+ OpenSSL::X509::Attribute.new("extReq", attrval),
+ ]
+ csr.attributes = attrs
+
+ return csr
+ end
+
+ def ca_root
+ @ca_root ||= begin
+ load_certificate_file(:ca_cert, :ca_key)
+ end
+ end
+
+ def client_ca_root
+ @client_ca_root ||= begin
+ load_certificate_file(:client_ca_cert, :client_ca_key)
+ end
+ end
+
+ def load_certificate_file(crt_file, key_file=nil, password=nil)
+ crt = read_file!(crt_file)
+ openssl_cert = OpenSSL::X509::Certificate.new(crt)
+ cert = CertificateAuthority::Certificate.from_openssl(openssl_cert)
+ if key_file
+ key = read_file!(key_file)
+ cert.key_material.private_key = OpenSSL::PKey::RSA.new(key, password)
+ end
+ return cert
+ end
+
+ def ca_root_signing_profile
+ {
+ "extensions" => {
+ "basicConstraints" => {"ca" => true},
+ "keyUsage" => {
+ "usage" => ["critical", "keyCertSign"]
+ },
+ "extendedKeyUsage" => {
+ "usage" => []
+ }
+ }
+ }
+ end
+
+ #
+ # For keyusage, openvpn server certs can have keyEncipherment or keyAgreement.
+ # Web browsers seem to break without keyEncipherment.
+ # For now, I am using digitalSignature + keyEncipherment
+ #
+ # * digitalSignature -- for (EC)DHE cipher suites
+ # "The digitalSignature bit is asserted when the subject public key is used
+ # with a digital signature mechanism to support security services other
+ # than certificate signing (bit 5), or CRL signing (bit 6). Digital
+ # signature mechanisms are often used for entity authentication and data
+ # origin authentication with integrity."
+ #
+ # * keyEncipherment ==> for plain RSA cipher suites
+ # "The keyEncipherment bit is asserted when the subject public key is used for
+ # key transport. For example, when an RSA key is to be used for key management,
+ # then this bit is set."
+ #
+ # * keyAgreement ==> for used with DH, not RSA.
+ # "The keyAgreement bit is asserted when the subject public key is used for key
+ # agreement. For example, when a Diffie-Hellman key is to be used for key
+ # management, then this bit is set."
+ #
+ # digest options: SHA512, SHA256, SHA1
+ #
+ def server_signing_profile(node)
+ {
+ "digest" => provider.ca.server_certificates.digest,
+ "extensions" => {
+ "keyUsage" => {
+ "usage" => ["digitalSignature", "keyEncipherment"]
+ },
+ "extendedKeyUsage" => {
+ "usage" => ["serverAuth", "clientAuth"]
+ },
+ "subjectAltName" => {
+ "ips" => [node.ip_address],
+ "dns_names" => dns_names_for_node(node)
+ }
+ }
+ }
+ end
+
+ #
+ # This is used when signing the main cert for the provider's domain
+ # with our own CA (for testing purposes). Typically, this cert would
+ # be purchased from a commercial CA, and not signed this way.
+ #
+ def domain_test_signing_profile
+ {
+ "digest" => "SHA256",
+ "extensions" => {
+ "keyUsage" => {
+ "usage" => ["digitalSignature", "keyEncipherment"]
+ },
+ "extendedKeyUsage" => {
+ "usage" => ["serverAuth"]
+ }
+ }
+ }
+ end
+
+ #
+ # This is used when signing a dummy client certificate that is only to be
+ # used for testing.
+ #
+ def client_test_signing_profile
+ {
+ "digest" => "SHA256",
+ "extensions" => {
+ "keyUsage" => {
+ "usage" => ["digitalSignature"]
+ },
+ "extendedKeyUsage" => {
+ "usage" => ["clientAuth"]
+ }
+ }
+ }
+ end
+
+ def dns_names_for_node(node)
+ names = [node.domain.internal, node.domain.full]
+ if node['dns'] && node.dns['aliases'] && node.dns.aliases.any?
+ names += node.dns.aliases
+ end
+ names.compact!
+ names.sort!
+ names.uniq!
+ return names
+ end
+
+ #
+ # For cert serial numbers, we need a non-colliding number less than 160 bits.
+ # md5 will do nicely, since there is no need for a secure hash, just a short one.
+ # (md5 is 128 bits)
+ #
+ def cert_serial_number(domain_name)
+ Digest::MD5.hexdigest("#{domain_name} -- #{Time.now}").to_i(16)
+ end
+
+ #
+ # for the random common name, we need a text string that will be unique across all certs.
+ # ruby 1.8 doesn't have a built-in uuid generator, or we would use SecureRandom.uuid
+ #
+ def random_common_name(domain_name)
+ cert_serial_number(domain_name).to_s(36)
+ end
+
+ # prints CertificateAuthority::DistinguishedName fields
+ def print_dn(dn)
+ fields = {}
+ [:common_name, :locality, :state, :country, :organization, :organizational_unit, :email_address].each do |attr|
+ fields[attr] = dn.send(attr) if dn.send(attr)
+ end
+ fields.inspect
+ end
+
+ ##
+ ## TIME HELPERS
+ ##
+ ## note: we use 'yesterday' instead of 'today', because times are in UTC, and some people on the planet
+ ## are behind UTC.
+ ##
+
+ def yesterday
+ t = Time.now - 24*24*60
+ Time.utc t.year, t.month, t.day
+ end
+
+ def yesterday_advance(string)
+ number, unit = string.split(' ')
+ unless ['years', 'months', 'days', 'hours', 'minutes'].include? unit
+ bail!("The time property '#{string}' is missing a unit (one of: years, months, days, hours, minutes).")
+ end
+ unless number.to_i.to_s == number
+ bail!("The time property '#{string}' is missing a number.")
+ end
+ yesterday.advance(unit.to_sym => number.to_i)
+ end
+
+end; end
diff --git a/lib/leap_cli/commands/clean.rb b/lib/leap_cli/commands/clean.rb
new file mode 100644
index 00000000..a9afff53
--- /dev/null
+++ b/lib/leap_cli/commands/clean.rb
@@ -0,0 +1,16 @@
+module LeapCli
+ module Commands
+
+ desc 'Removes all files generated with the "compile" command.'
+ command :clean do |c|
+ c.action do |global_options,options,args|
+ Dir.glob(path([:hiera, '*'])).each do |file|
+ remove_file! file
+ end
+ remove_file! path(:authorized_keys)
+ remove_file! path(:known_hosts)
+ end
+ end
+
+ end
+end \ No newline at end of file
diff --git a/lib/leap_cli/commands/compile.rb b/lib/leap_cli/commands/compile.rb
new file mode 100644
index 00000000..f9079279
--- /dev/null
+++ b/lib/leap_cli/commands/compile.rb
@@ -0,0 +1,531 @@
+require 'socket'
+
+module LeapCli
+ module Commands
+
+ desc "Compile generated files."
+ command [:compile, :c] do |c|
+ c.desc 'Compiles node configuration files into hiera files used for deployment.'
+ c.arg_name 'ENVIRONMENT', :optional => true
+ c.command :all do |all|
+ all.action do |global_options,options,args|
+ environment = args.first
+ compile_command(environment)
+ end
+ end
+
+ c.desc "Prints a DNS zone file for your provider."
+ c.command :zone do |zone|
+ zone.action do |global_options, options, args|
+ compile_command(nil)
+ compile_zone_file(global_options[:yes] || global_options[:force])
+ end
+ end
+
+ c.desc "Print entries suitable for an /etc/hosts file, useful for testing your provider."
+ c.command :hosts do |hosts|
+ hosts.action do |global_options, options, args|
+ compile_command(nil)
+ compile_hosts_file
+ end
+ end
+
+ c.desc "Compile provider.json bootstrap files for your provider."
+ c.command 'provider.json' do |provider|
+ provider.action do |global_options, options, args|
+ compile_command(nil)
+ compile_provider_json
+ end
+ end
+
+ c.desc "Prints a list of firewall rules. These rules are already "+
+ "implemented on each node, but you might want the list of all "+
+ "rules in case you also have a restrictive network firewall."
+ c.command :firewall do |zone|
+ zone.action do |global_options, options, args|
+ compile_command(nil)
+ compile_firewall
+ end
+ end
+
+ c.default_command :all
+ end
+
+ protected
+
+ def compile_command(environment)
+ if !LeapCli.leapfile.environment.nil? && !environment.nil? && environment != LeapCli.leapfile.environment
+ bail! "You cannot specify an ENVIRONMENT argument while the environment is pinned."
+ end
+ if environment
+ if manager.environment_names.include?(environment)
+ compile_hiera_files(manager.filter([environment]), false)
+ else
+ bail! "There is no environment named `#{environment}`."
+ end
+ else
+ clean_export = LeapCli.leapfile.environment.nil?
+ compile_hiera_files(manager.filter, clean_export)
+ end
+ if file_exists?(:static_web_readme)
+ compile_provider_json(environment)
+ end
+ end
+
+ #
+ # a "clean" export of secrets will also remove keys that are no longer used,
+ # but this should not be done if we are not examining all possible nodes.
+ #
+ def compile_hiera_files(nodes, clean_export)
+ update_certificates(nodes) # \ must come first so that output will
+ update_compiled_ssh_configs # / get included in compiled hiera files.
+ sanity_check(nodes)
+ manager.export_nodes(nodes)
+ manager.export_secrets(clean_export)
+ end
+
+ def update_compiled_ssh_configs
+ generate_monitor_ssh_keys
+ update_authorized_keys
+ update_known_hosts
+ end
+
+ def sanity_check(nodes)
+ # confirm that every node has a unique ip address
+ ips = {}
+ nodes.pick_fields('ip_address').each do |name, ip_address|
+ if ips.key?(ip_address)
+ bail! {
+ log(:fatal_error, "Every node must have its own IP address.") {
+ log "Nodes `#{name}` and `#{ips[ip_address]}` are both configured with `#{ip_address}`."
+ }
+ }
+ else
+ ips[ip_address] = name
+ end
+ end
+ # confirm that the IP address of this machine is not also used for a node.
+ Socket.ip_address_list.each do |addrinfo|
+ if !addrinfo.ipv4_private? && ips.key?(addrinfo.ip_address)
+ ip = addrinfo.ip_address
+ name = ips[ip]
+ bail! {
+ log(:fatal_error, "Something is very wrong. The `leap` command must only be run on your sysadmin machine, not on a provider node.") {
+ log "This machine has the same IP address (#{ip}) as node `#{name}`."
+ }
+ }
+ end
+ end
+ end
+
+ ##
+ ## SSH
+ ##
+
+ #
+ # generates a ssh key pair that is used only by remote monitors
+ # to connect to nodes and run certain allowed commands.
+ #
+ # every node has the public monitor key added to their authorized
+ # keys, and every monitor node has a copy of the private monitor key.
+ #
+ def generate_monitor_ssh_keys
+ priv_key_file = path(:monitor_priv_key)
+ pub_key_file = path(:monitor_pub_key)
+ unless file_exists?(priv_key_file, pub_key_file)
+ ensure_dir(File.dirname(priv_key_file))
+ ensure_dir(File.dirname(pub_key_file))
+ cmd = %(ssh-keygen -N '' -C 'monitor' -t rsa -b 4096 -f '%s') % priv_key_file
+ assert_run! cmd
+ if file_exists?(priv_key_file, pub_key_file)
+ log :created, priv_key_file
+ log :created, pub_key_file
+ else
+ log :failed, 'to create monitor ssh keys'
+ end
+ end
+ end
+
+ #
+ # Compiles the authorized keys file, which gets installed on every during init.
+ # Afterwards, puppet installs an authorized keys file that is generated differently
+ # (see authorized_keys() in macros.rb)
+ #
+ def update_authorized_keys
+ buffer = StringIO.new
+ keys = Dir.glob(path([:user_ssh, '*']))
+ if keys.empty?
+ bail! "You must have at least one public SSH user key configured in order to proceed. See `leap help add-user`."
+ end
+ if file_exists?(path(:monitor_pub_key))
+ keys << path(:monitor_pub_key)
+ end
+ keys.sort.each do |keyfile|
+ ssh_type, ssh_key = File.read(keyfile).strip.split(" ")
+ buffer << ssh_type
+ buffer << " "
+ buffer << ssh_key
+ buffer << " "
+ buffer << Path.relative_path(keyfile)
+ buffer << "\n"
+ end
+ write_file!(:authorized_keys, buffer.string)
+ end
+
+ #
+ # generates the known_hosts file.
+ #
+ # we do a 'late' binding on the hostnames and ip part of the ssh pub key record in order to allow
+ # for the possibility that the hostnames or ip has changed in the node configuration.
+ #
+ def update_known_hosts
+ buffer = StringIO.new
+ buffer << "#\n"
+ buffer << "# This file is automatically generated by the command `leap`. You should NOT modify this file.\n"
+ buffer << "# Instead, rerun `leap node init` on whatever node is causing SSH problems.\n"
+ buffer << "#\n"
+ manager.nodes.keys.sort.each do |node_name|
+ node = manager.nodes[node_name]
+ hostnames = [node.name, node.domain.internal, node.domain.full, node.ip_address].join(',')
+ pub_key = read_file([:node_ssh_pub_key,node.name])
+ if pub_key
+ buffer << [hostnames, pub_key].join(' ')
+ buffer << "\n"
+ end
+ end
+ write_file!(:known_hosts, buffer.string)
+ end
+
+ ##
+ ## provider.json
+ ##
+
+ #
+ # generates static provider.json files that can put into place
+ # (e.g. https://domain/provider.json) for the cases where the
+ # webapp domain does not match the provider's domain.
+ #
+ def compile_provider_json(environments=nil)
+ webapp_nodes = manager.nodes[:services => 'webapp']
+ write_file!(:static_web_readme, STATIC_WEB_README)
+ environments ||= manager.environment_names
+ environments.each do |env|
+ node = webapp_nodes[:environment => env].values.first
+ if node
+ env ||= 'default'
+ write_file!(
+ [:static_web_provider_json, env],
+ node['definition_files']['provider']
+ )
+ write_file!(
+ [:static_web_htaccess, env],
+ HTACCESS_FILE % {:min_version => manager.env(env).provider.client_version['min']}
+ )
+ end
+ end
+ end
+
+ HTACCESS_FILE = %[
+<Files provider.json>
+ Header set X-Minimum-Client-Version %{min_version}
+</Files>
+]
+
+ STATIC_WEB_README = %[
+This directory contains statically rendered copies of the `provider.json` file
+used by the client to "bootstrap" configure itself for use with your service
+provider.
+
+There is a separate provider.json file for each environment, although you
+should only need 'production/provider.json' or, if you have no environments
+configured, 'default/provider.json'.
+
+To clarify, this is the public `provider.json` file used by the client, not the
+`provider.json` file that is used to configure the provider.
+
+The provider.json file must be available at `https://domain/provider.json`
+(unless this provider is included in the list of providers which are pre-
+seeded in client).
+
+This provider.json file can be served correctly in one of three ways:
+
+(1) If the property webapp.domain is not configured, then the web app will be
+ installed at https://domain/ and it will handle serving the provider.json file.
+
+(2) If one or more nodes have the 'static' service configured for the provider's
+ domain, then these 'static' nodes will correctly serve provider.json.
+
+(3) Otherwise, you must copy the provider.json file to your web
+ server and make it available at '/provider.json'. The example htaccess
+ file shows what header options should be sent by the web server
+ with the response.
+
+This directory is needed for method (3), but not for methods (1) or (2).
+
+This directory has been created by the command `leap compile provider.json`.
+Once created, it will be kept up to date everytime you compile. You may safely
+remove this directory if you don't use it.
+]
+
+ ##
+ ##
+ ## ZONE FILE
+ ##
+
+ def relative_hostname(fqdn, provider)
+ @domain_regexp ||= /\.?#{Regexp.escape(provider.domain)}$/
+ fqdn.sub(@domain_regexp, '')
+ end
+
+ #
+ # serial is any number less than 2^32 (4294967296)
+ #
+ def compile_zone_file(force=false)
+ # note: we use the default provider for all nodes, because we use it
+ # to generate hostnames that are relative to the default domain.
+ provider = manager.env('default').provider
+ hosts_seen = {}
+ lines = []
+
+ #
+ # header
+ #
+ lines << ZONE_HEADER % {
+ :domain => provider.domain,
+ :ns => provider.domain,
+ :contact => provider.contacts.default.first.sub('@','.'),
+ :serial => generate_zone_serial
+ }
+
+ #
+ # common records
+ #
+ lines << ORIGIN_HEADER
+ # 'A' records for primary domain
+ manager.nodes[:environment => '!local'].each_node do |node|
+ if node.dns['aliases'] && node.dns.aliases.include?(provider.domain)
+ lines << ["@", "IN A #{node.ip_address}"]
+ end
+ end
+
+ # NS records
+ if provider['dns'] && provider.dns['nameservers']
+ unless provider.dns.nameservers.is_a?(Array)
+ # TODO: remove me once we have JSON schema working
+ bail! {log :error, 'dns.nameservers must be an array' }
+ end
+ provider.dns.nameservers.each do |ns|
+ lines << ["@", "IN NS #{ns}."]
+ end
+ elsif !force
+ log :warning, "Property dns.nameservers is not configured in provider.json." do
+ log "This will produce a zone file without any NS records."
+ log "Use --force to skip this warning."
+ end
+ return unless agree("Continue? ")
+ end
+
+ # environment records
+ manager.environment_names.each do |env|
+ next if env == 'local'
+ nodes = manager.nodes[:environment => env]
+ next unless nodes.any?
+ spf = nil
+ dkim = nil
+ lines << ENV_HEADER % (env.nil? ? 'default' : env)
+ nodes.each_node do |node|
+ if node.dns.public
+ lines << [relative_hostname(node.domain.full, provider), "IN A #{node.ip_address}"]
+ end
+ if node.dns['aliases']
+ node.dns.aliases.each do |host_alias|
+ if host_alias != node.domain.full && host_alias != provider.domain
+ lines << [relative_hostname(host_alias, provider), "IN A #{node.ip_address}"]
+ end
+ end
+ end
+ if node.services.include? 'mx'
+ mx_domain = relative_hostname(node.domain.full_suffix, provider)
+ lines << [mx_domain, "IN MX 10 #{relative_hostname(node.domain.full, provider)}"]
+ spf ||= [mx_domain, spf_record(node)]
+ dkim ||= dkim_record(node, provider)
+ end
+ end
+ lines << spf if spf
+ lines << dkim if dkim
+ end
+
+ # print the lines
+ max_width = lines.inject(0) {|max, line| line.is_a?(Array) ? [max, line[0].length].max : max}
+ max_width = [max_width, 24].min
+ lines.each do |host, line|
+ if line.nil?
+ puts(host)
+ else
+ host = '@' if host == ''
+ puts("%-#{max_width}s %s" % [host, line])
+ end
+ end
+ end
+
+ #
+ # outputs entries suitable for an /etc/hosts file
+ #
+ def compile_hosts_file
+ manager.environment_names.each do |env|
+ nodes = manager.nodes[:environment => env]
+ next unless nodes.any?
+ puts
+ puts "## environment '#{env || 'default'}'"
+ nodes.each do |name, node|
+ puts "%s %s" % [
+ node.ip_address,
+ [name, node.get('domain.full'), node.get('dns.aliases')].compact.join(' ')
+ ]
+ end
+ end
+ end
+
+ private
+
+ #
+ # allow mail from any mx node, plus the webapp nodes.
+ #
+ # TODO: ipv6
+ #
+ def spf_record(node)
+ ips = node.nodes_like_me['services' => 'webapp'].values.collect {|n|
+ "ip4:" + n.ip_address
+ }
+ # TXT strings may not be longer than 255 characters, although
+ # you can chain multiple strings together.
+ strings = "v=spf1 MX #{ips.join(' ')} -all".scan(/.{1,255}/).join('" "')
+ %(IN TXT "#{strings}")
+ end
+
+ #
+ # for example:
+ #
+ # selector._domainkey IN TXT "v=DKIM1;h=sha256;k=rsa;s=email;p=MIGfMA0GCSq...GSIb3DQ"
+ #
+ # specification: http://dkim.org/specs/rfc4871-dkimbase.html#rfc.section.7.4
+ #
+ def dkim_record(node, provider)
+ # PEM encoded public key (base64), without the ---PUBLIC KEY--- armor parts.
+ assert_files_exist! :dkim_pub_key
+ dkim_pub_key = Path.named_path(:dkim_pub_key)
+ public_key = File.readlines(dkim_pub_key).grep(/^[^\-]+/).join
+
+ host = relative_hostname(
+ node.mx.dkim.selector + "._domainkey." + node.domain.full_suffix,
+ provider)
+
+ attrs = [
+ "v=DKIM1",
+ "h=sha256",
+ "k=rsa",
+ "s=email",
+ "p=" + public_key
+ ]
+
+ return [host, "IN TXT " + txt_wrap(attrs.join(';'))]
+ end
+
+ #
+ # DNS TXT records cannot be longer than 255 characters.
+ #
+ # However, multiple responses will be concatenated together.
+ # It looks like this:
+ #
+ # IN TXT "v=spf1 .... first" "second string..."
+ #
+ def txt_wrap(str)
+ '"' + str.scan(/.{1,255}/).join('" "') + '"'
+ end
+
+ #
+ # For zone serial number, we want something that will be
+ # different each time you deploy but also will be greater
+ # than any prior likely serial that was prefixed by the
+ # year, such as 2016040600.
+ #
+ # so, we use time_t of right now, modified with first
+ # digit incremented by one.
+ #
+ # this will work until Time.at(2**32 - 1_000_000_000)
+ # aka 2074-05-31 04:41:36 UTC.
+ #
+ def generate_zone_serial
+ Time.now.utc.to_i + 1_000_000_000
+ end
+
+ ENV_HEADER = %[
+;;
+;; ENVIRONMENT %s
+;;
+
+]
+
+ ZONE_HEADER = %[
+;;
+;; BIND data file for %{domain}
+;;
+
+$TTL 600
+$ORIGIN %{domain}.
+
+@ IN SOA %{ns}. %{contact}. (
+ %{serial} ; serial
+ 7200 ; refresh ( 24 hours)
+ 3600 ; retry ( 2 hours)
+ 1209600 ; expire (1000 hours)
+ 600 ) ; minimum ( 2 days)
+;
+]
+
+ ORIGIN_HEADER = %[
+;;
+;; ZONE ORIGIN
+;;
+
+]
+
+ ##
+ ## FIREWALL
+ ##
+
+ public
+
+ def compile_firewall
+ manager.nodes.each_node(&:evaluate)
+
+ rules = [["ALLOW TO", "PORTS", "ALLOW FROM"]]
+ manager.nodes[:environment => '!local'].values.each do |node|
+ next unless node['firewall']
+ node.firewall.each do |name, rule|
+ if rule.is_a? Hash
+ rules << add_rule(rule)
+ elsif rule.is_a? Array
+ rule.each do |r|
+ rules << add_rule(r)
+ end
+ end
+ end
+ end
+
+ max_to = rules.inject(0) {|max, r| [max, r[0].length].max}
+ max_port = rules.inject(0) {|max, r| [max, r[1].length].max}
+ max_from = rules.inject(0) {|max, r| [max, r[2].length].max}
+ rules.each do |rule|
+ puts "%-#{max_to}s %-#{max_port}s %-#{max_from}s" % rule
+ end
+ end
+
+ private
+
+ def add_rule(rule)
+ [rule["to"], [rule["port"]].compact.join(','), rule["from"]]
+ end
+
+ end
+end \ No newline at end of file
diff --git a/lib/leap_cli/commands/db.rb b/lib/leap_cli/commands/db.rb
new file mode 100644
index 00000000..5307ac4d
--- /dev/null
+++ b/lib/leap_cli/commands/db.rb
@@ -0,0 +1,86 @@
+module LeapCli; module Commands
+
+ desc 'Database commands.'
+ command :db do |db|
+ db.desc 'Destroy one or more databases. If present, limit to FILTER nodes. For example `leap db destroy --db sessions,tokens testing`.'
+ db.arg_name 'FILTER', :optional => true
+ db.command :destroy do |destroy|
+ destroy.flag :db, :arg_name => "DATABASES", :desc => 'Comma separated list of databases to destroy (no space). Use "--db all" to destroy all databases.', :optional => true
+ destroy.flag :user, :arg_name => "USERS", :desc => 'Comma separated list of usernames. The storage databases for these user(s) will be destroyed.', :optional => true
+ destroy.action do |global_options,options,args|
+ dbs = (options[:db]||"").split(',')
+ users = (options[:user]||"").split(',')
+ if dbs.empty? && users.empty?
+ bail!('Either --db or --user is required.')
+ end
+ nodes = manager.filter(args)
+ if nodes.any?
+ nodes = nodes[:services => 'couchdb']
+ end
+ unless nodes.any?
+ bail! 'No db nodes selected.'
+ end
+ if users.any?
+ unless global_options[:yes]
+ say 'You are about to permanently destroy user databases for [%s] for nodes [%s].' % [users.join(', '), nodes.keys.join(', ')]
+ bail! unless agree("Continue? ")
+ end
+ destroy_user_dbs(nodes, users)
+ elsif dbs.any?
+ unless global_options[:yes]
+ if dbs.include?('all')
+ say 'You are about to permanently destroy all database data for nodes [%s].' % nodes.keys.join(', ')
+ else
+ say 'You are about to permanently destroy databases [%s] for nodes [%s].' % [dbs.join(', '), nodes.keys.join(', ')]
+ end
+ bail! unless agree("Continue? ")
+ end
+ if dbs.include?('all')
+ destroy_all_dbs(nodes)
+ else
+ destroy_dbs(nodes, dbs)
+ end
+ say 'You must run `leap deploy` in order to create the databases again.'
+ end
+ end
+ end
+ end
+
+ private
+
+ def destroy_all_dbs(nodes)
+ ssh_connect(nodes) do |ssh|
+ ssh.run('/etc/init.d/bigcouch stop && test ! -z "$(ls /opt/bigcouch/var/lib/ 2> /dev/null)" && rm -r /opt/bigcouch/var/lib/* && echo "All DBs destroyed" || echo "DBs already destroyed"')
+ end
+ end
+
+ def destroy_dbs(nodes, dbs)
+ nodes.each_node do |node|
+ ssh_connect(node) do |ssh|
+ dbs.each do |db|
+ ssh.run(DESTROY_DB_COMMAND % {:db => db})
+ end
+ end
+ end
+ end
+
+ def destroy_user_dbs(nodes, users)
+ nodes.each_node do |node|
+ ssh_connect(node) do |ssh|
+ users.each do |user|
+ ssh.run(DESTROY_USER_DB_COMMAND % {:user => user})
+ end
+ end
+ end
+ end
+
+ DESTROY_DB_COMMAND = %{
+if [ 200 = `curl -ns -w "%%{http_code}" -X GET "127.0.0.1:5984/%{db}" -o /dev/null` ]; then
+ echo "Result from DELETE /%{db}:" `curl -ns -X DELETE "127.0.0.1:5984/%{db}"`;
+else
+ echo "Skipping db '%{db}': it does not exist or has already been deleted.";
+fi
+}
+
+ DESTROY_USER_DB_COMMAND = %{/srv/leap/couchdb/scripts/destroy-user-db --username %{user}}
+end; end
diff --git a/lib/leap_cli/commands/deploy.rb b/lib/leap_cli/commands/deploy.rb
new file mode 100644
index 00000000..9dd190ab
--- /dev/null
+++ b/lib/leap_cli/commands/deploy.rb
@@ -0,0 +1,374 @@
+require 'etc'
+
+module LeapCli
+ module Commands
+
+ desc 'Apply recipes to a node or set of nodes.'
+ long_desc 'The FILTER can be the name of a node, service, or tag.'
+ arg_name 'FILTER'
+ command [:deploy, :d] do |c|
+
+ c.switch :fast, :desc => 'Makes the deploy command faster by skipping some slow steps. A "fast" deploy can be used safely if you recently completed a normal deploy.',
+ :negatable => false
+
+ c.switch :sync, :desc => "Sync files, but don't actually apply recipes.", :negatable => false
+
+ c.switch :force, :desc => 'Deploy even if there is a lockfile.', :negatable => false
+
+ c.switch :downgrade, :desc => 'Allows deploy to run with an older platform version.', :negatable => false
+
+ c.switch :dev, :desc => "Development mode: don't run 'git submodule update' before deploy.", :negatable => false
+
+ c.flag :tags, :desc => 'Specify tags to pass through to puppet (overriding the default).',
+ :arg_name => 'TAG[,TAG]'
+
+ c.flag :port, :desc => 'Override the default SSH port.',
+ :arg_name => 'PORT'
+
+ c.flag :ip, :desc => 'Override the default SSH IP address.',
+ :arg_name => 'IPADDRESS'
+
+ c.action do |global,options,args|
+
+ if options[:dev] != true
+ init_submodules
+ end
+
+ nodes = manager.filter!(args, :disabled => false)
+ if nodes.size > 1
+ say "Deploying to these nodes: #{nodes.keys.join(', ')}"
+ if !global[:yes] && !agree("Continue? ")
+ quit! "OK. Bye."
+ end
+ end
+
+ environments = nodes.field('environment').uniq
+ if environments.empty?
+ environments = [nil]
+ end
+ environments.each do |env|
+ check_platform_pinning(env, global)
+ end
+
+ # compile hiera files for all the nodes in every environment that is
+ # being deployed and only those environments.
+ compile_hiera_files(manager.filter(environments), false)
+
+ ssh_connect(nodes, connect_options(options)) do |ssh|
+ ssh.leap.log :checking, 'node' do
+ ssh.leap.check_for_no_deploy
+ ssh.leap.assert_initialized
+ end
+ ssh.leap.log :synching, "configuration files" do
+ sync_hiera_config(ssh)
+ sync_support_files(ssh)
+ end
+ ssh.leap.log :synching, "puppet manifests" do
+ sync_puppet_files(ssh)
+ end
+ unless options[:sync]
+ ssh.leap.log :applying, "puppet" do
+ ssh.puppet.apply(:verbosity => [LeapCli.log_level,5].min,
+ :tags => tags(options),
+ :force => options[:force],
+ :info => deploy_info,
+ :downgrade => options[:downgrade]
+ )
+ end
+ end
+ end
+ if !Util.exit_status.nil? && Util.exit_status != 0
+ log :warning, "puppet did not finish successfully."
+ end
+ end
+ end
+
+ desc 'Display recent deployment history for a set of nodes.'
+ long_desc 'The FILTER can be the name of a node, service, or tag.'
+ arg_name 'FILTER'
+ command [:history, :h] do |c|
+ c.flag :port, :desc => 'Override the default SSH port.',
+ :arg_name => 'PORT'
+ c.flag :ip, :desc => 'Override the default SSH IP address.',
+ :arg_name => 'IPADDRESS'
+ c.switch :last, :desc => 'Show last deploy only',
+ :negatable => false
+ c.action do |global,options,args|
+ if options[:last] == true
+ lines = 1
+ else
+ lines = 10
+ end
+ nodes = manager.filter!(args)
+ ssh_connect(nodes, connect_options(options)) do |ssh|
+ ssh.leap.history(lines)
+ end
+ end
+ end
+
+ private
+
+ def forcible_prompt(forced, msg, prompt)
+ say(msg)
+ if forced
+ log :warning, "continuing anyway because of --force"
+ else
+ say "hint: use --force to skip this prompt."
+ quit!("OK. Bye.") unless agree(prompt)
+ end
+ end
+
+ #
+ # The currently activated provider.json could have loaded some pinning
+ # information for the platform. If this is the case, refuse to deploy
+ # if there is a mismatch.
+ #
+ # For example:
+ #
+ # "platform": {
+ # "branch": "develop"
+ # "version": "1.0..99"
+ # "commit": "e1d6280e0a8c565b7fb1a4ed3969ea6fea31a5e2..HEAD"
+ # }
+ #
+ def check_platform_pinning(environment, global_options)
+ provider = manager.env(environment).provider
+ return unless provider['platform']
+
+ if environment.nil? || environment == 'default'
+ provider_json = 'provider.json'
+ else
+ provider_json = 'provider.' + environment + '.json'
+ end
+
+ # can we have json schema verification already?
+ unless provider.platform.is_a? Hash
+ bail!('`platform` attribute in #{provider_json} must be a hash (was %s).' % provider.platform.inspect)
+ end
+
+ # check version
+ if provider.platform['version']
+ if !Leap::Platform.version_in_range?(provider.platform.version)
+ forcible_prompt(
+ global_options[:force],
+ "The platform is pinned to a version range of '#{provider.platform.version}' "+
+ "by the `platform.version` property in #{provider_json}, but the platform "+
+ "(#{Path.platform}) has version #{Leap::Platform.version}.",
+ "Do you really want to deploy from the wrong version? "
+ )
+ end
+ end
+
+ # check branch
+ if provider.platform['branch']
+ if !is_git_directory?(Path.platform)
+ forcible_prompt(
+ global_options[:force],
+ "The platform is pinned to a particular branch by the `platform.branch` property "+
+ "in #{provider_json}, but the platform directory (#{Path.platform}) is not a git repository.",
+ "Do you really want to deploy anyway? "
+ )
+ end
+ unless provider.platform.branch == current_git_branch(Path.platform)
+ forcible_prompt(
+ global_options[:force],
+ "The platform is pinned to branch '#{provider.platform.branch}' by the `platform.branch` property "+
+ "in #{provider_json}, but the current branch is '#{current_git_branch(Path.platform)}' " +
+ "(for directory '#{Path.platform}')",
+ "Do you really want to deploy from the wrong branch? "
+ )
+ end
+ end
+
+ # check commit
+ if provider.platform['commit']
+ if !is_git_directory?(Path.platform)
+ forcible_prompt(
+ global_options[:force],
+ "The platform is pinned to a particular commit range by the `platform.commit` property "+
+ "in #{provider_json}, but the platform directory (#{Path.platform}) is not a git repository.",
+ "Do you really want to deploy anyway? "
+ )
+ end
+ current_commit = current_git_commit(Path.platform)
+ Dir.chdir(Path.platform) do
+ commit_range = assert_run!("git log --pretty='format:%H' '#{provider.platform.commit}'",
+ "The platform is pinned to a particular commit range by the `platform.commit` property "+
+ "in #{provider_json}, but git was not able to find commits in the range specified "+
+ "(#{provider.platform.commit}).")
+ commit_range = commit_range.split("\n")
+ if !commit_range.include?(current_commit) &&
+ provider.platform.commit.split('..').first != current_commit
+ forcible_prompt(
+ global_options[:force],
+ "The platform is pinned via the `platform.commit` property in #{provider_json} " +
+ "to a commit in the range #{provider.platform.commit}, but the current HEAD " +
+ "(#{current_commit}) is not in that range.",
+ "Do you really want to deploy from the wrong commit? "
+ )
+ end
+ end
+ end
+ end
+
+ def sync_hiera_config(ssh)
+ ssh.rsync.update do |server|
+ node = manager.node(server.host)
+ hiera_file = Path.relative_path([:hiera, node.name])
+ ssh.leap.log hiera_file + ' -> ' + node.name + ':' + Leap::Platform.hiera_path
+ {
+ :source => hiera_file,
+ :dest => Leap::Platform.hiera_path,
+ :flags => "-rltp --chmod=u+rX,go-rwx"
+ }
+ end
+ end
+
+ #
+ # sync various support files.
+ #
+ def sync_support_files(ssh)
+ dest_dir = Leap::Platform.files_dir
+ custom_files = build_custom_file_list
+ ssh.rsync.update do |server|
+ node = manager.node(server.host)
+ files_to_sync = node.file_paths.collect {|path| Path.relative_path(path, Path.provider) }
+ files_to_sync += custom_files
+ if files_to_sync.any?
+ ssh.leap.log(files_to_sync.join(', ') + ' -> ' + node.name + ':' + dest_dir)
+ {
+ :chdir => Path.named_path(:files_dir),
+ :source => ".",
+ :dest => dest_dir,
+ :excludes => "*",
+ :includes => calculate_includes_from_files(files_to_sync, '/files'),
+ :flags => "-rltp --chmod=u+rX,go-rwx --relative --delete --delete-excluded --copy-links"
+ }
+ else
+ nil
+ end
+ end
+ end
+
+ def sync_puppet_files(ssh)
+ ssh.rsync.update do |server|
+ ssh.leap.log(Path.platform + '/[bin,tests,puppet] -> ' + server.host + ':' + Leap::Platform.leap_dir)
+ {
+ :dest => Leap::Platform.leap_dir,
+ :source => '.',
+ :chdir => Path.platform,
+ :excludes => '*',
+ :includes => ['/bin', '/bin/**', '/puppet', '/puppet/**', '/tests', '/tests/**'],
+ :flags => "-rlt --relative --delete --copy-links"
+ }
+ end
+ end
+
+ #
+ # ensure submodules are up to date, if the platform is a git
+ # repository.
+ #
+ def init_submodules
+ return unless is_git_directory?(Path.platform)
+ Dir.chdir Path.platform do
+ assert_run! "git submodule sync"
+ statuses = assert_run! "git submodule status"
+ statuses.strip.split("\n").each do |status_line|
+ if status_line =~ /^[\+-]/
+ submodule = status_line.split(' ')[1]
+ log "Updating submodule #{submodule}"
+ assert_run! "git submodule update --init #{submodule}"
+ end
+ end
+ end
+ end
+
+ #
+ # converts an array of file paths into an array
+ # suitable for --include of rsync
+ #
+ # if set, `prefix` is stripped off.
+ #
+ def calculate_includes_from_files(files, prefix=nil)
+ return nil unless files and files.any?
+
+ # prepend '/' (kind of like ^ for rsync)
+ includes = files.collect {|file| file =~ /^\// ? file : '/' + file }
+
+ # include all sub files of specified directories
+ includes.size.times do |i|
+ if includes[i] =~ /\/$/
+ includes << includes[i] + '**'
+ end
+ end
+
+ # include all parent directories (required because of --exclude '*')
+ includes.size.times do |i|
+ path = File.dirname(includes[i])
+ while(path != '/')
+ includes << path unless includes.include?(path)
+ path = File.dirname(path)
+ end
+ end
+
+ if prefix
+ includes.map! {|path| path.sub(/^#{Regexp.escape(prefix)}\//, '/')}
+ end
+
+ return includes
+ end
+
+ def tags(options)
+ if options[:tags]
+ tags = options[:tags].split(',')
+ else
+ tags = Leap::Platform.default_puppet_tags.dup
+ end
+ tags << 'leap_slow' unless options[:fast]
+ tags.join(',')
+ end
+
+ #
+ # a provider might have various customization files that should be sync'ed to the server.
+ # this method builds that list of files to sync.
+ #
+ def build_custom_file_list
+ custom_files = []
+ Leap::Platform.paths.keys.grep(/^custom_/).each do |path|
+ if file_exists?(path)
+ relative_path = Path.relative_path(path, Path.provider)
+ if dir_exists?(path)
+ custom_files << relative_path + '/' # rsync needs trailing slash
+ else
+ custom_files << relative_path
+ end
+ end
+ end
+ return custom_files
+ end
+
+ def deploy_info
+ info = []
+ info << "user: %s" % Etc.getpwuid(Process.euid).name
+ if is_git_directory?(Path.platform) && current_git_branch(Path.platform) != 'master'
+ info << "platform: %s (%s %s)" % [
+ Leap::Platform.version,
+ current_git_branch(Path.platform),
+ current_git_commit(Path.platform)[0..4]
+ ]
+ else
+ info << "platform: %s" % Leap::Platform.version
+ end
+ if is_git_directory?(LEAP_CLI_BASE_DIR)
+ info << "leap_cli: %s (%s %s)" % [
+ LeapCli::VERSION,
+ current_git_branch(LEAP_CLI_BASE_DIR),
+ current_git_commit(LEAP_CLI_BASE_DIR)[0..4]
+ ]
+ else
+ info << "leap_cli: %s" % LeapCli::VERSION
+ end
+ info.join(', ')
+ end
+ end
+end
diff --git a/lib/leap_cli/commands/env.rb b/lib/leap_cli/commands/env.rb
new file mode 100644
index 00000000..80be2174
--- /dev/null
+++ b/lib/leap_cli/commands/env.rb
@@ -0,0 +1,76 @@
+module LeapCli
+ module Commands
+
+ desc "Manipulate and query environment information."
+ long_desc "The 'environment' node property can be used to isolate sets of nodes into entirely separate environments. "+
+ "A node in one environment will never interact with a node from another environment. "+
+ "Environment pinning works by modifying your ~/.leaprc file and is dependent on the "+
+ "absolute file path of your provider directory (pins don't apply if you move the directory)"
+ command [:env, :e] do |c|
+ c.desc "List the available environments. The pinned environment, if any, will be marked with '*'. Will also set the pin if run with an environment argument."
+ c.arg_name 'ENVIRONMENT', :optional => true
+ c.command :ls do |ls|
+ ls.action do |global_options, options, args|
+ environment = get_env_from_args(args)
+ if environment
+ pin(environment)
+ LeapCli.leapfile.load
+ end
+ print_envs
+ end
+ end
+
+ c.desc 'Pin the environment to ENVIRONMENT. All subsequent commands will only apply to nodes in this environment.'
+ c.arg_name 'ENVIRONMENT'
+ c.command :pin do |pin|
+ pin.action do |global_options,options,args|
+ environment = get_env_from_args(args)
+ if environment
+ pin(environment)
+ else
+ bail! "There is no environment `#{environment}`"
+ end
+ end
+ end
+
+ c.desc "Unpin the environment. All subsequent commands will apply to all nodes."
+ c.command :unpin do |unpin|
+ unpin.action do |global_options, options, args|
+ LeapCli.leapfile.unset('environment')
+ log 0, :saved, "~/.leaprc, removing environment property."
+ end
+ end
+
+ c.default_command :ls
+ end
+
+ protected
+
+ def get_env_from_args(args)
+ environment = args.first
+ if environment == 'default' || (environment && manager.environment_names.include?(environment))
+ return environment
+ else
+ return nil
+ end
+ end
+
+ def pin(environment)
+ LeapCli.leapfile.set('environment', environment)
+ log 0, :saved, "~/.leaprc with environment set to #{environment}."
+ end
+
+ def print_envs
+ envs = ["default"] + manager.environment_names.compact.sort
+ envs.each do |env|
+ if env
+ if LeapCli.leapfile.environment == env
+ puts "* #{env}"
+ else
+ puts " #{env}"
+ end
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/leap_cli/commands/facts.rb b/lib/leap_cli/commands/facts.rb
new file mode 100644
index 00000000..11329ccc
--- /dev/null
+++ b/lib/leap_cli/commands/facts.rb
@@ -0,0 +1,100 @@
+#
+# Gather facter facts
+#
+
+module LeapCli; module Commands
+
+ desc 'Gather information on nodes.'
+ command :facts do |facts|
+ facts.desc 'Query servers to update facts.json.'
+ facts.long_desc "Queries every node included in FILTER and saves the important information to facts.json"
+ facts.arg_name 'FILTER'
+ facts.command :update do |update|
+ update.action do |global_options,options,args|
+ update_facts(global_options, options, args)
+ end
+ end
+ end
+
+ protected
+
+ def facter_cmd
+ 'facter --json ' + Leap::Platform.facts.join(' ')
+ end
+
+ def remove_node_facts(name)
+ if file_exists?(:facts)
+ update_facts_file({name => nil})
+ end
+ end
+
+ def update_node_facts(name, facts)
+ update_facts_file({name => facts})
+ end
+
+ def rename_node_facts(old_name, new_name)
+ if file_exists?(:facts)
+ facts = JSON.parse(read_file(:facts) || {})
+ facts[new_name] = facts[old_name]
+ facts[old_name] = nil
+ update_facts_file(facts, true)
+ end
+ end
+
+ #
+ # if overwrite = true, then ignore existing facts.json.
+ #
+ def update_facts_file(new_facts, overwrite=false)
+ replace_file!(:facts) do |content|
+ if overwrite || content.nil? || content.empty?
+ old_facts = {}
+ else
+ old_facts = manager.facts
+ end
+ facts = old_facts.merge(new_facts)
+ facts.each do |name, value|
+ if value.is_a? String
+ if value == ""
+ value = nil
+ else
+ value = JSON.parse(value) rescue JSON::ParserError
+ end
+ end
+ if value.is_a? Hash
+ value.delete_if {|key,v| v.nil?}
+ end
+ facts[name] = value
+ end
+ facts.delete_if do |name, value|
+ value.nil? || value.empty?
+ end
+ if facts.empty?
+ "{}\n"
+ else
+ JSON.sorted_generate(facts) + "\n"
+ end
+ end
+ end
+
+ private
+
+ def update_facts(global_options, options, args)
+ nodes = manager.filter(args, :local => false, :disabled => false)
+ new_facts = {}
+ ssh_connect(nodes) do |ssh|
+ ssh.leap.run_with_progress(facter_cmd) do |response|
+ node = manager.node(response[:host])
+ if node
+ new_facts[node.name] = response[:data].strip
+ else
+ log :warning, 'Could not find node for hostname %s' % response[:host]
+ end
+ end
+ end
+ # only overwrite the entire facts file if and only if we are gathering facts
+ # for all nodes in all environments.
+ overwrite_existing = args.empty? && LeapCli.leapfile.environment.nil?
+ update_facts_file(new_facts, overwrite_existing)
+ end
+
+end; end \ No newline at end of file
diff --git a/lib/leap_cli/commands/info.rb b/lib/leap_cli/commands/info.rb
new file mode 100644
index 00000000..52225a94
--- /dev/null
+++ b/lib/leap_cli/commands/info.rb
@@ -0,0 +1,15 @@
+module LeapCli; module Commands
+
+ desc 'Prints information regarding facts, history, and running processes for a node or nodes.'
+ long_desc 'The FILTER can be the name of a node, service, or tag.'
+ arg_name 'FILTER'
+ command [:info] do |c|
+ c.action do |global,options,args|
+ nodes = manager.filter!(args)
+ ssh_connect(nodes, connect_options(options)) do |ssh|
+ ssh.leap.debug
+ end
+ end
+ end
+
+end; end
diff --git a/lib/leap_cli/commands/inspect.rb b/lib/leap_cli/commands/inspect.rb
new file mode 100644
index 00000000..20654fa7
--- /dev/null
+++ b/lib/leap_cli/commands/inspect.rb
@@ -0,0 +1,144 @@
+module LeapCli; module Commands
+
+ desc 'Prints details about a file. Alternately, the argument FILE can be the name of a node, service or tag.'
+ arg_name 'FILE'
+ command [:inspect, :i] do |c|
+ c.switch 'base', :desc => 'Inspect the FILE from the provider_base (i.e. without local inheritance).', :negatable => false
+ c.action do |global_options,options,args|
+ object = args.first
+ assert! object, 'A file path or node/service/tag name is required'
+ method = inspection_method(object)
+ if method && defined?(method)
+ self.send(method, object, options)
+ else
+ log "Sorry, I don't know how to inspect that."
+ end
+ end
+ end
+
+ private
+
+ FTYPE_MAP = {
+ "PEM certificate" => :inspect_x509_cert,
+ "PEM RSA private key" => :inspect_x509_key,
+ "OpenSSH RSA public key" => :inspect_ssh_pub_key,
+ "PEM certificate request" => :inspect_x509_csr
+ }
+
+ def inspection_method(object)
+ if File.exists?(object)
+ ftype = `file #{object}`.split(':').last.strip
+ log 2, "file is of type '#{ftype}'"
+ if FTYPE_MAP[ftype]
+ FTYPE_MAP[ftype]
+ elsif File.extname(object) == ".json"
+ full_path = File.expand_path(object, Dir.pwd)
+ if path_match?(:node_config, full_path)
+ :inspect_node
+ elsif path_match?(:service_config, full_path)
+ :inspect_service
+ elsif path_match?(:tag_config, full_path)
+ :inspect_tag
+ elsif path_match?(:provider_config, full_path) || path_match?(:provider_env_config, full_path)
+ :inspect_provider
+ elsif path_match?(:common_config, full_path)
+ :inspect_common
+ else
+ nil
+ end
+ end
+ elsif manager.nodes[object]
+ :inspect_node
+ elsif manager.services[object]
+ :inspect_service
+ elsif manager.tags[object]
+ :inspect_tag
+ elsif object == "common"
+ :inspect_common
+ elsif object == "provider"
+ :inspect_provider
+ else
+ nil
+ end
+ end
+
+ #
+ # inspectors
+ #
+
+ def inspect_x509_key(file_path, options)
+ assert_bin! 'openssl'
+ puts assert_run! 'openssl rsa -in %s -text -check' % file_path
+ end
+
+ def inspect_x509_cert(file_path, options)
+ assert_bin! 'openssl'
+ puts assert_run! 'openssl x509 -in %s -text -noout' % file_path
+ log 0, :"SHA256 fingerprint", X509.fingerprint("SHA256", file_path)
+ end
+
+ def inspect_x509_csr(file_path, options)
+ assert_bin! 'openssl'
+ puts assert_run! 'openssl req -text -noout -verify -in %s' % file_path
+ end
+
+ #def inspect_ssh_pub_key(file_path)
+ #end
+
+ def inspect_node(arg, options)
+ inspect_json manager.nodes[name(arg)]
+ end
+
+ def inspect_service(arg, options)
+ if options[:base]
+ inspect_json manager.base_services[name(arg)]
+ else
+ inspect_json manager.services[name(arg)]
+ end
+ end
+
+ def inspect_tag(arg, options)
+ if options[:base]
+ inspect_json manager.base_tags[name(arg)]
+ else
+ inspect_json manager.tags[name(arg)]
+ end
+ end
+
+ def inspect_provider(arg, options)
+ if options[:base]
+ inspect_json manager.base_provider
+ elsif arg =~ /provider\.(.*)\.json/
+ inspect_json manager.env($1).provider
+ else
+ inspect_json manager.provider
+ end
+ end
+
+ def inspect_common(arg, options)
+ if options[:base]
+ inspect_json manager.base_common
+ else
+ inspect_json manager.common
+ end
+ end
+
+ #
+ # helpers
+ #
+
+ def name(arg)
+ File.basename(arg).sub(/\.json$/, '')
+ end
+
+ def inspect_json(config)
+ if config
+ puts JSON.sorted_generate(config)
+ end
+ end
+
+ def path_match?(path_symbol, path)
+ Dir.glob(Path.named_path([path_symbol, '*'])).include?(path)
+ end
+
+end; end
diff --git a/lib/leap_cli/commands/list.rb b/lib/leap_cli/commands/list.rb
new file mode 100644
index 00000000..aa425432
--- /dev/null
+++ b/lib/leap_cli/commands/list.rb
@@ -0,0 +1,132 @@
+require 'command_line_reporter'
+
+module LeapCli; module Commands
+
+ desc 'List nodes and their classifications'
+ long_desc 'Prints out a listing of nodes, services, or tags. ' +
+ 'If present, the FILTER can be a list of names of nodes, services, or tags. ' +
+ 'If the name is prefixed with +, this acts like an AND condition. ' +
+ "For example:\n\n" +
+ "`leap list node1 node2` matches all nodes named \"node1\" OR \"node2\"\n\n" +
+ "`leap list openvpn +local` matches all nodes with service \"openvpn\" AND tag \"local\""
+
+ arg_name 'FILTER', :optional => true
+ command [:list,:ls] do |c|
+ c.flag 'print', :desc => 'What attributes to print (optional)'
+ c.switch 'disabled', :desc => 'Include disabled nodes in the list.', :negatable => false
+ c.action do |global_options,options,args|
+ # don't rely on default manager(), because we want to pass custom options to load()
+ manager = LeapCli::Config::Manager.new
+ if global_options[:color]
+ colors = ['cyan', 'white']
+ else
+ colors = [nil, nil]
+ end
+ puts
+ manager.load(:include_disabled => options['disabled'], :continue_on_error => true)
+ if options['print']
+ print_node_properties(manager.filter(args), options['print'])
+ else
+ if args.any?
+ NodeTable.new(manager.filter(args), colors).run
+ else
+ environment = LeapCli.leapfile.environment || '_all_'
+ TagTable.new('SERVICES', manager.env(environment).services, colors).run
+ TagTable.new('TAGS', manager.env(environment).tags, colors).run
+ NodeTable.new(manager.filter(), colors).run
+ end
+ end
+ end
+ end
+
+ private
+
+ def self.print_node_properties(nodes, properties)
+ properties = properties.split(',')
+ max_width = nodes.keys.inject(0) {|max,i| [i.size,max].max}
+ nodes.each_node do |node|
+ value = properties.collect{|prop|
+ prop_value = node[prop]
+ if prop_value.nil?
+ "null"
+ elsif prop_value == ""
+ "empty"
+ elsif prop_value.is_a? LeapCli::Config::Object
+ node[prop].dump_json(:format => :compact) # TODO: add option of getting pre-evaluation values.
+ else
+ prop_value.to_s
+ end
+ }.join(', ')
+ printf("%#{max_width}s %s\n", node.name, value)
+ end
+ puts
+ end
+
+ class TagTable
+ include CommandLineReporter
+ def initialize(heading, tag_list, colors)
+ @heading = heading
+ @tag_list = tag_list
+ @colors = colors
+ end
+ def run
+ tags = @tag_list.keys.select{|tag| tag !~ /^_/}.sort # sorted list of tags, excluding _partials
+ max_width = [20, (tags+[@heading]).inject(0) {|max,i| [i.size,max].max}].max
+ table :border => false do
+ row :color => @colors[0] do
+ column @heading, :align => 'right', :width => max_width
+ column "NODES", :width => HighLine::SystemExtensions.terminal_size.first - max_width - 2, :padding => 2
+ end
+ tags.each do |tag|
+ next if @tag_list[tag].node_list.empty?
+ row :color => @colors[1] do
+ column tag
+ column @tag_list[tag].node_list.keys.sort.join(', ')
+ end
+ end
+ end
+ vertical_spacing
+ end
+ end
+
+ #
+ # might be handy: HighLine::SystemExtensions.terminal_size.first
+ #
+ class NodeTable
+ include CommandLineReporter
+ def initialize(node_list, colors)
+ @node_list = node_list
+ @colors = colors
+ end
+ def run
+ rows = @node_list.keys.sort.collect do |node_name|
+ [node_name, @node_list[node_name].services.sort.join(', '), @node_list[node_name].tags.sort.join(', ')]
+ end
+ unless rows.any?
+ puts Paint["no results", :red]
+ puts
+ return
+ end
+ padding = 2
+ max_node_width = [20, (rows.map{|i|i[0]} + ["NODES"] ).inject(0) {|max,i| [i.size,max].max}].max
+ max_service_width = (rows.map{|i|i[1]} + ["SERVICES"]).inject(0) {|max,i| [i.size+padding+padding,max].max}
+ max_tag_width = (rows.map{|i|i[2]} + ["TAGS"] ).inject(0) {|max,i| [i.size,max].max}
+ table :border => false do
+ row :color => @colors[0] do
+ column "NODES", :align => 'right', :width => max_node_width
+ column "SERVICES", :width => max_service_width, :padding => 2
+ column "TAGS", :width => max_tag_width
+ end
+ rows.each do |r|
+ row :color => @colors[1] do
+ column r[0]
+ column r[1]
+ column r[2]
+ end
+ end
+ end
+ vertical_spacing
+ end
+ end
+
+end; end
diff --git a/lib/leap_cli/commands/node.rb b/lib/leap_cli/commands/node.rb
new file mode 100644
index 00000000..a23661b3
--- /dev/null
+++ b/lib/leap_cli/commands/node.rb
@@ -0,0 +1,188 @@
+#
+# fyi: the `node init` command lives in node_init.rb,
+# but all other `node x` commands live here.
+#
+
+autoload :IPAddr, 'ipaddr'
+
+module LeapCli; module Commands
+
+ ##
+ ## COMMANDS
+ ##
+
+ desc 'Node management'
+ command [:node, :n] do |node|
+ node.desc 'Create a new configuration file for a node named NAME.'
+ node.long_desc ["If specified, the optional argument SEED can be used to seed values in the node configuration file.",
+ "The format is property_name:value.",
+ "For example: `leap node add web1 ip_address:1.2.3.4 services:webapp`.",
+ "To set nested properties, property name can contain '.', like so: `leap node add web1 ssh.port:44`",
+ "Separeate multiple values for a single property with a comma, like so: `leap node add mynode services:webapp,dns`"].join("\n\n")
+ node.arg_name 'NAME [SEED]' # , :optional => false, :multiple => false
+ node.command :add do |add|
+ add.switch :local, :desc => 'Make a local testing node (by automatically assigning the next available local IP address). Local nodes are run as virtual machines on your computer.', :negatable => false
+ add.action do |global_options,options,args|
+ # argument sanity checks
+ name = args.first
+ assert_valid_node_name!(name, options[:local])
+ assert_files_missing! [:node_config, name]
+
+ # create and seed new node
+ node = Config::Node.new(manager.env)
+ if options[:local]
+ node['ip_address'] = pick_next_vagrant_ip_address
+ end
+ seed_node_data_from_cmd_line(node, args[1..-1])
+ seed_node_data_from_template(node)
+ validate_ip_address(node)
+ begin
+ node['name'] = name
+ json = node.dump_json(:exclude => ['name'])
+ write_file!([:node_config, name], json + "\n")
+ if file_exists? :ca_cert, :ca_key
+ generate_cert_for_node(manager.reload_node!(node))
+ end
+ rescue LeapCli::ConfigError => exc
+ remove_node_files(name)
+ end
+ end
+ end
+
+ node.desc 'Renames a node file, and all its related files.'
+ node.arg_name 'OLD_NAME NEW_NAME'
+ node.command :mv do |mv|
+ mv.action do |global_options,options,args|
+ node = get_node_from_args(args, include_disabled: true)
+ new_name = args.last
+ assert_valid_node_name!(new_name, node.vagrant?)
+ ensure_dir [:node_files_dir, new_name]
+ Leap::Platform.node_files.each do |path|
+ rename_file! [path, node.name], [path, new_name]
+ end
+ remove_directory! [:node_files_dir, node.name]
+ rename_node_facts(node.name, new_name)
+ end
+ end
+
+ node.desc 'Removes all the files related to the node named NAME.'
+ node.arg_name 'NAME' #:optional => false #, :multiple => false
+ node.command :rm do |rm|
+ rm.action do |global_options,options,args|
+ node = get_node_from_args(args, include_disabled: true)
+ remove_node_files(node.name)
+ if node.vagrant?
+ vagrant_command("destroy --force", [node.name])
+ end
+ remove_node_facts(node.name)
+ end
+ end
+ end
+
+ ##
+ ## PUBLIC HELPERS
+ ##
+
+ def get_node_from_args(args, options={})
+ node_name = args.first
+ node = manager.node(node_name)
+ if node.nil? && options[:include_disabled]
+ node = manager.disabled_node(node_name)
+ end
+ assert!(node, "Node '#{node_name}' not found.")
+ node
+ end
+
+ def seed_node_data_from_cmd_line(node, args)
+ args.each do |seed|
+ key, value = seed.split(':', 2)
+ value = format_seed_value(value)
+ assert! key =~ /^[0-9a-z\._]+$/, "illegal characters used in property '#{key}'"
+ if key =~ /\./
+ key_parts = key.split('.')
+ final_key = key_parts.pop
+ current_object = node
+ key_parts.each do |key_part|
+ current_object[key_part] ||= Config::Object.new
+ current_object = current_object[key_part]
+ end
+ current_object[final_key] = value
+ else
+ node[key] = value
+ end
+ end
+ end
+
+ #
+ # load "new node template" information into the `node`, modifying `node`.
+ # values in the template will not override existing node values.
+ #
+ def seed_node_data_from_template(node)
+ node.inherit_from!(manager.template('common'))
+ [node['services']].flatten.each do |service|
+ if service
+ template = manager.template(service)
+ if template
+ node.inherit_from!(template)
+ end
+ end
+ end
+ end
+
+ def remove_node_files(node_name)
+ (Leap::Platform.node_files + [:node_files_dir]).each do |path|
+ remove_file! [path, node_name]
+ end
+ end
+
+ #
+ # conversions:
+ #
+ # "x,y,z" => ["x","y","z"]
+ #
+ # "22" => 22
+ #
+ # "5.1" => 5.1
+ #
+ def format_seed_value(v)
+ if v =~ /,/
+ v = v.split(',')
+ v.map! do |i|
+ i = i.to_i if i.to_i.to_s == i
+ i = i.to_f if i.to_f.to_s == i
+ i
+ end
+ else
+ v = v.to_i if v.to_i.to_s == v
+ v = v.to_f if v.to_f.to_s == v
+ end
+ return v
+ end
+
+ def validate_ip_address(node)
+ if node['ip_address'] == "REQUIRED"
+ bail! do
+ log :error, "ip_address is not set. Specify with `leap node add NAME ip_address:ADDRESS`."
+ end
+ end
+ IPAddr.new(node['ip_address'])
+ rescue ArgumentError
+ bail! do
+ if node['ip_address']
+ log :invalid, "ip_address #{node['ip_address'].inspect}"
+ else
+ log :missing, "ip_address"
+ end
+ end
+ end
+
+ def assert_valid_node_name!(name, local=false)
+ assert! name, 'No <node-name> specified.'
+ if local
+ assert! name =~ /^[0-9a-z]+$/, "illegal characters used in node name '#{name}' (note: Vagrant does not allow hyphens or underscores)"
+ else
+ assert! name =~ /^[0-9a-z-]+$/, "illegal characters used in node name '#{name}' (note: Linux does not allow underscores)"
+ end
+ end
+
+end; end
diff --git a/lib/leap_cli/commands/node_init.rb b/lib/leap_cli/commands/node_init.rb
new file mode 100644
index 00000000..33f6288d
--- /dev/null
+++ b/lib/leap_cli/commands/node_init.rb
@@ -0,0 +1,169 @@
+#
+# Node initialization.
+# Most of the fun stuff is in tasks.rb.
+#
+
+module LeapCli; module Commands
+
+ desc 'Node management'
+ command :node do |node|
+ node.desc 'Bootstraps a node or nodes, setting up SSH keys and installing prerequisite packages'
+ node.long_desc "This command prepares a server to be used with the LEAP Platform by saving the server's SSH host key, " +
+ "copying the authorized_keys file, installing packages that are required for deploying, and registering important facts. " +
+ "Node init must be run before deploying to a server, and the server must be running and available via the network. " +
+ "This command only needs to be run once, but there is no harm in running it multiple times."
+ node.arg_name 'FILTER'
+ node.command :init do |init|
+ init.switch 'echo', :desc => 'If set, passwords are visible as you type them (default is hidden)', :negatable => false
+ init.flag :port, :desc => 'Override the default SSH port.', :arg_name => 'PORT'
+ init.flag :ip, :desc => 'Override the default SSH IP address.', :arg_name => 'IPADDRESS'
+
+ init.action do |global,options,args|
+ assert! args.any?, 'You must specify a FILTER'
+ finished = []
+ manager.filter!(args).each_node do |node|
+ is_node_alive(node, options)
+ save_public_host_key(node, global, options) unless node.vagrant?
+ update_compiled_ssh_configs
+ ssh_connect_options = connect_options(options).merge({:bootstrap => true, :echo => options[:echo]})
+ ssh_connect(node, ssh_connect_options) do |ssh|
+ if node.vagrant?
+ ssh.install_insecure_vagrant_key
+ end
+ ssh.install_authorized_keys
+ ssh.install_prerequisites
+ unless node.vagrant?
+ ssh.leap.log(:checking, "SSH host keys") do
+ ssh.leap.capture(get_ssh_keys_cmd) do |response|
+ update_local_ssh_host_keys(node, response[:data]) if response[:exitcode] == 0
+ end
+ end
+ end
+ ssh.leap.log(:updating, "facts") do
+ ssh.leap.capture(facter_cmd) do |response|
+ if response[:exitcode] == 0
+ update_node_facts(node.name, response[:data])
+ else
+ log :failed, "to run facter on #{node.name}"
+ end
+ end
+ end
+ end
+ finished << node.name
+ end
+ log :completed, "initialization of nodes #{finished.join(', ')}"
+ end
+ end
+ end
+
+ private
+
+ ##
+ ## PRIVATE HELPERS
+ ##
+
+ def is_node_alive(node, options)
+ address = options[:ip] || node.ip_address
+ port = options[:port] || node.ssh.port
+ log :connecting, "to node #{node.name}"
+ assert_run! "nc -zw3 #{address} #{port}",
+ "Failed to reach #{node.name} (address #{address}, port #{port}). You can override the configured IP address and port with --ip or --port."
+ end
+
+ #
+ # saves the public ssh host key for node into the provider directory.
+ #
+ # see `man sshd` for the format of known_hosts
+ #
+ def save_public_host_key(node, global, options)
+ log :fetching, "public SSH host key for #{node.name}"
+ address = options[:ip] || node.ip_address
+ port = options[:port] || node.ssh.port
+ host_keys = get_public_keys_for_ip(address, port)
+ pub_key_path = Path.named_path([:node_ssh_pub_key, node.name])
+
+ if Path.exists?(pub_key_path)
+ if host_keys.include? SshKey.load(pub_key_path)
+ log :trusted, "- Public SSH host key for #{node.name} matches previously saved key", :indent => 1
+ else
+ bail! do
+ log :error, "The public SSH host keys we just fetched for #{node.name} doesn't match what we have saved previously.", :indent => 1
+ log "Delete the file #{pub_key_path} if you really want to remove the trusted SSH host key.", :indent => 2
+ end
+ end
+ else
+ known_key = host_keys.detect{|k|k.in_known_hosts?(node.name, node.ip_address, node.domain.name)}
+ if known_key
+ log :trusted, "- Public SSH host key for #{node.name} is trusted (key found in your ~/.ssh/known_hosts)"
+ else
+ public_key = SshKey.pick_best_key(host_keys)
+ if public_key.nil?
+ bail!("We got back #{host_keys.size} host keys from #{node.name}, but we can't support any of them.")
+ else
+ say(" This is the SSH host key you got back from node \"#{node.name}\"")
+ say(" Type -- #{public_key.bits} bit #{public_key.type.upcase}")
+ say(" Fingerprint -- " + public_key.fingerprint)
+ say(" Public Key -- " + public_key.key)
+ if !global[:yes] && !agree(" Is this correct? ")
+ bail!
+ else
+ known_key = public_key
+ end
+ end
+ end
+ puts
+ write_file! [:node_ssh_pub_key, node.name], known_key.to_s
+ end
+ end
+
+ #
+ # Get the public host keys for a host using ssh-keyscan.
+ # Return an array of SshKey objects, one for each key.
+ #
+ def get_public_keys_for_ip(address, port=22)
+ assert_bin!('ssh-keyscan')
+ output = assert_run! "ssh-keyscan -p #{port} #{address}", "Could not get the public host key from #{address}:#{port}. Maybe sshd is not running?"
+ if output.empty?
+ bail! :failed, "ssh-keyscan returned empty output."
+ end
+
+ if output =~ /No route to host/
+ bail! :failed, 'ssh-keyscan: no route to %s' % address
+ else
+ keys = SshKey.parse_keys(output)
+ if keys.empty?
+ bail! "ssh-keyscan got zero host keys back (that we understand)! Output was: #{output}"
+ else
+ return keys
+ end
+ end
+ end
+
+ # run on the server to generate a string suitable for passing to SshKey.parse_keys()
+ def get_ssh_keys_cmd
+ "/bin/grep ^HostKey /etc/ssh/sshd_config | /usr/bin/awk '{print $2 \".pub\"}' | /usr/bin/xargs /bin/cat"
+ end
+
+ #
+ # Sometimes the ssh host keys on the server will be better than what we have
+ # stored locally. In these cases, ask the user if they want to upgrade.
+ #
+ def update_local_ssh_host_keys(node, remote_keys_string)
+ remote_keys = SshKey.parse_keys(remote_keys_string)
+ return unless remote_keys.any?
+ current_key = SshKey.load(Path.named_path([:node_ssh_pub_key, node.name]))
+ best_key = SshKey.pick_best_key(remote_keys)
+ return unless best_key && current_key
+ if current_key != best_key
+ say(" One of the SSH host keys for node '#{node.name}' is better than what you currently have trusted.")
+ say(" Current key: #{current_key.summary}")
+ say(" Better key: #{best_key.summary}")
+ if agree(" Do you want to use the better key? ")
+ write_file! [:node_ssh_pub_key, node.name], best_key.to_s
+ end
+ else
+ log(3, "current host key does not need updating")
+ end
+ end
+
+end; end
diff --git a/lib/leap_cli/commands/ssh.rb b/lib/leap_cli/commands/ssh.rb
new file mode 100644
index 00000000..3887618e
--- /dev/null
+++ b/lib/leap_cli/commands/ssh.rb
@@ -0,0 +1,225 @@
+module LeapCli; module Commands
+
+ desc 'Log in to the specified node with an interactive shell.'
+ arg_name 'NAME' #, :optional => false, :multiple => false
+ command :ssh do |c|
+ c.flag 'ssh', :desc => "Pass through raw options to ssh (e.g. `--ssh '-F ~/sshconfig'`)."
+ c.flag 'port', :arg_name => 'SSH_PORT', :desc => 'Override default SSH port used when trying to connect to the server. Same as `--ssh "-p SSH_PORT"`.'
+ c.action do |global_options,options,args|
+ exec_ssh(:ssh, options, args)
+ end
+ end
+
+ desc 'Log in to the specified node with an interactive shell using mosh (requires node to have mosh.enabled set to true).'
+ arg_name 'NAME'
+ command :mosh do |c|
+ c.flag 'ssh', :desc => "Pass through raw options to ssh (e.g. `--ssh '-F ~/sshconfig'`)."
+ c.flag 'port', :arg_name => 'SSH_PORT', :desc => 'Override default SSH port used when trying to connect to the server. Same as `--ssh "-p SSH_PORT"`.'
+ c.action do |global_options,options,args|
+ exec_ssh(:mosh, options, args)
+ end
+ end
+
+ desc 'Creates an SSH port forward (tunnel) to the node NAME. REMOTE_PORT is the port on the remote node that the tunnel will connect to. LOCAL_PORT is the optional port on your local machine. For example: `leap tunnel couch1:5984`.'
+ arg_name '[LOCAL_PORT:]NAME:REMOTE_PORT'
+ command :tunnel do |c|
+ c.flag 'ssh', :desc => "Pass through raw options to ssh (e.g. --ssh '-F ~/sshconfig')."
+ c.flag 'port', :arg_name => 'SSH_PORT', :desc => 'Override default SSH port used when trying to connect to the server. Same as `--ssh "-p SSH_PORT"`.'
+ c.action do |global_options,options,args|
+ local_port, node, remote_port = parse_tunnel_arg(args.first)
+ unless node.ssh.config.AllowTcpForwarding == "yes"
+ log :warning, "It looks like TCP forwarding is not enabled. "+
+ "The tunnel command requires that the node property ssh.config.AllowTcpForwarding "+
+ "be set to 'yes'. Add this property to #{node.name}.json, deploy, and then try tunnel again."
+ end
+ options[:ssh] = [options[:ssh], "-N -L 127.0.0.1:#{local_port}:0.0.0.0:#{remote_port}"].join(' ')
+ log("Forward port localhost:#{local_port} to #{node.name}:#{remote_port}")
+ if is_port_available?(local_port)
+ exec_ssh(:ssh, options, [node.name])
+ end
+ end
+ end
+
+ desc 'Secure copy from FILE1 to FILE2. Files are specified as NODE_NAME:FILE_PATH. For local paths, omit "NODE_NAME:".'
+ arg_name 'FILE1 FILE2'
+ command :scp do |c|
+ c.switch :r, :desc => 'Copy recursively'
+ c.action do |global_options, options, args|
+ if args.size != 2
+ bail!('You must specificy both FILE1 and FILE2')
+ end
+ from, to = args
+ if (from !~ /:/ && to !~ /:/) || (from =~ /:/ && to =~ /:/)
+ bail!('One FILE must be remote and the other local.')
+ end
+ src_node_name = src_file_path = src_node = nil
+ dst_node_name = dst_file_path = dst_node = nil
+ if from =~ /:/
+ src_node_name, src_file_path = from.split(':')
+ src_node = get_node_from_args([src_node_name], :include_disabled => true)
+ dst_file_path = to
+ else
+ dst_node_name, dst_file_path = to.split(':')
+ dst_node = get_node_from_args([dst_node_name], :include_disabled => true)
+ src_file_path = from
+ end
+ exec_scp(options, src_node, src_file_path, dst_node, dst_file_path)
+ end
+ end
+
+ protected
+
+ #
+ # allow for ssh overrides of all commands that use ssh_connect
+ #
+ def connect_options(options)
+ connect_options = {:ssh_options=>{}}
+ if options[:port]
+ connect_options[:ssh_options][:port] = options[:port]
+ end
+ if options[:ip]
+ connect_options[:ssh_options][:host_name] = options[:ip]
+ end
+ return connect_options
+ end
+
+ def ssh_config_help_message
+ puts ""
+ puts "Are 'too many authentication failures' getting you down?"
+ puts "Then we have the solution for you! Add something like this to your ~/.ssh/config file:"
+ puts " Host *.#{manager.provider.domain}"
+ puts " IdentityFile ~/.ssh/id_rsa"
+ puts " IdentitiesOnly=yes"
+ puts "(replace `id_rsa` with the actual private key filename that you use for this provider)"
+ end
+
+ require 'socket'
+ def is_port_available?(port)
+ TCPServer.open('127.0.0.1', port) {}
+ true
+ rescue Errno::EACCES
+ bail!("You don't have permission to bind to port #{port}.")
+ rescue Errno::EADDRINUSE
+ bail!("Local port #{port} is already in use. Specify LOCAL_PORT to pick another.")
+ rescue Exception => exc
+ bail!(exc.to_s)
+ end
+
+ private
+
+ def exec_ssh(cmd, cli_options, args)
+ node = get_node_from_args(args, :include_disabled => true)
+ port = node.ssh.port
+ options = ssh_config(node)
+ username = 'root'
+ if LeapCli.log_level >= 3
+ options << "-vv"
+ elsif LeapCli.log_level >= 2
+ options << "-v"
+ end
+ if cli_options[:port]
+ port = cli_options[:port]
+ end
+ if cli_options[:ssh]
+ options << cli_options[:ssh]
+ end
+ ssh = "ssh -l #{username} -p #{port} #{options.join(' ')}"
+ if cmd == :ssh
+ command = "#{ssh} #{node.domain.full}"
+ elsif cmd == :mosh
+ command = "MOSH_TITLE_NOPREFIX=1 mosh --ssh \"#{ssh}\" #{node.domain.full}"
+ end
+ log 2, command
+
+ # exec the shell command in a subprocess
+ pid = fork { exec "#{command}" }
+
+ Signal.trap("SIGINT") do
+ Process.kill("KILL", pid)
+ Process.wait(pid)
+ exit(0)
+ end
+
+ # wait for shell to exit so we can grab the exit status
+ _, status = Process.waitpid2(pid)
+
+ if status.exitstatus == 255
+ ssh_config_help_message
+ elsif status.exitstatus != 0
+ exit(status.exitstatus)
+ end
+ end
+
+ def exec_scp(cli_options, src_node, src_file_path, dst_node, dst_file_path)
+ node = src_node || dst_node
+ options = ssh_config(node)
+ port = node.ssh.port
+ username = 'root'
+ options << "-r" if cli_options[:r]
+ scp = "scp -P #{port} #{options.join(' ')}"
+ if src_node
+ command = "#{scp} #{username}@#{src_node.domain.full}:#{src_file_path} #{dst_file_path}"
+ elsif dst_node
+ command = "#{scp} #{src_file_path} #{username}@#{dst_node.domain.full}:#{dst_file_path}"
+ end
+ log 2, command
+
+ # exec the shell command in a subprocess
+ pid = fork { exec "#{command}" }
+
+ Signal.trap("SIGINT") do
+ Process.kill("KILL", pid)
+ Process.wait(pid)
+ exit(0)
+ end
+
+ # wait for shell to exit so we can grab the exit status
+ _, status = Process.waitpid2(pid)
+ exit(status.exitstatus)
+ end
+
+ #
+ # SSH command line -o options. See `man ssh_config`
+ #
+ # NOTES:
+ #
+ # The option 'HostKeyAlias=#{node.name}' is oddly incompatible with ports in
+ # known_hosts file, so we must not use this or non-standard ports break.
+ #
+ def ssh_config(node)
+ options = [
+ "-o 'HostName=#{node.ip_address}'",
+ "-o 'GlobalKnownHostsFile=#{path(:known_hosts)}'",
+ "-o 'UserKnownHostsFile=/dev/null'"
+ ]
+ if node.vagrant?
+ options << "-i #{vagrant_ssh_key_file}" # use the universal vagrant insecure key
+ options << "-o IdentitiesOnly=yes" # force the use of the insecure vagrant key
+ options << "-o 'StrictHostKeyChecking=no'" # blindly accept host key and don't save it
+ # (since userknownhostsfile is /dev/null)
+ else
+ options << "-o 'StrictHostKeyChecking=yes'"
+ end
+ if !node.supported_ssh_host_key_algorithms.empty?
+ options << "-o 'HostKeyAlgorithms=#{node.supported_ssh_host_key_algorithms}'"
+ end
+ return options
+ end
+
+ def parse_tunnel_arg(arg)
+ if arg.count(':') == 1
+ node_name, remote = arg.split(':')
+ local = nil
+ elsif arg.count(':') == 2
+ local, node_name, remote = arg.split(':')
+ else
+ bail!('Argument NAME:REMOTE_PORT required.')
+ end
+ node = get_node_from_args([node_name], :include_disabled => true)
+ remote = remote.to_i
+ local = local || remote
+ local = local.to_i
+ return [local, node, remote]
+ end
+
+end; end \ No newline at end of file
diff --git a/lib/leap_cli/commands/test.rb b/lib/leap_cli/commands/test.rb
new file mode 100644
index 00000000..73207b31
--- /dev/null
+++ b/lib/leap_cli/commands/test.rb
@@ -0,0 +1,74 @@
+module LeapCli; module Commands
+
+ desc 'Run tests.'
+ command [:test, :t] do |test|
+ test.desc 'Run the test suit on FILTER nodes.'
+ test.arg_name 'FILTER', :optional => true
+ test.command :run do |run|
+ run.switch 'continue', :desc => 'Continue over errors and failures (default is --no-continue).', :negatable => true
+ run.action do |global_options,options,args|
+ test_order = File.join(Path.platform, 'tests/order.rb')
+ if File.exists?(test_order)
+ require test_order
+ end
+ manager.filter!(args).names_in_test_dependency_order.each do |node_name|
+ node = manager.nodes[node_name]
+ begin
+ ssh_connect(node) do |ssh|
+ ssh.run(test_cmd(options))
+ end
+ rescue Capistrano::CommandError => exc
+ if options[:continue]
+ exit_status(1)
+ else
+ bail!
+ end
+ end
+ end
+ end
+ end
+
+ test.desc 'Creates files needed to run tests.'
+ test.command :init do |init|
+ init.action do |global_options,options,args|
+ generate_test_client_openvpn_configs
+ end
+ end
+
+ test.default_command :run
+ end
+
+ private
+
+ def test_cmd(options)
+ if options[:continue]
+ "#{Leap::Platform.leap_dir}/bin/run_tests --continue"
+ else
+ "#{Leap::Platform.leap_dir}/bin/run_tests"
+ end
+ end
+
+ #
+ # generates a whole bunch of openvpn configs that can be used to connect to different openvpn gateways
+ #
+ def generate_test_client_openvpn_configs
+ assert_config! 'provider.ca.client_certificates.unlimited_prefix'
+ assert_config! 'provider.ca.client_certificates.limited_prefix'
+ template = read_file! Path.find_file(:test_client_openvpn_template)
+ manager.environment_names.each do |env|
+ vpn_nodes = manager.nodes[:environment => env][:services => 'openvpn']['openvpn.allow_limited' => true]
+ if vpn_nodes.any?
+ generate_test_client_cert(provider.ca.client_certificates.limited_prefix) do |key, cert|
+ write_file! [:test_openvpn_config, [env, 'limited'].compact.join('_')], Util.erb_eval(template, binding)
+ end
+ end
+ vpn_nodes = manager.nodes[:environment => env][:services => 'openvpn']['openvpn.allow_unlimited' => true]
+ if vpn_nodes.any?
+ generate_test_client_cert(provider.ca.client_certificates.unlimited_prefix) do |key, cert|
+ write_file! [:test_openvpn_config, [env, 'unlimited'].compact.join('_')], Util.erb_eval(template, binding)
+ end
+ end
+ end
+ end
+
+end; end
diff --git a/lib/leap_cli/commands/user.rb b/lib/leap_cli/commands/user.rb
new file mode 100644
index 00000000..b842e854
--- /dev/null
+++ b/lib/leap_cli/commands/user.rb
@@ -0,0 +1,136 @@
+
+#
+# perhaps we want to verify that the key files are actually the key files we expect.
+# we could use 'file' for this:
+#
+# > file ~/.gnupg/00440025.asc
+# ~/.gnupg/00440025.asc: PGP public key block
+#
+# > file ~/.ssh/id_rsa.pub
+# ~/.ssh/id_rsa.pub: OpenSSH RSA public key
+#
+
+module LeapCli
+ module Commands
+
+ desc 'Adds a new trusted sysadmin by adding public keys to the "users" directory.'
+ arg_name 'USERNAME' #, :optional => false, :multiple => false
+ command :'add-user' do |c|
+
+ c.switch 'self', :desc => 'Add yourself as a trusted sysadmin by choosing among the public keys available for the current user.', :negatable => false
+ c.flag 'ssh-pub-key', :desc => 'SSH public key file for this new user'
+ c.flag 'pgp-pub-key', :desc => 'OpenPGP public key file for this new user'
+
+ c.action do |global_options,options,args|
+ username = args.first
+ if !username.any?
+ if options[:self]
+ username ||= `whoami`.strip
+ else
+ help! "Either USERNAME argument or --self flag is required."
+ end
+ end
+ if Leap::Platform.reserved_usernames.include? username
+ bail! %(The username "#{username}" is reserved. Sorry, pick another.)
+ end
+
+ ssh_pub_key = nil
+ pgp_pub_key = nil
+
+ if options['ssh-pub-key']
+ ssh_pub_key = read_file!(options['ssh-pub-key'])
+ end
+ if options['pgp-pub-key']
+ pgp_pub_key = read_file!(options['pgp-pub-key'])
+ end
+
+ if options[:self]
+ ssh_pub_key ||= pick_ssh_key.to_s
+ pgp_pub_key ||= pick_pgp_key
+ end
+
+ assert!(ssh_pub_key, 'Sorry, could not find SSH public key.')
+
+ if ssh_pub_key
+ write_file!([:user_ssh, username], ssh_pub_key)
+ end
+ if pgp_pub_key
+ write_file!([:user_pgp, username], pgp_pub_key)
+ end
+
+ update_authorized_keys
+ end
+ end
+
+ #
+ # let the the user choose among the ssh public keys that we encounter, or just pick the key if there is only one.
+ #
+ def pick_ssh_key
+ ssh_keys = []
+ Dir.glob("#{ENV['HOME']}/.ssh/*.pub").each do |keyfile|
+ ssh_keys << SshKey.load(keyfile)
+ end
+
+ if `which ssh-add`.strip.any?
+ `ssh-add -L 2> /dev/null`.split("\n").compact.each do |line|
+ key = SshKey.load(line)
+ if key
+ key.comment = 'ssh-agent'
+ ssh_keys << key unless ssh_keys.include?(key)
+ end
+ end
+ end
+ ssh_keys.compact!
+
+ assert! ssh_keys.any?, 'Sorry, could not find any SSH public key for you. Have you run ssh-keygen?'
+
+ if ssh_keys.length > 1
+ key_index = numbered_choice_menu('Choose your SSH public key', ssh_keys.collect(&:summary)) do |line, i|
+ say("#{i+1}. #{line}")
+ end
+ else
+ key_index = 0
+ end
+
+ return ssh_keys[key_index]
+ end
+
+ #
+ # let the the user choose among the gpg public keys that we encounter, or just pick the key if there is only one.
+ #
+ def pick_pgp_key
+ begin
+ require 'gpgme'
+ rescue LoadError
+ log "Skipping OpenPGP setup because gpgme is not installed."
+ return
+ end
+
+ secret_keys = GPGME::Key.find(:secret)
+ if secret_keys.empty?
+ log "Skipping OpenPGP setup because I could not find any OpenPGP keys for you"
+ return nil
+ end
+
+ secret_keys.select!{|key| !key.expired}
+
+ if secret_keys.length > 1
+ key_index = numbered_choice_menu('Choose your OpenPGP public key', secret_keys) do |key, i|
+ key_info = key.to_s.split("\n")[0..1].map{|line| line.sub(/^\s*(sec|uid)\s*/,'')}.join(' -- ')
+ say("#{i+1}. #{key_info}")
+ end
+ else
+ key_index = 0
+ end
+
+ key_id = secret_keys[key_index].sha
+
+ # can't use this, it includes signatures:
+ #puts GPGME::Key.export(key_id, :armor => true, :export_options => :export_minimal)
+
+ # export with signatures removed:
+ return `gpg --armor --export-options export-minimal --export #{key_id}`.strip
+ end
+
+ end
+end
diff --git a/lib/leap_cli/commands/util.rb b/lib/leap_cli/commands/util.rb
new file mode 100644
index 00000000..c1da570e
--- /dev/null
+++ b/lib/leap_cli/commands/util.rb
@@ -0,0 +1,50 @@
+module LeapCli; module Commands
+
+ extend self
+ extend LeapCli::Util
+ extend LeapCli::Util::RemoteCommand
+
+ def path(name)
+ Path.named_path(name)
+ end
+
+ #
+ # keeps prompting the user for a numbered choice, until they pick a good one or bail out.
+ #
+ # block is yielded and is responsible for rendering the choices.
+ #
+ def numbered_choice_menu(msg, items, &block)
+ while true
+ say("\n" + msg + ':')
+ items.each_with_index &block
+ say("q. quit")
+ index = ask("number 1-#{items.length}> ")
+ if index.empty?
+ next
+ elsif index =~ /q/
+ bail!
+ else
+ i = index.to_i - 1
+ if i < 0 || i >= items.length
+ bail!
+ else
+ return i
+ end
+ end
+ end
+ end
+
+
+ def parse_node_list(nodes)
+ if nodes.is_a? Config::Object
+ Config::ObjectList.new(nodes)
+ elsif nodes.is_a? Config::ObjectList
+ nodes
+ elsif nodes.is_a? String
+ manager.filter!(nodes)
+ else
+ bail! "argument error"
+ end
+ end
+
+end; end
diff --git a/lib/leap_cli/commands/vagrant.rb b/lib/leap_cli/commands/vagrant.rb
new file mode 100644
index 00000000..9fdd48e3
--- /dev/null
+++ b/lib/leap_cli/commands/vagrant.rb
@@ -0,0 +1,180 @@
+autoload :IPAddr, 'ipaddr'
+require 'fileutils'
+
+module LeapCli; module Commands
+
+ desc "Manage local virtual machines."
+ long_desc "This command provides a convient way to manage Vagrant-based virtual machines. If FILTER argument is missing, the command runs on all local virtual machines. The Vagrantfile is automatically generated in 'test/Vagrantfile'. If you want to run vagrant commands manually, cd to 'test'."
+ command [:local, :l] do |local|
+ local.desc 'Starts up the virtual machine(s)'
+ local.arg_name 'FILTER', :optional => true #, :multiple => false
+ local.command :start do |start|
+ start.flag(:basebox,
+ :desc => "The basebox to use. This value is passed to vagrant as the "+
+ "`config.vm.box` option. The value here should be the name of an installed box or a "+
+ "shorthand name of a box in HashiCorp's Atlas.",
+ :arg_name => 'BASEBOX',
+ :default_value => 'LEAP/jessie'
+ )
+ start.action do |global_options,options,args|
+ vagrant_command(["up", "sandbox on"], args, options)
+ end
+ end
+
+ local.desc 'Shuts down the virtual machine(s)'
+ local.arg_name 'FILTER', :optional => true #, :multiple => false
+ local.command :stop do |stop|
+ stop.action do |global_options,options,args|
+ if global_options[:yes]
+ vagrant_command("halt --force", args)
+ else
+ vagrant_command("halt", args)
+ end
+ end
+ end
+
+ local.desc 'Destroys the virtual machine(s), reclaiming the disk space'
+ local.arg_name 'FILTER', :optional => true #, :multiple => false
+ local.command :destroy do |destroy|
+ destroy.action do |global_options,options,args|
+ if global_options[:yes]
+ vagrant_command("destroy --force", args)
+ else
+ vagrant_command("destroy", args)
+ end
+ end
+ end
+
+ local.desc 'Print the status of local virtual machine(s)'
+ local.arg_name 'FILTER', :optional => true #, :multiple => false
+ local.command :status do |status|
+ status.action do |global_options,options,args|
+ vagrant_command("status", args)
+ end
+ end
+
+ local.desc 'Saves the current state of the virtual machine as a new snapshot'
+ local.arg_name 'FILTER', :optional => true #, :multiple => false
+ local.command :save do |status|
+ status.action do |global_options,options,args|
+ vagrant_command("sandbox commit", args)
+ end
+ end
+
+ local.desc 'Resets virtual machine(s) to the last saved snapshot'
+ local.arg_name 'FILTER', :optional => true #, :multiple => false
+ local.command :reset do |reset|
+ reset.action do |global_options,options,args|
+ vagrant_command("sandbox rollback", args)
+ end
+ end
+ end
+
+ public
+
+ #
+ # returns the path to a vagrant ssh private key file.
+ #
+ # if the vagrant.key file is owned by root or ourselves, then
+ # we need to make sure that it owned by us and not world readable.
+ #
+ def vagrant_ssh_key_file
+ file_path = Path.vagrant_ssh_priv_key_file
+ Util.assert_files_exist! file_path
+ uid = File.new(file_path).stat.uid
+ if uid == 0 || uid == Process.euid
+ FileUtils.install file_path, '/tmp/vagrant.key', :mode => 0600
+ file_path = '/tmp/vagrant.key'
+ end
+ return file_path
+ end
+
+ protected
+
+ def vagrant_command(cmds, args, options={})
+ vagrant_setup(options)
+ cmds = cmds.to_a
+ if args.empty?
+ nodes = [""]
+ else
+ nodes = manager.filter(args)[:environment => "local"].field(:name)
+ end
+ if nodes.any?
+ vagrant_dir = File.dirname(Path.named_path(:vagrantfile))
+ exec = ["cd #{vagrant_dir}"]
+ cmds.each do |cmd|
+ nodes.each do |node|
+ exec << "vagrant #{cmd} #{node}"
+ end
+ end
+ execute exec.join('; ')
+ else
+ bail! "No nodes found. This command only works on nodes with ip_address in the network #{LeapCli.leapfile.vagrant_network}"
+ end
+ end
+
+ private
+
+ def vagrant_setup(options)
+ assert_bin! 'vagrant', 'Vagrant is required for running local virtual machines. Run "sudo apt-get install vagrant".'
+ assert! (vagrant_version >= Gem::Version.new('1.1')), 'Vagrant version >= 1.1 is required for running local virtual machines. Please upgrade.'
+
+ unless assert_run!('vagrant plugin list | grep sahara | cat').chars.any?
+ log :installing, "vagrant plugin 'sahara'"
+ assert_run! 'vagrant plugin install sahara'
+ end
+ create_vagrant_file(options)
+ end
+
+ def vagrant_version
+ @vagrant_version ||= Gem::Version.new(assert_run!('vagrant --version').split(' ')[1])
+ end
+
+ def execute(cmd)
+ log 2, :run, cmd
+ exec cmd
+ end
+
+ def create_vagrant_file(options)
+ lines = []
+
+ basebox = options[:basebox] || 'LEAP/jessie'
+ # override basebox with custom setting from Leapfile or ~/.leaprc
+ basebox = leapfile.vagrant_basebox || basebox
+
+ lines << %[Vagrant.configure("2") do |config|]
+ manager.each_node do |node|
+ if node.vagrant?
+ lines << %[ config.vm.define :#{node.name} do |config|]
+ lines << %[ config.vm.box = "#{basebox}"]
+ lines << %[ config.vm.network :private_network, ip: "#{node.ip_address}"]
+ lines << %[ config.vm.provider "virtualbox" do |v|]
+ lines << %[ v.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]]
+ lines << %[ v.name = "#{node.name}"]
+ lines << %[ v.memory = 1536]
+ lines << %[ end]
+ lines << %[ config.vm.provider "libvirt" do |v|]
+ lines << %[ v.memory = 1536]
+ lines << %[ end]
+ lines << %[ #{leapfile.custom_vagrant_vm_line}] if leapfile.custom_vagrant_vm_line
+ lines << %[ end]
+ end
+ end
+
+ lines << %[end]
+ lines << ""
+ write_file! :vagrantfile, lines.join("\n")
+ end
+
+ def pick_next_vagrant_ip_address
+ taken_ips = manager.nodes[:environment => "local"].field(:ip_address)
+ if taken_ips.any?
+ highest_ip = taken_ips.map{|ip| IPAddr.new(ip)}.max
+ new_ip = highest_ip.succ
+ else
+ new_ip = IPAddr.new(LeapCli.leapfile.vagrant_network).succ.succ
+ end
+ return new_ip.to_s
+ end
+
+end; end
diff --git a/lib/leap_cli/macros.rb b/lib/leap_cli/macros.rb
new file mode 100644
index 00000000..fdb9a94e
--- /dev/null
+++ b/lib/leap_cli/macros.rb
@@ -0,0 +1,16 @@
+#
+# MACROS
+#
+# The methods in these files are available in the context of a .json configuration file.
+# (The module LeapCli::Macro is included in Config::Object)
+#
+
+require_relative 'macros/core'
+require_relative 'macros/files'
+require_relative 'macros/haproxy'
+require_relative 'macros/hosts'
+require_relative 'macros/keys'
+require_relative 'macros/nodes'
+require_relative 'macros/secrets'
+require_relative 'macros/stunnel'
+require_relative 'macros/provider'
diff --git a/lib/leap_cli/macros/core.rb b/lib/leap_cli/macros/core.rb
new file mode 100644
index 00000000..873da358
--- /dev/null
+++ b/lib/leap_cli/macros/core.rb
@@ -0,0 +1,92 @@
+# encoding: utf-8
+
+module LeapCli
+ module Macro
+
+ #
+ # Creates a hash from the ssh key info in users directory, for use in
+ # updating authorized_keys file. Additionally, the 'monitor' public key is
+ # included, which is used by the monitor nodes to run particular commands
+ # remotely.
+ #
+ def authorized_keys
+ hash = {}
+ keys = Dir.glob(Path.named_path([:user_ssh, '*']))
+ keys.sort.each do |keyfile|
+ ssh_type, ssh_key = File.read(keyfile, :encoding => 'UTF-8').strip.split(" ")
+ name = File.basename(File.dirname(keyfile))
+ until hash[name].nil?
+ i ||= 1; name = "#{name}#{i+=1}"
+ end
+ hash[name] = {
+ "type" => ssh_type,
+ "key" => ssh_key
+ }
+ end
+ ssh_type, ssh_key = File.read(Path.named_path(:monitor_pub_key), :encoding => 'UTF-8').strip.split(" ")
+ hash[Leap::Platform.monitor_username] = {
+ "type" => ssh_type,
+ "key" => ssh_key
+ }
+ hash
+ end
+
+ def assert(assertion)
+ if instance_eval(assertion)
+ true
+ else
+ raise AssertionFailed.new(assertion), assertion, caller
+ end
+ end
+
+ def error(msg)
+ raise ConfigError.new(@node, msg), msg, caller
+ end
+
+ #
+ # applies a JSON partial to this node
+ #
+ def apply_partial(partial_path)
+ if env.partials[partial_path]
+ self.deep_merge!(env.partials[partial_path])
+ else
+ raise ArgumentError.new(
+ "No such partial `%s`. Available partials include:\n%s" %
+ [partial_path, env.partials.keys.join(", ")]
+ )
+ end
+ end
+
+ #
+ # If at first you don't succeed, then it is time to give up.
+ #
+ # try{} returns nil if anything in the block throws an exception.
+ #
+ # You can wrap something that might fail in `try`, like so.
+ #
+ # "= try{ nodes[:services => 'tor'].first.ip_address } "
+ #
+ def try(&block)
+ yield
+ rescue NoMethodError
+ rescue ArgumentError
+ nil
+ end
+
+ protected
+
+ #
+ # returns a node list, if argument is not already one
+ #
+ def listify(node_list)
+ if node_list.is_a? Config::ObjectList
+ node_list
+ elsif node_list.is_a? Config::Object
+ Config::ObjectList.new(node_list)
+ else
+ raise ArgumentError, 'argument must be a node or node list, not a `%s`' % node_list.class, caller
+ end
+ end
+
+ end
+end
diff --git a/lib/leap_cli/macros/files.rb b/lib/leap_cli/macros/files.rb
new file mode 100644
index 00000000..04c94edf
--- /dev/null
+++ b/lib/leap_cli/macros/files.rb
@@ -0,0 +1,124 @@
+# encoding: utf-8
+
+##
+## FILES
+##
+
+module LeapCli
+ module Macro
+
+ #
+ # inserts the contents of a file
+ #
+ def file(filename, options={})
+ if filename.is_a? Symbol
+ filename = [filename, @node.name]
+ end
+ filepath = Path.find_file(filename)
+ if filepath
+ if filepath =~ /\.erb$/
+ return ERB.new(File.read(filepath, :encoding => 'UTF-8'), nil, '%<>').result(binding)
+ else
+ return File.read(filepath, :encoding => 'UTF-8')
+ end
+ else
+ raise FileMissing.new(Path.named_path(filename), options)
+ end
+ end
+
+ #
+ # like #file, but allow missing files
+ #
+ def try_file(filename)
+ return file(filename)
+ rescue FileMissing
+ return nil
+ end
+
+ #
+ # returns the location of a file that is stored on the local
+ # host, under PROVIDER_DIR/files.
+ #
+ def local_file_path(path, options={})
+ if path.is_a? Symbol
+ path = [path, @node.name]
+ elsif path.is_a? String
+ # ensure it prefixed with files/
+ unless path =~ /^files\//
+ path = "files/" + path
+ end
+ end
+ local_path = Path.find_file(path)
+ if local_path.nil?
+ if options[:missing]
+ raise FileMissing.new(Path.named_path(path), options)
+ elsif block_given?
+ yield
+ return local_file_path(path, options) # try again.
+ else
+ Util::log 2, :skipping, "local_file_path(\"#{path}\") because there is no such file."
+ return nil
+ end
+ else
+ return local_path
+ end
+ end
+
+ #
+ # Returns the location of a file once it is deployed via rsync to the a
+ # remote server. An internal list of discovered file paths is saved, in
+ # order to rsync these files when needed.
+ #
+ # If the file does not exist, nil is returned.
+ #
+ # If there is a block given and the file does not actually exist, the
+ # block will be yielded to give an opportunity for some code to create the
+ # file.
+ #
+ # For example:
+ #
+ # file_path(:dkim_priv_key) {generate_dkim_key}
+ #
+ # notes:
+ #
+ # * argument 'path' is relative to Path.provider/files or
+ # Path.provider_base/files
+ # * the path returned by this method is absolute
+ # * the path stored for use later by rsync is relative to Path.provider
+ # * if the path does not exist locally, but exists in provider_base,
+ # then the default file from provider_base is copied locally. this
+ # is required for rsync to work correctly.
+ #
+ def remote_file_path(path, options={}, &block)
+ local_path = local_file_path(path, options, &block)
+
+ return nil if local_path.nil?
+
+ # if file is under Path.provider_base, we must copy the default file to
+ # to Path.provider in order for rsync to be able to sync the file.
+ if local_path =~ /^#{Regexp.escape(Path.provider_base)}/
+ local_provider_path = local_path.sub(/^#{Regexp.escape(Path.provider_base)}/, Path.provider)
+ FileUtils.mkdir_p File.dirname(local_provider_path), :mode => 0700
+ FileUtils.install local_path, local_provider_path, :mode => 0600
+ Util.log :created, Path.relative_path(local_provider_path)
+ local_path = local_provider_path
+ end
+
+ # ensure directories end with /, important for building rsync command
+ if File.directory?(local_path) && local_path !~ /\/$/
+ local_path += '/'
+ end
+
+ relative_path = Path.relative_path(local_path)
+ relative_path.sub!(/^files\//, '') # remove "files/" prefix
+ @node.file_paths << relative_path
+ return File.join(Leap::Platform.files_dir, relative_path)
+ end
+
+ # deprecated
+ def file_path(path, options={})
+ return remote_file_path(path, options)
+ end
+
+ end
+end \ No newline at end of file
diff --git a/lib/leap_cli/macros/haproxy.rb b/lib/leap_cli/macros/haproxy.rb
new file mode 100644
index 00000000..602ae726
--- /dev/null
+++ b/lib/leap_cli/macros/haproxy.rb
@@ -0,0 +1,73 @@
+# encoding: utf-8
+
+##
+## HAPROXY
+##
+
+module LeapCli
+ module Macro
+
+ #
+ # creates a hash suitable for configuring haproxy. the key is the node name of the server we are proxying to.
+ #
+ # * node_list - a hash of nodes for the haproxy servers
+ # * stunnel_client - contains the mappings to local ports for each server node.
+ # * non_stunnel_port - in case self is included in node_list, the port to connect to.
+ #
+ # 1000 weight is used for nodes in the same location.
+ # 100 otherwise.
+ #
+ def haproxy_servers(node_list, stunnel_clients, non_stunnel_port=nil)
+ default_weight = 10
+ local_weight = 100
+
+ # record the hosts_file
+ hostnames(node_list)
+
+ # create a simple map for node name -> local stunnel accept port
+ accept_ports = stunnel_clients.inject({}) do |hsh, stunnel_entry|
+ name = stunnel_entry.first.sub /_[0-9]+$/, ''
+ hsh[name] = stunnel_entry.last['accept_port']
+ hsh
+ end
+
+ # if one the nodes in the node list is ourself, then there will not be a stunnel to it,
+ # but we need to include it anyway in the haproxy config.
+ if node_list[self.name] && non_stunnel_port
+ accept_ports[self.name] = non_stunnel_port
+ end
+
+ # create the first pass of the servers hash
+ servers = node_list.values.inject(Config::ObjectList.new) do |hsh, node|
+ # make sure we have a port to talk to
+ unless accept_ports[node.name]
+ error "haproxy needs a local port to talk to when connecting to #{node.name}"
+ end
+ weight = default_weight
+ try {
+ weight = local_weight if self.location.name == node.location.name
+ }
+ hsh[node.name] = Config::Object[
+ 'backup', false,
+ 'host', 'localhost',
+ 'port', accept_ports[node.name],
+ 'weight', weight
+ ]
+ if node.services.include?('couchdb')
+ hsh[node.name]['writable'] = node.couch.mode != 'mirror'
+ end
+ hsh
+ end
+
+ # if there are some local servers, make the others backup
+ if servers.detect{|k,v| v.weight == local_weight}
+ servers.each do |k,server|
+ server['backup'] = server['weight'] == default_weight
+ end
+ end
+
+ return servers
+ end
+
+ end
+end
diff --git a/lib/leap_cli/macros/hosts.rb b/lib/leap_cli/macros/hosts.rb
new file mode 100644
index 00000000..963857ae
--- /dev/null
+++ b/lib/leap_cli/macros/hosts.rb
@@ -0,0 +1,90 @@
+# encoding: utf-8
+
+module LeapCli
+ module Macro
+
+ ##
+ ## IPs
+ ##
+
+ #
+ # returns a simple array of all the IPs for the specified node list
+ #
+ def host_ips(node_list)
+ if self.vagrant?
+ node_list = node_list['environment' => 'local']
+ else
+ node_list = node_list['environment' => '!local']
+ end
+ node_list.map {|name, n|
+ [n.ip_address, (manager.facts[name]||{})['ec2_public_ipv4']]
+ }.flatten.compact.uniq
+ end
+
+ ##
+ ## HOSTS
+ ##
+
+ #
+ # records the list of hosts that are encountered for this node
+ #
+ def hostnames(nodes)
+ @referenced_nodes ||= Config::ObjectList.new
+ nodes = listify(nodes)
+ nodes.each_node do |node|
+ @referenced_nodes[node.name] ||= node
+ end
+ return nodes.values.collect {|node| node.domain.name}
+ end
+
+ #
+ # Generates entries needed for updating /etc/hosts on a node (as a hash).
+ #
+ # Argument `nodes` can be nil or a list of nodes. If nil, only include the
+ # IPs of the other nodes this @node as has encountered (plus all mx nodes).
+ #
+ # Also, for virtual machines, we use the local address if this @node is in
+ # the same location as the node in question.
+ #
+ # We include the ssh public key for each host, so that the hash can also
+ # be used to generate the /etc/ssh/known_hosts
+ #
+ def hosts_file(nodes=nil)
+ if nodes.nil?
+ if @referenced_nodes && @referenced_nodes.any?
+ nodes = @referenced_nodes
+ nodes = nodes.merge(nodes_like_me[:services => 'mx']) # all nodes always need to communicate with mx nodes.
+ end
+ end
+ return {} unless nodes
+ hosts = {}
+ my_location = @node['location'] ? @node['location']['name'] : nil
+ nodes.each_node do |node|
+ hosts[node.name] = {
+ 'ip_address' => node.ip_address,
+ 'domain_internal' => node.domain.internal,
+ 'domain_full' => node.domain.full,
+ 'port' => node.ssh.port
+ }
+ if node.dns['aliases'] && node.dns['aliases'].any?
+ # include aliases, but without domain.full
+ hosts[node.name]['aliases'] = node.dns['aliases'] - [node.domain.full]
+ end
+ node_location = node['location'] ? node['location']['name'] : nil
+ if my_location == node_location
+ if facts = @node.manager.facts[node.name]
+ if facts['ec2_public_ipv4']
+ hosts[node.name]['ip_address'] = facts['ec2_public_ipv4']
+ end
+ end
+ end
+ host_pub_key = Util::read_file([:node_ssh_pub_key,node.name])
+ if host_pub_key
+ hosts[node.name]['host_pub_key'] = host_pub_key
+ end
+ end
+ hosts
+ end
+
+ end
+end \ No newline at end of file
diff --git a/lib/leap_cli/macros/keys.rb b/lib/leap_cli/macros/keys.rb
new file mode 100644
index 00000000..e7a75cfb
--- /dev/null
+++ b/lib/leap_cli/macros/keys.rb
@@ -0,0 +1,97 @@
+# encoding: utf-8
+
+#
+# Macro for dealing with cryptographic keys
+#
+
+module LeapCli
+ module Macro
+
+ #
+ # return a fingerprint for a key or certificate
+ #
+ def fingerprint(filename, options={})
+ options[:mode] ||= :x509
+ if options[:mode] == :x509
+ "SHA256: " + X509.fingerprint("SHA256", Path.named_path(filename))
+ elsif options[:mode] == :rsa
+ key = OpenSSL::PKey::RSA.new(File.read(filename))
+ Digest::SHA1.new.hexdigest(key.to_der)
+ end
+ end
+
+ ##
+ ## TOR
+ ##
+
+ #
+ # return the path to the tor public key
+ # generating key if it is missing
+ #
+ def tor_public_key_path(path_name, key_type)
+ file_path(path_name) { generate_tor_key(key_type) }
+ end
+
+ #
+ # return the path to the tor private key
+ # generating key if it is missing
+ #
+ def tor_private_key_path(path_name, key_type)
+ file_path(path_name) { generate_tor_key(key_type) }
+ end
+
+ #
+ # Generates a onion_address from a public RSA key file.
+ #
+ # path_name is the named path of the Tor public key.
+ #
+ # Basically, an onion address is nothing more than a base32 encoding
+ # of the first 10 bytes of a sha1 digest of the public key.
+ #
+ # Additionally, Tor ignores the 22 byte header of the public key
+ # before taking the sha1 digest.
+ #
+ def onion_address(path_name)
+ require 'base32'
+ require 'base64'
+ require 'openssl'
+ path = Path.find_file([path_name, self.name])
+ if path && File.exists?(path)
+ public_key_str = File.readlines(path).grep(/^[^-]/).join
+ public_key = Base64.decode64(public_key_str)
+ public_key = public_key.slice(22..-1) # Tor ignores the 22 byte SPKI header
+ sha1sum = Digest::SHA1.new.digest(public_key)
+ Base32.encode(sha1sum.slice(0,10)).downcase
+ else
+ LeapCli.log :warning, 'Tor public key file "%s" does not exist' % tor_public_key_path
+ end
+ end
+
+ def generate_dkim_key(bit_size=2048)
+ LeapCli.log :generating, "%s bit RSA DKIM key" % bit_size do
+ private_key = OpenSSL::PKey::RSA.new(bit_size)
+ public_key = private_key.public_key
+ LeapCli::Util.write_file! :dkim_priv_key, private_key.to_pem
+ LeapCli::Util.write_file! :dkim_pub_key, public_key.to_pem
+ end
+ end
+
+ private
+
+ def generate_tor_key(key_type)
+ if key_type == 'RSA'
+ require 'certificate_authority'
+ keypair = CertificateAuthority::MemoryKeyMaterial.new
+ bit_size = 1024
+ LeapCli.log :generating, "%s bit RSA Tor key" % bit_size do
+ keypair.generate_key(bit_size)
+ LeapCli::Util.write_file! [:node_tor_priv_key, self.name], keypair.private_key.to_pem
+ LeapCli::Util.write_file! [:node_tor_pub_key, self.name], keypair.public_key.to_pem
+ end
+ else
+ LeapCli.bail! 'tor.key.type of %s is not yet supported' % key_type
+ end
+ end
+
+ end
+end
diff --git a/lib/leap_cli/macros/nodes.rb b/lib/leap_cli/macros/nodes.rb
new file mode 100644
index 00000000..0e23831d
--- /dev/null
+++ b/lib/leap_cli/macros/nodes.rb
@@ -0,0 +1,88 @@
+# encoding: utf-8
+
+##
+## node related macros
+##
+
+module LeapCli
+ module Macro
+
+ #
+ # the list of all the nodes
+ #
+ def nodes
+ env.nodes
+ end
+
+ #
+ # simple alias for global.provider
+ #
+ def provider
+ env.provider
+ end
+
+ #
+ # returns a list of nodes that match the same environment
+ #
+ # if @node.environment is not set, we return other nodes
+ # where environment is not set.
+ #
+ def nodes_like_me
+ nodes[:environment => @node.environment]
+ end
+
+ #
+ # returns a list of nodes that match the location name
+ # and environment of @node.
+ #
+ def nodes_near_me
+ if @node['location'] && @node['location']['name']
+ nodes_like_me['location.name' => @node.location.name]
+ else
+ nodes_like_me['location' => nil]
+ end
+ end
+
+ #
+ #
+ # picks a node out from the node list in such a way that:
+ #
+ # (1) which nodes picked which nodes is saved in secrets.json
+ # (2) when other nodes call this macro with the same node list, they are guaranteed to get a different node
+ # (3) if all the nodes in the pick_node list have been picked, remaining nodes are distributed randomly.
+ #
+ # if the node_list is empty, an exception is raised.
+ # if node_list size is 1, then that node is returned and nothing is
+ # memorized via the secrets.json file.
+ #
+ # `label` is needed to distinguish between pools of nodes for different purposes.
+ #
+ # TODO: more evenly balance after all the nodes have been picked.
+ #
+ def pick_node(label, node_list)
+ if node_list.any?
+ if node_list.size == 1
+ return node_list.values.first
+ else
+ secrets_key = "pick_node(:#{label},#{node_list.keys.sort.join(',')})"
+ secrets_value = @manager.secrets.retrieve(secrets_key, @node.environment) || {}
+ secrets_value[@node.name] ||= begin
+ node_to_pick = nil
+ node_list.each_node do |node|
+ next if secrets_value.values.include?(node.name)
+ node_to_pick = node.name
+ end
+ node_to_pick ||= secrets_value.values.shuffle.first # all picked already, so pick a random one.
+ node_to_pick
+ end
+ picked_node_name = secrets_value[@node.name]
+ @manager.secrets.set(secrets_key, secrets_value, @node.environment)
+ return node_list[picked_node_name]
+ end
+ else
+ raise ArgumentError.new('pick_node(node_list): node_list cannot be empty')
+ end
+ end
+
+ end
+end \ No newline at end of file
diff --git a/lib/leap_cli/macros/provider.rb b/lib/leap_cli/macros/provider.rb
new file mode 100644
index 00000000..4e74da01
--- /dev/null
+++ b/lib/leap_cli/macros/provider.rb
@@ -0,0 +1,90 @@
+#
+# These macros are intended only for use in provider.json, although they are
+# currently loaded in all .json contexts.
+#
+
+module LeapCli
+ module Macro
+
+ #
+ # returns an array of the service names, including only those services that
+ # are enabled for this environment.
+ #
+ def enabled_services
+ manager.env(self.environment).services[:service_type => :user_service].field(:name).select { |service|
+ manager.nodes[:environment => self.environment][:services => service].any?
+ }
+ end
+
+ #
+ # The webapp will not work unless the service level configuration is precisely defined.
+ # Here, we take what the sysadmin has specified in provider.json and clean it up to
+ # ensure it is OK.
+ #
+ # It would be better to add support for JSON schema.
+ #
+ def service_levels()
+ levels = {}
+ provider.service.levels.each do |name, level|
+ if name =~ /^[0-9]+$/
+ name = name.to_i
+ end
+ levels[name] = level_cleanup(name, level.clone)
+ end
+ levels
+ end
+
+ private
+
+ def print_warning(name, msg)
+ if self.environment
+ provider_str = "provider.json or %s" % ['provider', self.environment, 'json'].join('.')
+ else
+ provider_str = "provider.json"
+ end
+ LeapCli::log :warning, "In #{provider_str}, you have an incorrect definition for service level '#{name}':" do
+ LeapCli::log msg
+ end
+ end
+
+ def level_cleanup(name, level)
+ unless level['name']
+ print_warning(name, 'required field "name" is missing')
+ end
+ unless level['description']
+ print_warning(name, 'required field "description" is missing')
+ end
+ unless level['bandwidth'].nil? || level['bandwidth'] == 'limited'
+ print_warning(name, 'field "bandwidth" must be nil or "limited"')
+ end
+ unless level['rate'].nil? || level['rate'].is_a?(Hash)
+ print_warning(name, 'field "rate" must be nil or a hash (e.g. {"USD":10, "EUR":10})')
+ end
+ possible_services = enabled_services
+ if level['services']
+ level['services'].each do |service|
+ unless possible_services.include? service
+ print_warning(name, "the service '#{service}' does not exist or there are no nodes that provide this service.")
+ LeapCli::Util::bail!
+ end
+ end
+ else
+ level['services'] = possible_services
+ end
+ level['services'] = remap_services(level['services'])
+ level
+ end
+
+ #
+ # the service names that the webapp uses and that leap_platform uses are different. ugh.
+ #
+ SERVICE_MAP = {
+ "mx" => "email",
+ "openvpn" => "eip"
+ }
+ def remap_services(services)
+ services.map {|srv| SERVICE_MAP[srv]}
+ end
+
+ end
+end
diff --git a/lib/leap_cli/macros/secrets.rb b/lib/leap_cli/macros/secrets.rb
new file mode 100644
index 00000000..8d1feb55
--- /dev/null
+++ b/lib/leap_cli/macros/secrets.rb
@@ -0,0 +1,39 @@
+# encoding: utf-8
+
+require 'base32'
+
+module LeapCli
+ module Macro
+
+ #
+ # inserts a named secret, generating it if needed.
+ #
+ # manager.export_secrets should be called later to capture any newly generated secrets.
+ #
+ # +length+ is the character length of the generated password.
+ #
+ def secret(name, length=32)
+ manager.secrets.set(name, @node.environment) { Util::Secret.generate(length) }
+ end
+
+ # inserts a base32 encoded secret
+ def base32_secret(name, length=20)
+ manager.secrets.set(name, @node.environment) { Base32.encode(Util::Secret.generate(length)) }
+ end
+
+ # Picks a random obfsproxy port from given range
+ def rand_range(name, range)
+ manager.secrets.set(name, @node.environment) { rand(range) }
+ end
+
+ #
+ # inserts an hexidecimal secret string, generating it if needed.
+ #
+ # +bit_length+ is the bits in the secret, (ie length of resulting hex string will be bit_length/4)
+ #
+ def hex_secret(name, bit_length=128)
+ manager.secrets.set(name, @node.environment) { Util::Secret.generate_hex(bit_length) }
+ end
+
+ end
+end \ No newline at end of file
diff --git a/lib/leap_cli/macros/stunnel.rb b/lib/leap_cli/macros/stunnel.rb
new file mode 100644
index 00000000..821bda38
--- /dev/null
+++ b/lib/leap_cli/macros/stunnel.rb
@@ -0,0 +1,106 @@
+##
+## STUNNEL
+##
+
+#
+# About stunnel
+# --------------------------
+#
+# The network looks like this:
+#
+# From the client's perspective:
+#
+# |------- stunnel client --------------| |---------- stunnel server -----------------------|
+# consumer app -> localhost:accept_port -> connect:connect_port -> ??
+#
+# From the server's perspective:
+#
+# |------- stunnel client --------------| |---------- stunnel server -----------------------|
+# ?? -> *:accept_port -> localhost:connect_port -> service
+#
+
+module LeapCli
+ module Macro
+
+ #
+ # stunnel configuration for the client side.
+ #
+ # +node_list+ is a ObjectList of nodes running stunnel servers.
+ #
+ # +port+ is the real port of the ultimate service running on the servers
+ # that the client wants to connect to.
+ #
+ # * accept_port is the port on localhost to which local clients
+ # can connect. it is auto generated serially.
+ #
+ # * connect_port is the port on the stunnel server to connect to.
+ # it is auto generated from the +port+ argument.
+ #
+ # generates an entry appropriate to be passed directly to
+ # create_resources(stunnel::service, hiera('..'), defaults)
+ #
+ # local ports are automatically generated, starting at 4000
+ # and incrementing in sorted order (by node name).
+ #
+ def stunnel_client(node_list, port, options={})
+ @next_stunnel_port ||= 4000
+ node_list = listify(node_list)
+ hostnames(node_list) # record the hosts
+ result = Config::ObjectList.new
+ node_list.each_node do |node|
+ if node.name != self.name || options[:include_self]
+ s_port = stunnel_port(port)
+ result["#{node.name}_#{port}"] = Config::Object[
+ 'accept_port', @next_stunnel_port,
+ 'connect', node.domain.internal,
+ 'connect_port', s_port,
+ 'original_port', port
+ ]
+ manager.connections.add(:from => @node.ip_address, :to => node.ip_address, :port => s_port)
+ @next_stunnel_port += 1
+ end
+ end
+ result
+ end
+
+ #
+ # generates a stunnel server entry.
+ #
+ # +port+ is the real port targeted service.
+ #
+ # * `accept_port` is the publicly bound port
+ # * `connect_port` is the port that the local service is running on.
+ #
+ def stunnel_server(port)
+ {
+ "accept_port" => stunnel_port(port),
+ "connect_port" => port
+ }
+ end
+
+ #
+ # lists the ips that connect to this node, on particular ports.
+ #
+ def stunnel_firewall
+ manager.connections.select {|connection|
+ connection['to'] == @node.ip_address
+ }
+ end
+
+ private
+
+ #
+ # maps a real port to a stunnel port (used as the connect_port in the client config
+ # and the accept_port in the server config)
+ #
+ def stunnel_port(port)
+ port = port.to_i
+ if port < 50000
+ return port + 10000
+ else
+ return port - 10000
+ end
+ end
+
+ end
+end \ No newline at end of file
diff --git a/platform.rb b/platform.rb
new file mode 100644
index 00000000..1e19a2a9
--- /dev/null
+++ b/platform.rb
@@ -0,0 +1,119 @@
+# encoding: utf-8
+#
+# These are variables defined by this leap_platform and used by leap_cli.
+#
+
+Leap::Platform.define do
+ self.version = "0.8"
+ self.compatible_cli = "1.8".."1.99"
+
+ #
+ # the facter facts that should be gathered
+ #
+ self.facts = ["ec2_local_ipv4", "ec2_public_ipv4"]
+
+ #
+ # absolute paths on the destination server
+ #
+ self.hiera_dir = '/etc/leap' if self.respond_to?(:hiera_dir)
+ self.hiera_path = '/etc/leap/hiera.yaml'
+ self.leap_dir = '/srv/leap'
+ self.files_dir = '/srv/leap/files'
+ self.init_path = '/srv/leap/initialized'
+
+ #
+ # the named paths for this platform
+ # (relative to the provider directory)
+ #
+ self.paths = {
+ # directories
+ :hiera_dir => 'hiera',
+ :files_dir => 'files',
+ :nodes_dir => 'nodes',
+ :services_dir => 'services',
+ :templates_dir => 'templates',
+ :tags_dir => 'tags',
+ :node_files_dir => 'files/nodes/#{arg}',
+
+ # input config files
+ :common_config => 'common.json',
+ :provider_config => 'provider.json',
+ :service_config => 'services/#{arg}.json',
+ :tag_config => 'tags/#{arg}.json',
+ :template_config => 'templates/#{arg}.json',
+ :secrets_config => 'secrets.json',
+ :node_config => 'nodes/#{arg}.json',
+
+ # input config files, environmentally scoped
+ :common_env_config => 'commmon.#{arg}.json',
+ :provider_env_config => 'provider.#{arg}.json',
+ :service_env_config => 'services/#{arg[0]}.#{arg[1]}.json',
+ :tag_env_config => 'tags/#{arg[0]}.#{arg[1]}.json',
+
+ # input templates
+ :provider_json_template => 'files/service-definitions/provider.json.erb',
+ :eip_service_json_template => 'files/service-definitions/#{arg}/eip-service.json.erb',
+ :soledad_service_json_template => 'files/service-definitions/#{arg}/soledad-service.json.erb',
+ :smtp_service_json_template => 'files/service-definitions/#{arg}/smtp-service.json.erb',
+
+ # custom files
+ :custom_puppet_dir => 'files/puppet',
+ :custom_puppet_modules_dir => 'files/puppet/modules',
+ :custom_puppet_manifests_dir => 'files/puppet/manifests',
+ :custom_tests => 'files/tests',
+ :custom_bin => 'files/bin',
+
+ # output files
+ :facts => 'facts.json',
+ :user_ssh => 'users/#{arg}/#{arg}_ssh.pub',
+ :user_pgp => 'users/#{arg}/#{arg}_pgp.pub',
+ :known_hosts => 'files/ssh/known_hosts',
+ :authorized_keys => 'files/ssh/authorized_keys',
+ :monitor_pub_key => 'files/ssh/monitor_ssh.pub',
+ :monitor_priv_key => 'files/ssh/monitor_ssh',
+ :ca_key => 'files/ca/ca.key',
+ :ca_cert => 'files/ca/ca.crt',
+ :client_ca_key => 'files/ca/client_ca.key',
+ :client_ca_cert => 'files/ca/client_ca.crt',
+ :dh_params => 'files/ca/dh.pem',
+ :commercial_key => 'files/cert/#{arg}.key',
+ :commercial_csr => 'files/cert/#{arg}.csr',
+ :commercial_cert => 'files/cert/#{arg}.crt',
+ :dkim_priv_key => 'files/mx/dkim.key',
+ :dkim_pub_key => 'files/mx/dkim.pub',
+
+ :commercial_ca_cert => 'files/cert/commercial_ca.crt',
+ :vagrantfile => 'test/Vagrantfile',
+ :static_web_provider_json => 'files/web/bootstrap/#{arg}/provider.json',
+ :static_web_htaccess => 'files/web/bootstrap/#{arg}/htaccess',
+ :static_web_readme => 'files/web/bootstrap/README',
+
+ # node output files
+ :hiera => 'hiera/#{arg}.yaml',
+ :node_ssh_pub_key => 'files/nodes/#{arg}/#{arg}_ssh.pub',
+ :node_x509_key => 'files/nodes/#{arg}/#{arg}.key',
+ :node_x509_cert => 'files/nodes/#{arg}/#{arg}.crt',
+ :node_tor_priv_key => 'files/nodes/#{arg}/tor.key',
+ :node_tor_pub_key => 'files/nodes/#{arg}/tor.pub',
+
+ # testing files
+ :test_client_key => 'test/cert/client.key',
+ :test_client_cert => 'test/cert/client.crt',
+ :test_openvpn_config => 'test/openvpn/#{arg}.ovpn',
+ :test_client_openvpn_template => 'test/openvpn/client.ovpn.erb'
+ }
+
+ #
+ # the files that need to get renamed when a node is renamed
+ #
+ self.node_files = [
+ :node_config, :hiera, :node_x509_cert, :node_x509_key, :node_ssh_pub_key
+ ]
+
+ self.monitor_username = 'monitor'
+
+ self.reserved_usernames = ['monitor', 'root']
+
+ self.default_puppet_tags = ['leap_base','leap_service']
+end
+
diff --git a/provider_base/README b/provider_base/README
new file mode 100644
index 00000000..bb80df50
--- /dev/null
+++ b/provider_base/README
@@ -0,0 +1,9 @@
+This directory holds the base provider files that actual providers inherit from.
+
+For example:
+
+ the file........ myproject/provider/common.json
+ inherits from... myproject/leap_platform/provider_base/common.json
+
+
+
diff --git a/provider_base/common.json b/provider_base/common.json
new file mode 100644
index 00000000..5e689109
--- /dev/null
+++ b/provider_base/common.json
@@ -0,0 +1,97 @@
+{
+ "ip_address": null,
+ "environment": null,
+ "services": [],
+ "tags": [],
+ "contacts": "= provider.contacts.default",
+ "domain": {
+ "full_suffix": "= provider.domain",
+ "internal_suffix": "= provider.domain_internal",
+ "full": "= node.name + '.' + domain.full_suffix",
+ "internal": "= node.name + '.' + domain.internal_suffix",
+ "name": "= node.name + '.' + (dns.public ? domain.full_suffix : domain.internal_suffix)"
+ },
+ "dns": {
+ "public": "= service_type != 'internal_service'"
+ },
+ "ssh": {
+ "authorized_keys": "= authorized_keys",
+ "config": {
+ "AllowTcpForwarding": "no"
+ },
+ "port": 22,
+ "mosh": {
+ "ports": "60000:61000",
+ "enabled": false
+ }
+ },
+ "hosts": "=> hosts_file",
+ "x509": {
+ "use": true,
+ "use_commercial": false,
+ "cert": "= x509.use ? file(:node_x509_cert, :missing => 'x509 certificate for node $node. Run `leap cert update`') : nil",
+ "key": "= x509.use ? file(:node_x509_key, :missing => 'x509 key for node $node. Run `leap cert update`') : nil",
+ "ca_cert": "= try_file :ca_cert",
+ "commercial_cert": "= x509.use_commercial ? file([:commercial_cert, try{webapp.domain}||domain.full_suffix], :missing => 'commercial x509 certificate for node $node. Add file $file, or run `leap cert csr --domain %s` to generate a temporary self-signed cert and CSR you can use to purchase a real cert.' % (try{webapp.domain}||domain.full_suffix)) : nil",
+ "commercial_key": "= x509.use_commercial ? file([:commercial_key, try{webapp.domain}||domain.full_suffix], :missing => 'commercial x509 certificate for node $node. Add file $file, or run `leap cert csr --domain %s` to generate a temporary self-signed cert and CSR you can use to purchase a real cert.' % (try{webapp.domain}||domain.full_suffix)) : nil",
+ "commercial_ca_cert": "= x509.use_commercial ? try_file(:commercial_ca_cert) : nil"
+ },
+ "service_type": "internal_service",
+ "development": {
+ "site_config": true
+ },
+ "name": "common",
+ "location": null,
+ "enabled": true,
+ "mail": {
+ "smarthost": "= nodes_like_me[:services => :mx].exclude(self).field('domain.full')"
+ },
+ "stunnel": {
+ "clients": {},
+ "servers": {}
+ },
+ "firewall": {
+ "ssh": {
+ "from": "sysadmin",
+ "to": "= ip_address",
+ "port": "= ssh.port"
+ },
+ "stunnel": "=> stunnel_firewall"
+ },
+ "platform": {
+ "version": "= Leap::Platform.version.to_s",
+ "major_version": "= Leap::Platform.major_version"
+ },
+ "sources": {
+ "apt": {
+ "basic": "http://httpredir.debian.org/debian/",
+ "security": "http://security.debian.org/",
+ "backports": "http://httpredir.debian.org/debian/"
+ },
+ "leap-mx": {
+ "type": "apt",
+ "package": "leap-mx",
+ "revision": "latest"
+ },
+ "nickserver": {
+ "type": "git",
+ "source": "https://leap.se/git/nickserver",
+ "revision": "origin/version/0.8"
+ },
+ "platform": {
+ "apt": {
+ "basic": "= 'http://deb.leap.se/' + Leap::Platform.major_version"
+ }
+ },
+ "soledad": {
+ "type": "apt",
+ "package": "soledad-server",
+ "revision": "latest"
+ },
+ "webapp": {
+ "type": "git",
+ "source": "https://leap.se/git/leap_web",
+ "revision": "origin/version/0.8"
+ }
+ }
+}
diff --git a/provider_base/files/branding/head.scss b/provider_base/files/branding/head.scss
new file mode 100644
index 00000000..c100a004
--- /dev/null
+++ b/provider_base/files/branding/head.scss
@@ -0,0 +1 @@
+// no head.scss set
diff --git a/provider_base/files/branding/tail.scss b/provider_base/files/branding/tail.scss
new file mode 100644
index 00000000..919aeec6
--- /dev/null
+++ b/provider_base/files/branding/tail.scss
@@ -0,0 +1 @@
+// no tail.scss set
diff --git a/provider_base/files/service-definitions/provider.json.erb b/provider_base/files/service-definitions/provider.json.erb
new file mode 100644
index 00000000..a75bea61
--- /dev/null
+++ b/provider_base/files/service-definitions/provider.json.erb
@@ -0,0 +1,16 @@
+<%=
+ # grab some fields from provider.json
+ hsh = provider.pick(
+ :languages, :description, :name, :services,
+ :enrollment_policy, :default_language, :service
+ )
+ hsh['domain'] = domain.full_suffix
+
+ hsh['api_version'] = "1"
+ hsh['api_uri'] = ["https://", api.domain, ':', api.port].join
+
+ hsh['ca_cert_uri'] = api.ca_cert_uri
+ hsh['ca_cert_fingerprint'] = fingerprint(:ca_cert)
+
+ hsh.dump_json
+%> \ No newline at end of file
diff --git a/provider_base/files/service-definitions/v1/eip-service.json.erb b/provider_base/files/service-definitions/v1/eip-service.json.erb
new file mode 100644
index 00000000..4bd220df
--- /dev/null
+++ b/provider_base/files/service-definitions/v1/eip-service.json.erb
@@ -0,0 +1,55 @@
+<%=
+ def underscore(words)
+ words = words.to_s.dup
+ words.downcase!
+ words.gsub! /[^a-z]/, '_'
+ words
+ end
+
+ def add_gateway(node, locations, options={})
+ return nil if options[:ip] == 'REQUIRED'
+ gateway = {}
+ gateway["capabilities"] = node.openvpn.pick(:ports, :protocols, :user_ips, :adblock, :filter_dns)
+ gateway["capabilities"]["transport"] = ["openvpn"]
+ gateway["host"] = node.domain.full
+ gateway["ip_address"] = options[:ip]
+ gateway["capabilities"]["limited"] = options[:limited]
+ if node['location']
+ location_name = underscore(node.location.name)
+ gateway["location"] = location_name
+ locations[location_name] ||= node.location
+ end
+ gateway
+ end
+
+ hsh = {}
+ hsh["serial"] = 1
+ hsh["version"] = 1
+ locations = {}
+ gateways = []
+ configuration = nil
+ nodes_like_me[:services => 'openvpn'].each_node do |node|
+ if node.openvpn.allow_limited && node.openvpn.allow_unlimited
+ gateways << add_gateway(node, locations, :ip => node.openvpn.gateway_address, :limited => false)
+ gateways << add_gateway(node, locations, :ip => node.openvpn.second_gateway_address, :limited => true)
+ elsif node.openvpn.allow_unlimited
+ gateways << add_gateway(node, locations, :ip => node.openvpn.gateway_address, :limited => false)
+ elsif node.openvpn.allow_limited
+ gateways << add_gateway(node, locations, :ip => node.openvpn.gateway_address, :limited => true)
+ end
+ if configuration && node.openvpn.configuration != configuration
+ log :error, "OpenVPN nodes in the environment `#{node.environment}` have conflicting `openvpn.configuration` values. This will result in bad errors."
+ end
+ configuration = node.openvpn.configuration
+ end
+ if gateways.any?
+ configuration = configuration.dup
+ if configuration['fragment'] && configuration['fragment'] == 1500
+ configuration.delete('fragment')
+ end
+ hsh["gateways"] = gateways.compact
+ hsh["locations"] = locations
+ hsh["openvpn_configuration"] = configuration
+ end
+ JSON.sorted_generate hsh
+%> \ No newline at end of file
diff --git a/provider_base/files/service-definitions/v1/smtp-service.json.erb b/provider_base/files/service-definitions/v1/smtp-service.json.erb
new file mode 100644
index 00000000..45f240ac
--- /dev/null
+++ b/provider_base/files/service-definitions/v1/smtp-service.json.erb
@@ -0,0 +1,29 @@
+<%=
+ def underscore(words)
+ words = words.to_s.dup
+ words.downcase!
+ words.gsub! /[^a-z]/, '_'
+ words
+ end
+
+ hsh = {}
+ hsh["serial"] = 1
+ hsh["version"] = 1
+ locations = {}
+ hosts = {}
+ nodes_like_me[:services => 'mx'].each_node do |node|
+ host = {}
+ host["hostname"] = node.domain.full
+ host["ip_address"] = node.ip_address
+ host["port"] = 465 # hard coded for now, later node.smtp.port
+ if node['location']
+ location_name = underscore(node.location.name)
+ host["location"] = location_name
+ locations[location_name] ||= node.location
+ end
+ hosts[node.name] = host
+ end
+ hsh["hosts"] = hosts
+ hsh["locations"] = locations
+ JSON.sorted_generate hsh
+%>
diff --git a/provider_base/files/service-definitions/v1/soledad-service.json.erb b/provider_base/files/service-definitions/v1/soledad-service.json.erb
new file mode 100644
index 00000000..0cd1c927
--- /dev/null
+++ b/provider_base/files/service-definitions/v1/soledad-service.json.erb
@@ -0,0 +1,29 @@
+<%=
+ def underscore(words)
+ words = words.to_s.dup
+ words.downcase!
+ words.gsub! /[^a-z]/, '_'
+ words
+ end
+
+ hsh = {}
+ hsh["serial"] = 1
+ hsh["version"] = 1
+ locations = {}
+ hosts = {}
+ nodes_like_me[:services => 'soledad'].each_node do |node|
+ host = {}
+ host["hostname"] = node.domain.full
+ host["ip_address"] = node.ip_address
+ host["port"] = node.soledad.port
+ if node['location']
+ location_name = underscore(node.location.name)
+ host["location"] = location_name
+ locations[location_name] ||= node.location
+ end
+ hosts[node.name] = host
+ end
+ hsh["hosts"] = hosts
+ hsh["locations"] = locations
+ JSON.sorted_generate hsh
+%> \ No newline at end of file
diff --git a/provider_base/provider.json b/provider_base/provider.json
new file mode 100644
index 00000000..81b2ea98
--- /dev/null
+++ b/provider_base/provider.json
@@ -0,0 +1,64 @@
+{
+ "domain": "REQUIRED",
+ "domain_internal": "= domain.sub(/\\.[^\\.]*$/, '.i')",
+ "name": {
+ "en": "REQUIRED"
+ },
+ "description": {
+ "en": "REQUIRED"
+ },
+ "contacts": {
+ "default": ["REQUIRED"],
+ "english": "= contacts.default.map {|email| email.split('@').join(' at the domain ')}.join(', ')"
+ },
+ "languages": ["en"],
+ "default_language": "en",
+ "enrollment_policy": "open",
+ "services": "= enabled_services",
+ "service": {
+ // bandwidth limit is in Bytes, storage limit is in MB.
+ // for example:
+ // "levels": {
+ // "1": {"name": "free", "description":"Limited service, but without cost to you.", "storage":50},
+ // "2": {"name": "basic", "description":"The standard package.", "storage":1000, "rate": {"USD":5}},
+ // "3": {"name": "pro", "description":"Extra storage for power users." , "storage":10000, "rate": {"USD":10}}
+ // }
+ "levels": {
+ "1": {
+ "name": "free", "description": "Please donate."
+ }
+ },
+ "default_service_level": 1,
+ "bandwidth_limit": 102400,
+ "allow_free": "= provider.service.levels.select {|l| l['rate'].nil?}.any?",
+ "allow_paid": "= provider.service.levels.select {|l| !l['rate'].nil?}.any?",
+ "allow_anonymous": "= provider.service.levels.select {|l| l['name'] == 'anonymous'}.any? && services.include?('openvpn')",
+ "allow_registration": "= provider.enrollment_policy != 'closed' && provider.service.levels.select {|l| l['name'] != 'anonymous'}.any?",
+ "allow_limited_bandwidth": "= provider.service.levels.select {|l| l['bandwidth'] == 'limited'}.any?",
+ "allow_unlimited_bandwidth": "= provider.service.levels.select {|l| l['bandwidth'].nil?}.any?"
+ },
+ "ca": {
+ "name": "= provider.ca.organization + ' Root CA'",
+ "organization": "= provider.name[provider.default_language]",
+ "organizational_unit": "= 'https://' + provider.domain",
+ "bit_size": 4096,
+ "digest": "SHA256",
+ "life_span": "10 years",
+ "server_certificates": {
+ "bit_size": 4096,
+ "digest": "SHA256",
+ "life_span": "1 years"
+ },
+ "client_certificates": {
+ "bit_size": 2048,
+ "digest": "SHA256",
+ "life_span": "2 months",
+ "limited_prefix": "LIMITED",
+ "unlimited_prefix": "UNLIMITED"
+ }
+ },
+ "client_version": {
+ "min": "0.7",
+ "max": null
+ }
+}
diff --git a/provider_base/services/_api_tester.json b/provider_base/services/_api_tester.json
new file mode 100644
index 00000000..790aa7d8
--- /dev/null
+++ b/provider_base/services/_api_tester.json
@@ -0,0 +1,13 @@
+//
+// This partial should be added to any service that runs tests that rely on
+// accessing the bonafide webapp API.
+//
+{
+ "testing": {
+ "monitor_auth_token": "= secret :api_monitor_auth_token",
+ "api_uri": "= global.services[:webapp].api.uri",
+ // api_hosts is not used directly, but calling hostnames() will ensure
+ // that the hostnames are added to /etc/hosts
+ "api_hosts": "= hostnames(nodes_like_me[:services => 'webapp'])"
+ }
+} \ No newline at end of file
diff --git a/provider_base/services/_couchdb_mirror.json b/provider_base/services/_couchdb_mirror.json
new file mode 100644
index 00000000..da496bae
--- /dev/null
+++ b/provider_base/services/_couchdb_mirror.json
@@ -0,0 +1,22 @@
+//
+// Applied to all non-master couchdb nodes
+// NOT CURRENTLY SUPPORTED
+//
+{
+ "stunnel": {
+ "clients": {
+ "couch_client": "= stunnel_client(nodes[couch.replication.masters.keys], couch.port)"
+ }
+ },
+ "couch": {
+ "mode": "mirror",
+ "replication": {
+ // for now, pick the first close one, or the first one.
+ // in the future, maybe use haproxy to balance among all the masters
+ "masters": "= try{pick_node(:couch_master,nodes_near_me['services' => 'couchdb']['couch.master' => true]).pick_fields('domain.internal', 'couch.port')} || try{pick_node(:couch_master,nodes_like_me['services' => 'couchdb']['couch.master' => true]).pick_fields('domain.internal', 'couch.port')}",
+ "username": "replication",
+ "password": "= secret :couch_replication_password",
+ "role": "replication"
+ }
+ }
+}
diff --git a/provider_base/services/_couchdb_multimaster.json b/provider_base/services/_couchdb_multimaster.json
new file mode 100644
index 00000000..803a9416
--- /dev/null
+++ b/provider_base/services/_couchdb_multimaster.json
@@ -0,0 +1,24 @@
+//
+// Only applied to master couchdb nodes when there are multiple masters
+// NOT CURRENTLY USED.
+{
+ "stunnel": {
+ "servers": {
+ "epmd_server": "= stunnel_server(couch.bigcouch.epmd_port)",
+ "ednp_server": "= stunnel_server(couch.bigcouch.ednp_port)"
+ },
+ "clients": {
+ "epmd_clients": "= stunnel_client(nodes_like_me['services' => 'couchdb']['couch.mode' => 'multimaster'], couch.bigcouch.epmd_port)",
+ "ednp_clients": "= stunnel_client(nodes_like_me['services' => 'couchdb']['couch.mode' => 'multimaster'], couch.bigcouch.ednp_port)"
+ }
+ },
+ "couch": {
+ "mode": "multimaster",
+ "bigcouch": {
+ "epmd_port": 4369,
+ "ednp_port": 9002,
+ "cookie": "= secret :bigcouch_cookie",
+ "neighbors": "= nodes_like_me['services' => 'couchdb']['couch.mode' => 'multimaster'].exclude(self).field('domain.full')"
+ }
+ }
+}
diff --git a/provider_base/services/couchdb.json b/provider_base/services/couchdb.json
new file mode 100644
index 00000000..30cb53d1
--- /dev/null
+++ b/provider_base/services/couchdb.json
@@ -0,0 +1,49 @@
+{
+ "x509": {
+ "use": true
+ },
+ "stunnel": {
+ "servers": {
+ "couch_server": "= stunnel_server(couch.port)"
+ }
+ },
+ "couch": {
+ "port": 5984,
+ "mode": "plain",
+ "users": {
+ "admin": {
+ "username": "admin",
+ "password": "= secret :couch_admin_password",
+ "salt": "= hex_secret :couch_admin_password_salt, 128"
+ },
+ "leap_mx": {
+ "username": "leap_mx",
+ "password": "= secret :couch_leap_mx_password",
+ "salt": "= hex_secret :couch_leap_mx_password_salt, 128"
+ },
+ "nickserver": {
+ "username": "nickserver",
+ "password": "= secret :couch_nickserver_password",
+ "salt": "= hex_secret :couch_nickserver_password_salt, 128"
+ },
+ "soledad": {
+ "username": "soledad",
+ "password": "= secret :couch_soledad_password",
+ "salt": "= hex_secret :couch_soledad_password_salt, 128"
+ },
+ "webapp": {
+ "username": "webapp",
+ "password": "= secret :couch_webapp_password",
+ "salt": "= hex_secret :couch_webapp_password_salt, 128"
+ },
+ "replication": {
+ "username": "replication",
+ "password": "= secret :couch_replication_password",
+ "salt": "= hex_secret :couch_replication_password_salt, 128"
+ }
+ },
+ "webapp": {
+ "nagios_test_pw": "= secret :nagios_test_password"
+ }
+ }
+}
diff --git a/provider_base/services/couchdb.rb b/provider_base/services/couchdb.rb
new file mode 100644
index 00000000..ba7e5ae5
--- /dev/null
+++ b/provider_base/services/couchdb.rb
@@ -0,0 +1,27 @@
+#
+# custom logic for couchdb json resolution
+# ============================================
+#
+# bigcouch is no longer maintained, so now couchdb is required...
+# no matter what!
+#
+
+if self.couch['master']
+ LeapCli::log :warning, %("The node property {couch.master:true} is deprecated.\n) +
+ %( Only {couch.mode:plain} is supported. (node #{self.name}))
+end
+
+couchdb_nodes = nodes_like_me['services' => 'couchdb']
+
+if couchdb_nodes.size > 1
+ LeapCli::log :error, "Having multiple nodes with {services:couchdb} is no longer supported (nodes #{couchdb_nodes.keys.join(', ')})."
+elsif self.couch.mode == "multimaster"
+ LeapCli::log :error, "Nodes with {couch.mode:multimaster} are no longer supported (node #{self.name})."
+end
+
+#
+# This is needed for the "test" that creates and removes the storage db
+# for test_user_email. If that test is removed, then this is no longer
+# necessary:
+#
+apply_partial('_api_tester') \ No newline at end of file
diff --git a/provider_base/services/dns.json b/provider_base/services/dns.json
new file mode 100644
index 00000000..67948ef8
--- /dev/null
+++ b/provider_base/services/dns.json
@@ -0,0 +1,14 @@
+{
+ "hosts": {
+ "public": "= nodes['dns.public' => true].fields('domain.name', 'dns.aliases', 'ip_address')",
+ "private": "= nodes['dns.public' => false].fields('domain.name', 'dns.aliases', 'ip_address')"
+ },
+ "service_type": "public_service",
+ "firewall": {
+ "dns": {
+ "from": "*",
+ "to": "= ip_address",
+ "port": "53"
+ }
+ }
+} \ No newline at end of file
diff --git a/provider_base/services/monitor.json b/provider_base/services/monitor.json
new file mode 100644
index 00000000..9ddc0ec7
--- /dev/null
+++ b/provider_base/services/monitor.json
@@ -0,0 +1,29 @@
+{
+ "nagios": {
+ "nagiosadmin_pw": "= secret :nagios_admin_password",
+ "domains_internal": "= nagios.hosts.values.map{|h|h['domain_internal_suffix']}.uniq",
+ "environments": "= Hash[ nagios.hosts.values.map{|h|h['environment']}.uniq.map{|e| [e||'default',{'contact_emails'=>manager.env(e).provider.contacts.default}]} ]",
+ "hosts": "= (self.environment == 'local' ? nodes_like_me : nodes[:environment => '!local']).pick_fields('environment', 'domain.internal', 'domain.internal_suffix', 'domain.full_suffix', 'ip_address', 'services', 'openvpn.gateway_address', 'ssh.port')"
+ },
+ "hosts": "= self.environment == 'local' ? hosts_file(nodes_like_me) : hosts_file(nodes[:environment => '!local'])",
+ "ssh": {
+ "monitor": {
+ "username": "= Leap::Platform.monitor_username",
+ "private_key": "= file(:monitor_priv_key)"
+ }
+ },
+ "x509": {
+ "use": true,
+ "use_commercial": true,
+ "ca_cert": "= file :ca_cert, :missing => 'provider CA. Run `leap cert ca`'",
+ "client_ca_cert": "= file :client_ca_cert, :missing => 'Certificate Authority. Run `leap cert ca`'",
+ "client_ca_key": "= file :client_ca_key, :missing => 'Certificate Authority. Run `leap cert ca`'"
+ },
+ "firewall": {
+ "monitor": {
+ "from": "sysadmin",
+ "to": "= ip_address",
+ "port": [443, 80]
+ }
+ }
+}
diff --git a/provider_base/services/monitor.rb b/provider_base/services/monitor.rb
new file mode 100644
index 00000000..01590d5c
--- /dev/null
+++ b/provider_base/services/monitor.rb
@@ -0,0 +1,3 @@
+unless self.services.include? "webapp"
+ LeapCli.log :error, "service `monitor` requires service `webapp` on the same node (node #{self.name})."
+end
diff --git a/provider_base/services/mx.json b/provider_base/services/mx.json
new file mode 100644
index 00000000..c7e99d85
--- /dev/null
+++ b/provider_base/services/mx.json
@@ -0,0 +1,53 @@
+{
+ "mx": {
+ // provider should define their own custom aliases.
+ // these are in *addition* to the standard reserved aliases for root and postmaster, etc.
+ "aliases": {},
+ // this is the domain that is used for the OpenPGP header
+ "key_lookup_domain": "= global.services[:webapp].webapp.domain",
+ "dkim": {
+ // bit sizes larger than 2048 are not necessarily supported
+ "bit_size": 2048,
+ "public_key": "= remote_file_path(:dkim_pub_key) { generate_dkim_key(mx.dkim.bit_size) }",
+ "private_key": "= remote_file_path(:dkim_priv_key) { generate_dkim_key(mx.dkim.bit_size) }",
+ // generate selector based on first ten digits of pub key fingerprint:
+ "selector": "= fingerprint(local_file_path(:dkim_pub_key) { generate_dkim_key(mx.dkim.bit_size) }, :mode => :rsa).slice(0,10)"
+ }
+ },
+ "stunnel": {
+ "clients": {
+ "couch_client": "= stunnel_client(nodes_like_me[:services => :couchdb], global.services[:couchdb].couch.port)"
+ }
+ },
+ "haproxy": {
+ "couch": {
+ "listen_port": 4096,
+ "servers": "= haproxy_servers(nodes_like_me[:services => :couchdb], stunnel.clients.couch_client, global.services[:couchdb].couch.port)"
+ }
+ },
+ "couchdb_leap_mx_user": {
+ "username": "= global.services[:couchdb].couch.users[:leap_mx].username",
+ "password": "= secret :couch_leap_mx_password",
+ "salt": "= hex_secret :couch_leap_mx_password_salt, 128"
+ },
+ "mynetworks": "= host_ips(nodes)",
+ "rbls": ["zen.spamhaus.org"],
+ "clamav": {
+ "whitelisted_addresses": []
+ },
+ "x509": {
+ "use": true,
+ "use_commercial": true,
+ "ca_cert": "= file :ca_cert, :missing => 'provider CA. Run `leap cert ca`'",
+ "client_ca_cert": "= file :client_ca_cert, :missing => 'Certificate Authority. Run `leap cert ca`'",
+ "client_ca_key": "= file :client_ca_key, :missing => 'Certificate Authority. Run `leap cert ca`'"
+ },
+ "service_type": "user_service",
+ "firewall": {
+ "mx": {
+ "from": "*",
+ "to": "= ip_address",
+ "port": [25, 465]
+ }
+ }
+}
diff --git a/provider_base/services/mx.rb b/provider_base/services/mx.rb
new file mode 100644
index 00000000..03ee561f
--- /dev/null
+++ b/provider_base/services/mx.rb
@@ -0,0 +1 @@
+apply_partial('_api_tester')
diff --git a/provider_base/services/obfsproxy.json b/provider_base/services/obfsproxy.json
new file mode 100644
index 00000000..979d0ef9
--- /dev/null
+++ b/provider_base/services/obfsproxy.json
@@ -0,0 +1,9 @@
+{
+ "obfsproxy": {
+ "scramblesuit": {
+ "password": "= base32_secret('scramblesuit_password_'+name)",
+ "port" : "= rand_range('scramblesuit_port_'+name, 18000..32000)"
+ },
+ "gateway_address": "= try{pick_node(:obfs_gateway,nodes_near_me['services' => 'openvpn']).pick_fields('openvpn.gateway_address')} || try{pick_node(:obfs_gateway,nodes_like_me['services' => 'openvpn']).pick_fields('openvpn.gateway_address')}"
+ }
+}
diff --git a/provider_base/services/openvpn.json b/provider_base/services/openvpn.json
new file mode 100644
index 00000000..6f73e31c
--- /dev/null
+++ b/provider_base/services/openvpn.json
@@ -0,0 +1,45 @@
+{
+ "service_type": "user_service",
+ "x509": {
+ "use": true,
+ "client_ca_cert": "= file :client_ca_cert, :missing => 'Certificate Authority. Run `leap cert ca`'",
+ "dh": "= file :dh_params, :missing => 'Diffie-Hellman parameters. Run `leap cert dh`'"
+ },
+ "location": null,
+ "openvpn": {
+ "gateway_address": "REQUIRED",
+ "second_gateway_address": "= openvpn.allow_limited && openvpn.allow_unlimited ? 'REQUIRED' : nil",
+ "ports": ["80", "443", "53", "1194"],
+ "protocols": ["tcp", "udp"],
+ "filter_dns": false,
+ "adblock": false,
+ "user_ips": false,
+ "allow_limited": "= provider.service.allow_limited_bandwidth",
+ "allow_unlimited": "= provider.service.allow_unlimited_bandwidth",
+ "limited_prefix": "= provider.ca.client_certificates.limited_prefix",
+ "unlimited_prefix": "= provider.ca.client_certificates.unlimited_prefix",
+ "rate_limit": "= openvpn.allow_limited ? provider.service.bandwidth_limit : nil",
+ "configuration": {
+ "tls-cipher": "DHE-RSA-AES128-SHA",
+ "auth": "SHA1",
+ "cipher": "AES-128-CBC",
+ "keepalive": "10 30",
+ "tun-ipv6": true,
+ "fragment": 1500
+ }
+ },
+ "obfsproxy": {
+ "scramblesuit": {
+ "password": "= base32_secret('scramblesuit_password_'+name)",
+ "port" : "= rand_range('scramblesuit_port_'+name, 18000..32000)"
+ },
+ "gateway_address": "= openvpn.gateway_address"
+ },
+ "firewall": {
+ "vpn": {
+ "from": "*",
+ "to": "= openvpn.gateway_address",
+ "port": "= openvpn.ports + [obfsproxy.scramblesuit.port]"
+ }
+ }
+}
diff --git a/provider_base/services/soledad.json b/provider_base/services/soledad.json
new file mode 100644
index 00000000..169588c8
--- /dev/null
+++ b/provider_base/services/soledad.json
@@ -0,0 +1,21 @@
+{
+ "soledad": {
+ "port": 2323,
+ "couchdb_soledad_user": {
+ "username": "= global.services[:couchdb].couch.users[:soledad].username",
+ "password": "= secret :couch_soledad_password",
+ "salt": "= hex_secret :couch_soledad_password_salt, 128"
+ },
+ "couchdb_leap_mx_user": {
+ "username": "= global.services[:couchdb].couch.users[:leap_mx].username"
+ }
+ },
+ "service_type": "public_service",
+ "firewall": {
+ "soledad": {
+ "from": "*",
+ "to": "= ip_address",
+ "port": "= soledad.port"
+ }
+ }
+}
diff --git a/provider_base/services/soledad.rb b/provider_base/services/soledad.rb
new file mode 100644
index 00000000..9b220c39
--- /dev/null
+++ b/provider_base/services/soledad.rb
@@ -0,0 +1,3 @@
+unless self.services.include? "couchdb"
+ LeapCli.log :error, "service `soledad` requires service `couchdb` on the same node (node #{self.name})."
+end
diff --git a/provider_base/services/static.json b/provider_base/services/static.json
new file mode 100644
index 00000000..2f408ec1
--- /dev/null
+++ b/provider_base/services/static.json
@@ -0,0 +1,20 @@
+{
+ "static": {
+ "formats": "=> try{static.domains.values.collect{|d| try{d.locations.values.collect{|l|l.format}} }.flatten.compact.uniq} || []",
+ // include a copy of provider.json in case any of the configured domains happens to match provider.domain
+ "bootstrap_files": {
+ "domain": "= provider.domain",
+ "enabled": "= !! try{static.domains[provider.domain]}",
+ "provider_json": "=> static.bootstrap_files.enabled ? try{nodes_like_me[:services => 'webapp'].values.first.definition_files['provider']} : nil",
+ "client_version": "= static.bootstrap_files.enabled ? provider.client_version : nil"
+ }
+ },
+ "service_type": "public_service",
+ "firewall": {
+ "static": {
+ "from": "*",
+ "to": "= ip_address",
+ "port": [80, 443]
+ }
+ }
+} \ No newline at end of file
diff --git a/provider_base/services/tor.json b/provider_base/services/tor.json
new file mode 100644
index 00000000..55d3d2ee
--- /dev/null
+++ b/provider_base/services/tor.json
@@ -0,0 +1,15 @@
+{
+ "tor": {
+ "bandwidth_rate": 6550,
+ "contacts": "= [provider.contacts['tor'] || provider.contacts.default].flatten",
+ "nickname": "= (self.name + secret(:tor_family)).sub('_','')[0..18]",
+ "family": "= nodes[:services => 'tor'][:environment => '!local'].field('tor.nickname').join(',')",
+ "hidden_service": {
+ "active": null,
+ "key_type": "RSA",
+ "public_key": "= tor_public_key_path(:node_tor_pub_key, tor.hidden_service.key_type) if tor.hidden_service.active",
+ "private_key": "= tor_private_key_path(:node_tor_priv_key, tor.hidden_service.key_type) if tor.hidden_service.active",
+ "address": "= onion_address(:node_tor_pub_key) if tor.hidden_service.active"
+ }
+ }
+}
diff --git a/provider_base/services/webapp.json b/provider_base/services/webapp.json
new file mode 100644
index 00000000..b1d2ca59
--- /dev/null
+++ b/provider_base/services/webapp.json
@@ -0,0 +1,93 @@
+{
+ "webapp": {
+ "admins": [],
+ "forbidden_usernames": [
+ "admin", "admins", "administrator", "administrators", "arin-admin",
+ "certmaster", "contact", "email", "help", "help-desk", "help-ticket",
+ "help-tickets", "help_desk", "help_ticket", "help_tickets", "helpdesk",
+ "helpticket", "helptickets", "info", "mail", "maildrop", "noreply",
+ "owner", "owners", "postmaster", "reply", "robot", "ssladmin", "staff",
+ "support", "tech-support", "tech_support", "techsupport", "ticket",
+ "tickets", "vmail", "www-data"],
+ "domain": "= provider.domain",
+ "modules": ["user", "billing", "help"],
+ "couchdb_webapp_user": "= global.services[:couchdb].couch.users[:webapp]",
+ "couchdb_admin_user": "= global.services[:couchdb].couch.users[:admin]",
+ "customization_dir": "= file_path 'webapp'",
+ "client_certificates": "= provider.ca.client_certificates",
+ "allow_limited_certs": "= provider.service.allow_limited_bandwidth",
+ "allow_unlimited_certs": "= provider.service.allow_unlimited_bandwidth",
+ "allow_anonymous_certs": "= provider.service.allow_anonymous",
+ "allow_registration": "= provider.service.allow_registration",
+ "default_service_level": "= provider.service.default_service_level",
+ "service_levels": "= service_levels()",
+ "secret_token": "= secret :webapp_secret_token",
+ "api_version": 1,
+ "secure": false,
+ "client_version": "= provider.client_version",
+ "nagios_test_user": {
+ "username": "nagios_test",
+ "password": "= secret :nagios_test_password"
+ },
+ "engines": [
+ "support"
+ ],
+ "locales": "= provider.languages",
+ "default_locale": "= provider.default_language",
+ "api_tokens": {
+ "monitor": "= secret :api_monitor_auth_token",
+ "allowed_ips": "= host_ips(nodes_like_me)"
+ }
+ },
+ "stunnel": {
+ "clients": {
+ "couch_client": "= stunnel_client(nodes_like_me[:services => :couchdb], global.services[:couchdb].couch.port)"
+ }
+ },
+ "haproxy": {
+ "couch": {
+ "listen_port": 4096,
+ "servers": "= haproxy_servers(nodes_like_me[:services => :couchdb], stunnel.clients.couch_client, global.services[:couchdb].couch.port)"
+ }
+ },
+ "definition_files": {
+ "provider": "= file :provider_json_template",
+ "eip_service": "= file [:eip_service_json_template, 'v'+webapp.api_version.to_s]",
+ "soledad_service": "= file [:soledad_service_json_template, 'v'+webapp.api_version.to_s]",
+ "smtp_service": "= file [:smtp_service_json_template, 'v'+webapp.api_version.to_s]"
+ },
+ "service_type": "public_service",
+ "api": {
+ "domain": "= 'api.' + webapp.domain",
+ "version": 1,
+ "port": 4430,
+ "ca_cert_uri": "= 'https://' + webapp.domain + '/ca.crt'",
+ "uri": "= %(https://#{api.domain}:#{api.port}/#{api.version})"
+ },
+ "nickserver": {
+ "domain": "= 'nicknym.' + domain.full_suffix",
+ "couchdb_nickserver_user": {
+ "username": "= global.services[:couchdb].couch.users[:nickserver].username",
+ "password": "= secret :couch_nickserver_password",
+ "salt": "= hex_secret :couch_nickserver_password_salt, 128"
+ },
+ "port": 6425
+ },
+ "dns": {
+ "aliases": "= [domain.full, webapp.domain, api.domain, nickserver.domain]"
+ },
+ "x509": {
+ "use": true,
+ "use_commercial": true,
+ "ca_cert": "= file :ca_cert, :missing => 'provider CA. Run `leap cert ca`'",
+ "client_ca_cert": "= file :client_ca_cert, :missing => 'Certificate Authority. Run `leap cert ca`.'",
+ "client_ca_key": "= file :client_ca_key, :missing => 'Certificate Authority. Run `leap cert ca`.'"
+ },
+ "firewall": {
+ "webapp": {
+ "from": "*",
+ "to": "= ip_address",
+ "port": "= [api.port, 443, 80, nickserver.port]"
+ }
+ }
+}
diff --git a/provider_base/tags/development.json b/provider_base/tags/development.json
new file mode 100644
index 00000000..caf18e9d
--- /dev/null
+++ b/provider_base/tags/development.json
@@ -0,0 +1,3 @@
+{
+ "environment": "development"
+} \ No newline at end of file
diff --git a/provider_base/tags/local.json b/provider_base/tags/local.json
new file mode 100644
index 00000000..48312b33
--- /dev/null
+++ b/provider_base/tags/local.json
@@ -0,0 +1,3 @@
+{
+ "environment": "local"
+} \ No newline at end of file
diff --git a/provider_base/tags/production.json b/provider_base/tags/production.json
new file mode 100644
index 00000000..ea17498f
--- /dev/null
+++ b/provider_base/tags/production.json
@@ -0,0 +1,3 @@
+{
+ "environment": "production"
+} \ No newline at end of file
diff --git a/provider_base/templates/common.json b/provider_base/templates/common.json
new file mode 100644
index 00000000..a7675b15
--- /dev/null
+++ b/provider_base/templates/common.json
@@ -0,0 +1,3 @@
+{
+ "ip_address": "REQUIRED"
+} \ No newline at end of file
diff --git a/provider_base/templates/couchdb.json b/provider_base/templates/couchdb.json
new file mode 100644
index 00000000..34b60915
--- /dev/null
+++ b/provider_base/templates/couchdb.json
@@ -0,0 +1,5 @@
+{
+ "couch": {
+ "mode": "plain"
+ }
+}
diff --git a/provider_base/templates/openvpn.json b/provider_base/templates/openvpn.json
new file mode 100644
index 00000000..cbe183e8
--- /dev/null
+++ b/provider_base/templates/openvpn.json
@@ -0,0 +1,7 @@
+{
+ "openvpn": {
+ "gateway_address": "REQUIRED",
+ "ports": ["443"],
+ "protocols": ["tcp"]
+ }
+}
diff --git a/provider_base/test/openvpn/client.ovpn.erb b/provider_base/test/openvpn/client.ovpn.erb
new file mode 100644
index 00000000..af183ef4
--- /dev/null
+++ b/provider_base/test/openvpn/client.ovpn.erb
@@ -0,0 +1,28 @@
+client
+dev tun
+remote-cert-tls server
+remote-random
+nobind
+script-security 2
+verb 3
+auth SHA1
+cipher AES-128-CBC
+tls-cipher DHE-RSA-AES128-SHA
+
+<% vpn_nodes.each_node do |node| -%>
+<%= "remote #{node.openvpn.gateway_address} 1194 udp"%>
+<% end -%>
+
+<ca>
+<%= read_file! :ca_cert -%>
+</ca>
+
+<cert>
+<%# read_file! :test_client_cert -%>
+<%= cert -%>
+</cert>
+
+<key>
+<%# read_file! :test_client_key -%>
+<%= key -%>
+</key>
diff --git a/puppet/bin/apply_on_node.sh b/puppet/bin/apply_on_node.sh
new file mode 100755
index 00000000..09e5b035
--- /dev/null
+++ b/puppet/bin/apply_on_node.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+# Script to use on a node for debugging
+# Usage: ./apply_on_node.sh <puppet parameters>
+#
+# Example: ./apply_on_node.sh --debug --verbose
+
+ROOTDIR='/srv/leap'
+PLATFORM="$ROOTDIR"
+MODULEPATH="$PLATFORM/puppet/modules"
+LOG=/var/log/leap.log
+
+# example tags to use
+#TAGS='--tags=leap_base,leap_service,leap_slow'
+#TAGS='--tags=leap_base,leap_slow'
+#TAGS='--tags=leap_base,leap_service'
+
+#######
+# Setup
+#######
+
+puppet apply -v --confdir $PLATFORM/puppet --libdir $PLATFORM/puppet/lib --modulepath=$MODULEPATH $PLATFORM/puppet/manifests/setup.pp $TAGS $@ |tee $LOG 2>&1
+
+#########
+# site.pp
+#########
+
+puppet apply -v --confdir $PLATFORM/puppet --libdir $PLATFORM/puppet/lib --modulepath=$MODULEPATH $PLATFORM/puppet/manifests/site.pp $TAGS $@ |tee $LOG 2>&1
+
+
diff --git a/puppet/hiera.yaml b/puppet/hiera.yaml
new file mode 100644
index 00000000..93448e23
--- /dev/null
+++ b/puppet/hiera.yaml
@@ -0,0 +1,15 @@
+---
+:backends:
+ - yaml
+ - puppet
+
+:logger: console
+
+:yaml:
+ :datadir: /etc/leap
+
+:hierarchy:
+ - hiera
+
+:puppet:
+ :datasource: data
diff --git a/puppet/lib/puppet/parser/functions/create_resources_hash_from.rb b/puppet/lib/puppet/parser/functions/create_resources_hash_from.rb
new file mode 100644
index 00000000..47d0df9c
--- /dev/null
+++ b/puppet/lib/puppet/parser/functions/create_resources_hash_from.rb
@@ -0,0 +1,116 @@
+#
+# create_resources_hash_from.rb
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+module Puppet::Parser::Functions
+ newfunction(:create_resources_hash_from, :type => :rvalue, :doc => <<-EOS
+Given:
+ A formatted string (to use as the resource name)
+ An array to loop through (because puppet cannot loop)
+ A hash defining the parameters for a resource
+ And optionally an hash of parameter names to add to the resource and an
+ associated formatted string that should be configured with the current
+ element of the loop array
+
+This function will return a hash of hashes that can be used with the
+create_resources function.
+
+*Examples:*
+ $allowed_hosts = ['10.0.0.0/8', '192.168.0.0/24']
+ $resource_name = "100 allow %s to apache on ports 80"
+ $my_resource_hash = {
+ 'proto' => 'tcp',
+ 'action' => 'accept',
+ 'dport' => 80
+ }
+ $dynamic_parameters = {
+ 'source' => '%s'
+ }
+
+ $created_resource_hash = create_resources_hash_from($resource_name, $allowed_hosts, $my_resource_hash, $dynamic_parameters)
+
+$created_resource_hash would equal:
+ {
+ '100 allow 10.0.0.0/8 to apache on ports 80' => {
+ 'proto' => 'tcp',
+ 'action' => 'accept',
+ 'dport' => 80,
+ 'source' => '10.0.0.0/8'
+ },
+ '100 allow 192.168.0.0/24 to apache on ports 80' => {
+ 'proto' => 'tcp',
+ 'action' => 'accept',
+ 'dport' => 80,
+ 'source' => '192.168.0.0/24'
+ }
+ }
+
+$created_resource_hash could then be used with create_resources
+
+ create_resources(firewall, $created_resource_hash)
+
+To create a bunch of resources in a way that would only otherwise be possible
+with a loop of some description.
+ EOS
+ ) do |arguments|
+
+ raise Puppet::ParseError, "create_resources_hash_from(): Wrong number of arguments " +
+ "given (#{arguments.size} for 3 or 4)" if arguments.size < 3 or arguments.size > 4
+
+ formatted_string = arguments[0]
+
+ unless formatted_string.is_a?(String)
+ raise(Puppet::ParseError, 'create_resources_hash_from(): first argument must be a string')
+ end
+
+ loop_array = arguments[1]
+
+ unless loop_array.is_a?(Array)
+ raise(Puppet::ParseError, 'create_resources_hash_from(): second argument must be an array')
+ end
+
+ resource_hash = arguments[2]
+ unless resource_hash.is_a?(Hash)
+ raise(Puppet::ParseError, 'create_resources_hash_from(): third argument must be a hash')
+ end
+
+ if arguments.size == 4
+ dynamic_parameters = arguments[3]
+ unless dynamic_parameters.is_a?(Hash)
+ raise(Puppet::ParseError, 'create_resources_hash_from(): fourth argument must be a hash')
+ end
+ end
+
+ result = {}
+
+ loop_array.each do |i|
+ my_resource_hash = resource_hash.clone
+ if dynamic_parameters
+ dynamic_parameters.each do |param, value|
+ if my_resource_hash.member?(param)
+ raise(Puppet::ParseError, "create_resources_hash_from(): dynamic_parameter '#{param}' already exists in resource hash")
+ end
+ my_resource_hash[param] = sprintf(value,[i])
+ end
+ end
+ result[sprintf(formatted_string,[i])] = my_resource_hash
+ end
+
+ result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
+# encoding: utf-8
diff --git a/puppet/lib/puppet/parser/functions/sorted_json.rb b/puppet/lib/puppet/parser/functions/sorted_json.rb
new file mode 100644
index 00000000..605da00e
--- /dev/null
+++ b/puppet/lib/puppet/parser/functions/sorted_json.rb
@@ -0,0 +1,47 @@
+#
+# Written by Gavin Mogan, from https://gist.github.com/halkeye/2287885
+# Put in the public domain by the author.
+#
+
+require 'json'
+
+def sorted_json(obj)
+ case obj
+ when String, Fixnum, Float, TrueClass, FalseClass, NilClass
+ return obj.to_json
+ when Array
+ arrayRet = []
+ obj.each do |a|
+ arrayRet.push(sorted_json(a))
+ end
+ return "[" << arrayRet.join(',') << "]";
+ when Hash
+ ret = []
+ obj.keys.sort.each do |k|
+ ret.push(k.to_json << ":" << sorted_json(obj[k]))
+ end
+ return "{" << ret.join(",") << "}";
+ else
+ raise Exception("Unable to handle object of type <%s>" % obj.class.to_s)
+ end
+end
+
+module Puppet::Parser::Functions
+ newfunction(:sorted_json, :type => :rvalue, :doc => <<-EOS
+This function takes data, outputs making sure the hash keys are sorted
+
+*Examples:*
+
+ sorted_json({'key'=>'value'})
+
+Would return: {'key':'value'}
+ EOS
+ ) do |arguments|
+ raise(Puppet::ParseError, "sorted_json(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size != 1
+
+ json = arguments[0]
+ return sorted_json(json)
+ end
+end
+
diff --git a/puppet/lib/puppet/parser/functions/sorted_yaml.rb b/puppet/lib/puppet/parser/functions/sorted_yaml.rb
new file mode 100644
index 00000000..46cd46ce
--- /dev/null
+++ b/puppet/lib/puppet/parser/functions/sorted_yaml.rb
@@ -0,0 +1,400 @@
+# encoding: UTF-8
+#
+# provides sorted_yaml() function, using Ya2YAML.
+# see https://github.com/afunai/ya2yaml
+#
+
+class Ya2YAML
+ #
+ # Author:: Akira FUNAI
+ # Copyright:: Copyright (c) 2006-2010 Akira FUNAI
+ # License:: MIT License
+ #
+
+ def initialize(opts = {})
+ options = opts.dup
+ options[:indent_size] = 2 if options[:indent_size].to_i <= 0
+ options[:minimum_block_length] = 0 if options[:minimum_block_length].to_i <= 0
+ options.update(
+ {
+ :printable_with_syck => true,
+ :escape_b_specific => true,
+ :escape_as_utf8 => true,
+ }
+ ) if options[:syck_compatible]
+
+ @options = options
+ end
+
+ def _ya2yaml(obj)
+ #raise 'set $KCODE to "UTF8".' if (RUBY_VERSION < '1.9.0') && ($KCODE != 'UTF8')
+ if (RUBY_VERSION < '1.9.0')
+ $KCODE = 'UTF8'
+ end
+ '--- ' + emit(obj, 1) + "\n"
+ rescue SystemStackError
+ raise ArgumentError, "ya2yaml can't handle circular references"
+ end
+
+ private
+
+ def emit(obj, level)
+ case obj
+ when Array
+ if (obj.length == 0)
+ '[]'
+ else
+ indent = "\n" + s_indent(level - 1)
+ ###
+ ### NOTE: a minor modification to normal Ya2YAML...
+ ### We want arrays to be output in sorted order, not just
+ ### Hashes.
+ ###
+ #obj.collect {|o|
+ # indent + '- ' + emit(o, level + 1)
+ #}.join('')
+ obj.sort {|a,b| a.to_s <=> b.to_s}.collect {|o|
+ indent + '- ' + emit(o, level + 1)
+ }.join('')
+ end
+ when Hash
+ if (obj.length == 0)
+ '{}'
+ else
+ indent = "\n" + s_indent(level - 1)
+ hash_order = @options[:hash_order]
+ if (hash_order && level == 1)
+ hash_keys = obj.keys.sort {|x, y|
+ x_order = hash_order.index(x) ? hash_order.index(x) : Float::MAX
+ y_order = hash_order.index(y) ? hash_order.index(y) : Float::MAX
+ o = (x_order <=> y_order)
+ (o != 0) ? o : (x.to_s <=> y.to_s)
+ }
+ elsif @options[:preserve_order]
+ hash_keys = obj.keys
+ else
+ hash_keys = obj.keys.sort {|x, y| x.to_s <=> y.to_s }
+ end
+ hash_keys.collect {|k|
+ key = emit(k, level + 1)
+ if (
+ is_one_plain_line?(key) ||
+ key =~ /\A(#{REX_BOOL}|#{REX_FLOAT}|#{REX_INT}|#{REX_NULL})\z/x
+ )
+ indent + key + ': ' + emit(obj[k], level + 1)
+ else
+ indent + '? ' + key +
+ indent + ': ' + emit(obj[k], level + 1)
+ end
+ }.join('')
+ end
+ when NilClass
+ '~'
+ when String
+ emit_string(obj, level)
+ when TrueClass, FalseClass
+ obj.to_s
+ when Fixnum, Bignum, Float
+ obj.to_s
+ when Date
+ obj.to_s
+ when Time
+ offset = obj.gmtoff
+ off_hm = sprintf(
+ '%+.2d:%.2d',
+ (offset / 3600.0).to_i,
+ (offset % 3600.0) / 60
+ )
+ u_sec = (obj.usec != 0) ? sprintf(".%.6d", obj.usec) : ''
+ obj.strftime("%Y-%m-%d %H:%M:%S#{u_sec} #{off_hm}")
+ when Symbol
+ '!ruby/symbol ' + emit_string(obj.to_s, level)
+ when Range
+ '!ruby/range ' + obj.to_s
+ when Regexp
+ '!ruby/regexp ' + obj.inspect
+ else
+ case
+ when obj.is_a?(Struct)
+ struct_members = {}
+ obj.each_pair{|k, v| struct_members[k.to_s] = v }
+ '!ruby/struct:' + obj.class.to_s.sub(/^(Struct::(.+)|.*)$/, '\2') + ' ' +
+ emit(struct_members, level + 1)
+ else
+ # serialized as a generic object
+ object_members = {}
+ obj.instance_variables.each{|k, v|
+ object_members[k.to_s.sub(/^@/, '')] = obj.instance_variable_get(k)
+ }
+ '!ruby/object:' + obj.class.to_s + ' ' +
+ emit(object_members, level + 1)
+ end
+ end
+ end
+
+ def emit_string(str, level)
+ (is_string, is_printable, is_one_line, is_one_plain_line) = string_type(str)
+ if is_string
+ if is_printable
+ if is_one_plain_line
+ emit_simple_string(str, level)
+ else
+ (is_one_line || str.length < @options[:minimum_block_length]) ?
+ emit_quoted_string(str, level) :
+ emit_block_string(str, level)
+ end
+ else
+ emit_quoted_string(str, level)
+ end
+ else
+ emit_base64_binary(str, level)
+ end
+ end
+
+ def emit_simple_string(str, level)
+ str
+ end
+
+ def emit_block_string(str, level)
+ str = normalize_line_break(str)
+
+ indent = s_indent(level)
+ indentation_indicator = (str =~ /\A /) ? indent.size.to_s : ''
+ str =~ /(#{REX_NORMAL_LB}*)\z/
+ chomping_indicator = case $1.length
+ when 0
+ '-'
+ when 1
+ ''
+ else
+ '+'
+ end
+
+ str.chomp!
+ str.gsub!(/#{REX_NORMAL_LB}/) {
+ $1 + indent
+ }
+ '|' + indentation_indicator + chomping_indicator + "\n" + indent + str
+ end
+
+ def emit_quoted_string(str, level)
+ str = yaml_escape(normalize_line_break(str))
+ if (str.length < @options[:minimum_block_length])
+ str.gsub!(/#{REX_NORMAL_LB}/) { ESCAPE_SEQ_LB[$1] }
+ else
+ str.gsub!(/#{REX_NORMAL_LB}$/) { ESCAPE_SEQ_LB[$1] }
+ str.gsub!(/(#{REX_NORMAL_LB}+)(.)/) {
+ trail_c = $3
+ $1 + trail_c.sub(/([\t ])/) { ESCAPE_SEQ_WS[$1] }
+ }
+ indent = s_indent(level)
+ str.gsub!(/#{REX_NORMAL_LB}/) {
+ ESCAPE_SEQ_LB[$1] + "\\\n" + indent
+ }
+ end
+ '"' + str + '"'
+ end
+
+ def emit_base64_binary(str, level)
+ indent = "\n" + s_indent(level)
+ base64 = [str].pack('m')
+ '!binary |' + indent + base64.gsub(/\n(?!\z)/, indent)
+ end
+
+ def string_type(str)
+ if str.respond_to?(:encoding) && (!str.valid_encoding? || str.encoding == Encoding::ASCII_8BIT)
+ return false, false, false, false
+ end
+ (ucs_codes = str.unpack('U*')) rescue (
+ # ArgumentError -> binary data
+ return false, false, false, false
+ )
+ if (
+ @options[:printable_with_syck] &&
+ str =~ /\A#{REX_ANY_LB}* | #{REX_ANY_LB}*\z|#{REX_ANY_LB}{2}\z/
+ )
+ # detour Syck bug
+ return true, false, nil, false
+ end
+ ucs_codes.each {|ucs_code|
+ return true, false, nil, false unless is_printable?(ucs_code)
+ }
+ return true, true, is_one_line?(str), is_one_plain_line?(str)
+ end
+
+ def is_printable?(ucs_code)
+ # YAML 1.1 / 4.1.1.
+ (
+ [0x09, 0x0a, 0x0d, 0x85].include?(ucs_code) ||
+ (ucs_code <= 0x7e && ucs_code >= 0x20) ||
+ (ucs_code <= 0xd7ff && ucs_code >= 0xa0) ||
+ (ucs_code <= 0xfffd && ucs_code >= 0xe000) ||
+ (ucs_code <= 0x10ffff && ucs_code >= 0x10000)
+ ) &&
+ !(
+ # treat LS/PS as non-printable characters
+ @options[:escape_b_specific] &&
+ (ucs_code == 0x2028 || ucs_code == 0x2029)
+ )
+ end
+
+ def is_one_line?(str)
+ str !~ /#{REX_ANY_LB}(?!\z)/
+ end
+
+ def is_one_plain_line?(str)
+ # YAML 1.1 / 4.6.11.
+ str !~ /^([\-\?:,\[\]\{\}\#&\*!\|>'"%@`\s]|---|\.\.\.)/ &&
+ str !~ /[:\#\s\[\]\{\},]/ &&
+ str !~ /#{REX_ANY_LB}/ &&
+ str !~ /^(#{REX_BOOL}|#{REX_FLOAT}|#{REX_INT}|#{REX_MERGE}
+ |#{REX_NULL}|#{REX_TIMESTAMP}|#{REX_VALUE})$/x
+ end
+
+ def s_indent(level)
+ # YAML 1.1 / 4.2.2.
+ ' ' * (level * @options[:indent_size])
+ end
+
+ def normalize_line_break(str)
+ # YAML 1.1 / 4.1.4.
+ str.gsub(/(#{REX_CRLF}|#{REX_CR}|#{REX_NEL})/, "\n")
+ end
+
+ def yaml_escape(str)
+ # YAML 1.1 / 4.1.6.
+ str.gsub(/[^a-zA-Z0-9]/u) {|c|
+ ucs_code, = (c.unpack('U') rescue [??])
+ case
+ when ESCAPE_SEQ[c]
+ ESCAPE_SEQ[c]
+ when is_printable?(ucs_code)
+ c
+ when @options[:escape_as_utf8]
+ c.respond_to?(:bytes) ?
+ c.bytes.collect {|b| '\\x%.2x' % b }.join :
+ '\\x' + c.unpack('H2' * c.size).join('\\x')
+ when ucs_code == 0x2028 || ucs_code == 0x2029
+ ESCAPE_SEQ_LB[c]
+ when ucs_code <= 0x7f
+ sprintf('\\x%.2x', ucs_code)
+ when ucs_code <= 0xffff
+ sprintf('\\u%.4x', ucs_code)
+ else
+ sprintf('\\U%.8x', ucs_code)
+ end
+ }
+ end
+
+ module Constants
+ UCS_0X85 = [0x85].pack('U') # c285@UTF8 Unicode next line
+ UCS_0XA0 = [0xa0].pack('U') # c2a0@UTF8 Unicode non-breaking space
+ UCS_0X2028 = [0x2028].pack('U') # e280a8@UTF8 Unicode line separator
+ UCS_0X2029 = [0x2029].pack('U') # e280a9@UTF8 Unicode paragraph separator
+
+ # non-break characters
+ ESCAPE_SEQ = {
+ "\x00" => '\\0',
+ "\x07" => '\\a',
+ "\x08" => '\\b',
+ "\x0b" => '\\v',
+ "\x0c" => '\\f',
+ "\x1b" => '\\e',
+ "\"" => '\\"',
+ "\\" => '\\\\',
+ }
+
+ # non-breaking space
+ ESCAPE_SEQ_NS = {
+ UCS_0XA0 => '\\_',
+ }
+
+ # white spaces
+ ESCAPE_SEQ_WS = {
+ "\x09" => '\\t',
+ " " => '\\x20',
+ }
+
+ # line breaks
+ ESCAPE_SEQ_LB ={
+ "\x0a" => '\\n',
+ "\x0d" => '\\r',
+ UCS_0X85 => '\\N',
+ UCS_0X2028 => '\\L',
+ UCS_0X2029 => '\\P',
+ }
+
+ # regexps for line breaks
+ REX_LF = Regexp.escape("\x0a")
+ REX_CR = Regexp.escape("\x0d")
+ REX_CRLF = Regexp.escape("\x0d\x0a")
+ REX_NEL = Regexp.escape(UCS_0X85)
+ REX_LS = Regexp.escape(UCS_0X2028)
+ REX_PS = Regexp.escape(UCS_0X2029)
+
+ REX_ANY_LB = /(#{REX_LF}|#{REX_CR}|#{REX_NEL}|#{REX_LS}|#{REX_PS})/
+ REX_NORMAL_LB = /(#{REX_LF}|#{REX_LS}|#{REX_PS})/
+
+ # regexps for language-Independent types for YAML1.1
+ REX_BOOL = /
+ y|Y|yes|Yes|YES|n|N|no|No|NO
+ |true|True|TRUE|false|False|FALSE
+ |on|On|ON|off|Off|OFF
+ /x
+ REX_FLOAT = /
+ [-+]?([0-9][0-9_]*)?\.[0-9.]*([eE][-+][0-9]+)? # (base 10)
+ |[-+]?[0-9][0-9_]*(:[0-5]?[0-9])+\.[0-9_]* # (base 60)
+ |[-+]?\.(inf|Inf|INF) # (infinity)
+ |\.(nan|NaN|NAN) # (not a number)
+ /x
+ REX_INT = /
+ [-+]?0b[0-1_]+ # (base 2)
+ |[-+]?0[0-7_]+ # (base 8)
+ |[-+]?(0|[1-9][0-9_]*) # (base 10)
+ |[-+]?0x[0-9a-fA-F_]+ # (base 16)
+ |[-+]?[1-9][0-9_]*(:[0-5]?[0-9])+ # (base 60)
+ /x
+ REX_MERGE = /
+ <<
+ /x
+ REX_NULL = /
+ ~ # (canonical)
+ |null|Null|NULL # (English)
+ | # (Empty)
+ /x
+ REX_TIMESTAMP = /
+ [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] # (ymd)
+ |[0-9][0-9][0-9][0-9] # (year)
+ -[0-9][0-9]? # (month)
+ -[0-9][0-9]? # (day)
+ ([Tt]|[ \t]+)[0-9][0-9]? # (hour)
+ :[0-9][0-9] # (minute)
+ :[0-9][0-9] # (second)
+ (\.[0-9]*)? # (fraction)
+ (([ \t]*)Z|[-+][0-9][0-9]?(:[0-9][0-9])?)? # (time zone)
+ /x
+ REX_VALUE = /
+ =
+ /x
+ end
+
+ include Constants
+end
+
+module Puppet::Parser::Functions
+ newfunction(:sorted_yaml,
+ :type => :rvalue,
+ :doc => "This function outputs yaml, but ensures the keys are sorted."
+ ) do |arguments|
+
+ if arguments.is_a?(Array)
+ if arguments.size != 1
+ raise(Puppet::ParseError, "sorted_yaml(): Wrong number of arguments given (#{arguments.size} for 1)")
+ end
+ yaml = arguments.first
+ else
+ yaml = arguments
+ end
+ return Ya2YAML.new()._ya2yaml(yaml)
+ end
+end
diff --git a/puppet/manifests/site.pp b/puppet/manifests/site.pp
new file mode 100644
index 00000000..ecda4012
--- /dev/null
+++ b/puppet/manifests/site.pp
@@ -0,0 +1,60 @@
+# set a default exec path
+# the logoutput exec parameter defaults to "on_error" in puppet 3,
+# but to "false" in puppet 2.7, so we need to set this globally here
+Exec {
+ logoutput => on_failure,
+ path => '/usr/bin:/usr/sbin/:/bin:/sbin:/usr/local/bin:/usr/local/sbin'
+}
+
+Package <| provider == 'apt' |> {
+ install_options => ['--no-install-recommends'],
+}
+
+$services = hiera('services', [])
+$services_str = join($services, ', ')
+notice("Services for ${fqdn}: ${services_str}")
+
+# In the default deployment case, we want to run an 'apt-get dist-upgrade'
+# to ensure the latest packages are installed. This is done by including the
+# class 'site_config::slow' here. However, you only changed a small bit of
+# the platform and want to skip this slow part of deployment, you can do that
+# by using 'leap deploy --fast' which will only apply those resources that are
+# tagged with 'leap_base' or 'leap_service'.
+# See https://leap.se/en/docs/platform/details/under-the-hood#tags
+include site_config::slow
+
+if member($services, 'openvpn') {
+ include site_openvpn
+}
+
+if member($services, 'couchdb') {
+ include site_couchdb
+}
+
+if member($services, 'webapp') {
+ include site_webapp
+}
+
+if member($services, 'soledad') {
+ include soledad::server
+}
+
+if member($services, 'monitor') {
+ include site_nagios
+}
+
+if member($services, 'tor') {
+ include site_tor
+}
+
+if member($services, 'mx') {
+ include site_mx
+}
+
+if member($services, 'static') {
+ include site_static
+}
+
+if member($services, 'obfsproxy') {
+ include site_obfsproxy
+}
diff --git a/puppet/modules/apache/.gitignore b/puppet/modules/apache/.gitignore
new file mode 100644
index 00000000..cb918d8c
--- /dev/null
+++ b/puppet/modules/apache/.gitignore
@@ -0,0 +1,6 @@
+.tmp_*~
+.librarian
+.tmp
+spec/fixtures/modules
+spec/fixtures/manifests
+*.lock
diff --git a/puppet/modules/apache/.rspec b/puppet/modules/apache/.rspec
new file mode 100644
index 00000000..8c18f1ab
--- /dev/null
+++ b/puppet/modules/apache/.rspec
@@ -0,0 +1,2 @@
+--format documentation
+--color
diff --git a/puppet/modules/apache/Gemfile b/puppet/modules/apache/Gemfile
new file mode 100644
index 00000000..b1fc9814
--- /dev/null
+++ b/puppet/modules/apache/Gemfile
@@ -0,0 +1,13 @@
+source 'https://rubygems.org'
+
+if ENV.key?('PUPPET_VERSION')
+ puppetversion = "~> #{ENV['PUPPET_VERSION']}"
+else
+ puppetversion = ['>= 3.3.1']
+end
+
+gem 'puppet', puppetversion
+gem 'puppet-lint', '>=0.3.2'
+gem 'puppetlabs_spec_helper', '>=0.2.0'
+gem 'rake', '>=0.9.2.2'
+gem 'librarian-puppet', '>=0.9.10'
diff --git a/puppet/modules/apache/LICENSE b/puppet/modules/apache/LICENSE
new file mode 100644
index 00000000..94a9ed02
--- /dev/null
+++ b/puppet/modules/apache/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/puppet/modules/apache/Puppetfile b/puppet/modules/apache/Puppetfile
new file mode 100644
index 00000000..86d58ae6
--- /dev/null
+++ b/puppet/modules/apache/Puppetfile
@@ -0,0 +1,15 @@
+# empty
+
+forge 'https://forgeapi.puppetlabs.com'
+
+mod 'shorewall', :git => 'https://git-ipuppet.immerda.ch/module-shorewall'
+mod 'templatewlv', :git => 'https://git-ipuppet.immerda.ch/module-templatewlv'
+mod 'mod_security', :git => 'https://git-ipuppet.immerda.ch/module-mod_security'
+mod 'mod_fcgid', :git => 'https://git-ipuppet.immerda.ch/module-mod_fcgid'
+mod 'php', :git => 'https://git-ipuppet.immerda.ch/module-php'
+mod 'perl', :git => 'https://git-ipuppet.immerda.ch/module-perl'
+mod 'scl', :git => 'https://git-ipuppet.immerda.ch/module-scl'
+mod 'yum', :git => 'https://git-ipuppet.immerda.ch/module-yum'
+mod 'puppetlabs-stdlib'
+mod 'puppetlabs-concat'
+#mod 'munin', :git => 'https://git-ipuppet.immerda.ch/module-munin'
diff --git a/puppet/modules/apache/README.md b/puppet/modules/apache/README.md
new file mode 100644
index 00000000..331c85b0
--- /dev/null
+++ b/puppet/modules/apache/README.md
@@ -0,0 +1,233 @@
+Puppet module for managing an Apache web server
+===============================================
+
+This module tries to manage apache on different distros in a similar manner. a
+few additional directories have to be created as well some configuration files
+have to be deployed to fit this schema.
+
+! Upgrade Notices !
+
+ * The $ssl_cipher_suite has been evaluated from the `cert` module in the
+ past, but is now a hardcoded default for the sake of reducing dependency
+ to other modules. If you were using the `cert` module before, you should
+ pass this parameter when declaring the apache class !
+
+ * this module now only works with puppet 2.7 or newer
+
+ * this module now uses parameterized classes, if you were using global
+ variables before, you need to change the class declarations in your manifests
+
+ * this module now requires the stdlib module
+
+ * this module no longer requires the common module
+
+ * if using the munin module, you need a version of the munin module that is
+ at or newer than commit 77e0a70999a8c4c20ee8d9eb521b927c525ac653 (Feb 28, 2013)
+
+ * if using munin, you will need to have the perl module installed
+
+ * you must change your modules/site-apache to modules/site_apache
+
+ * the $apache_no_default_site variable is no longer supported, you should
+ switch to passing the parameter "no_default_site => true" to the apache class
+
+ * the $use_munin variable is no longer supported, you should switch to
+ passing the parameter 'manage_munin' to the apache class
+
+ * the $use_shorewall variable is no longer supported, you should switch to
+ passing the parameter 'manage_shorewall' to the apache class
+
+ * if you were using apache::vhost::file, or apache::vhost::template, there is a
+ wrapper called apache::vhost now that takes a $vhost_mode (either the default
+ 'template', or 'file), although you can continue to use the longer defines
+
+ * Previously, apache::config::file resources would require the source to be a
+ full source specification, this is no longer needed, so please change any:
+
+ source => "puppet:///modules/site-apache/blah"
+
+ to be:
+
+ source => "modules/site-apache/blah"
+
+
+Requirements
+------------
+
+ * puppet 2.7 or newer
+ * stdlib module
+ * templatewlv module
+ * facter >= 2.2
+ because we check for $::operatingsystemmajrelease on multiple places.
+ In Debian wheezy, facter needs to get upgraded from wheezy-backports.
+ The facter version of Debian jessie is new enough.
+
+Usage
+=====
+
+Installing Apache
+-----------------
+
+To install Apache, simply include the 'apache' class in your manifests:
+
+ include apache
+
+This will give you a basic managed setup. You can pass a couple parameters to the
+class to have the module do some things for you:
+
+ * manage_shorewall: If you have the shorewall module installed and are using
+ it then rules will be automatically defined for you to let traffic come from
+ the exterior into the web server via port 80, and also 443 if you're using
+ the apache::ssl class. (Default: false)
+
+ * manage_munin: If you have the munin module installed and are using it, then
+ some apache graphs will be configured for you. (Default: false)
+
+ * no_default_site: If you do not want the 0-default.conf and
+ 0-default_ssl.conf virtualhosts automatically created in your node
+ configuration. (Default: false)
+
+ * ssl: If you want to install Apache SSL support enabled, just pass this
+ parameter (Default: false)
+
+For example:
+
+ class { 'apache':
+ manage_shorewall => true,
+ manage_munin => true,
+ no_default_site => true,
+ ssl => true
+ }
+
+You can install the ITK worker model to enforce stronger, per-user security:
+
+ include apache::itk
+
+On CentOS you can include 'apache::itk_plus' to get that mode. Not currently
+implemented for other operating systems
+
+You can combine SSL support and the ITK worker model by including both classes.
+
+
+Configuring Apache
+------------------
+
+To deploy a configuration files to the conf.d or include.d directory under
+Apache's config directory, you can use the following:
+
+ apache::config::file { 'filename':
+ content => 'Alias /thisApplication /usr/share/thisApplication/htdocs',
+ }
+
+by default this will deploy a conf.d global configuration file called 'filename'
+with that content.
+
+You can pass the parameter 'type => include' to add includes for vhosts
+
+
+To manage users in an htpasswd file:
+
+ apache::htpasswd_user { "joe@$domain":
+ ensure => present, # default: present
+ site => "$domain", # default: 'absent' - will use $name
+ username => 'joe', # default: 'absent' - will use $name
+ password => "pass",
+ password_iscrypted => false, # default: false - will sha1 hash the value
+ path => 'absent' # default: 'absent' - /var/www/htpasswds/${site}
+ }
+
+This will place an encrypted version of "pass" for user joe into
+/var/www/htpasswds/${site}
+
+You will need to make sure that ${site} exists before this is done, see the
+apache::vhost class below for how this is done.
+
+VirtualHost files
+-----------------
+
+vhosts can be added with the apache::vhost define.
+
+You can ship a flat file containing the configuration, or a template. That is
+controlled by the 'vhost_mode' parameter, which can be either 'file', or
+'template' (default).
+
+Unless specified, the source will be automatically pulled from
+modules/site_apache/{templates,files}/vhosts.d, searched in this order:
+
+ "puppet:///modules/site_apache/vhosts.d/${::fqdn}/${name}.conf",
+ "puppet:///modules/site_apache/vhosts.d/{$apache::cluster_node}/${name}.conf",
+ "puppet:///modules/site_apache/vhosts.d/${::operatingsystem}.${::operatingsystemmajrelease}/${name}.conf",
+ "puppet:///modules/site_apache/vhosts.d/${::operatingsystem}/${name}.conf",
+ "puppet:///modules/site_apache/vhosts.d/${name}.conf",
+
+otherwise you can pass a 'content' parameter to configure a template location that
+it should be pulled from, or a 'vhost_source' parameter to specify the file source.
+
+For example:
+
+This would deploy a the vhost for $domain, pulled from a file from the sources
+listed above:
+
+ apache::vhost { "$domain": vhost_mode => 'file' }
+
+ apache::vhost { "$domain":
+ vhost_mode => 'file',
+ vhost_source => 'modules/site_configs/vhosts.d/${name}.conf"
+ }
+
+There are multiple other additional configurables that you can pass to each
+vhost definition:
+
+* logmode:
+ - default: Do normal logging to CustomLog and ErrorLog
+ - nologs: Send every logging to /dev/null
+ - anonym: Don't log ips for CustomLog, send ErrorLog to /dev/null
+ - semianonym: Don't log ips for CustomLog, log normal ErrorLog
+
+* run_mode: controls in which mode the vhost should be run, there are different setups
+ possible:
+ - normal: (*default*) run vhost with the current active worker (default: prefork) don't
+ setup anything special
+ - itk: run vhost with the mpm_itk module (Incompatibility: cannot be used in combination
+ with 'proxy-itk' & 'static-itk' mode)
+ - proxy-itk: run vhost with a dual prefork/itk setup, where prefork just proxies all the
+ requests for the itk setup, that listens only on the loobpack device.
+ (Incompatibility: cannot be used in combination with the itk setup.)
+ - static-itk: run vhost with a dual prefork/itk setup, where prefork serves all the static
+ content and proxies the dynamic calls to the itk setup, that listens only on
+ the loobpack device (Incompatibility: cannot be used in combination with
+ 'itk' mode)
+
+* mod_security: Whether we use mod_security or not (will include mod_security module)
+ - false: (*default*) don't activate mod_security
+ - true: activate mod_security
+
+For templates, you can pass various parameters that will automatically configure
+the template accordingly (such as php_options and php_settings). Please see
+manifests/vhost/template.pp for the full list.
+
+There are various pre-made vhost configurations that use good defaults that you can use:
+
+- apache::vhost::gitweb - sets up a gitweb vhost
+- apache::vhost::modperl - uses modperl, with optional fastcgi
+- apache::vhost::passenger - setup passenger
+- apache::vhost::proxy - setup a proxy vhost
+- apache::vhost::redirect - vhost to redirect hosts
+- apache::vhost::static - a static vhost
+- apache::vhost::webdav - for managing webdave accessible targets
+
+Additionally, for php sites, there are several handy pre-made vhost configurations:
+
+- apache::vhost::php::drupal
+- apache::vhost::php::gallery2
+- apache::vhost::php::global_exec_bin_dir
+- apache::vhost::php::joomla
+- apache::vhost::php::mediawiki
+- apache::vhost::php::safe_mode_bin
+- apache::vhost::php::silverstripe
+- apache::vhost::php::simplemachine
+- apache::vhost::php::spip
+- apache::vhost::php::standard
+- apache::vhost::php::typo3
+- apache::vhost::php::webapp
+- apache::vhost::php::wordpress
diff --git a/puppet/modules/apache/Rakefile b/puppet/modules/apache/Rakefile
new file mode 100644
index 00000000..ec1c52b3
--- /dev/null
+++ b/puppet/modules/apache/Rakefile
@@ -0,0 +1,26 @@
+require 'bundler'
+Bundler.require(:rake)
+
+require 'puppetlabs_spec_helper/rake_tasks'
+require 'puppet-lint/tasks/puppet-lint'
+
+Rake::Task[:lint].clear
+PuppetLint::RakeTask.new :lint do |config|
+ config.ignore_paths = ["spec/**/*.pp", "vendor/**/*.pp"]
+ config.log_format = '%{path}:%{linenumber}:%{KIND}: %{message}'
+ config.disable_checks = [ "class_inherits_from_params_class", "80chars" ]
+end
+
+# use librarian-puppet to manage fixtures instead of .fixtures.yml
+# offers more possibilities like explicit version management, forge downloads,...
+task :librarian_spec_prep do
+ sh "librarian-puppet install --path=spec/fixtures/modules/"
+ pwd = `pwd`.strip
+ unless File.directory?("#{pwd}/spec/fixtures/modules/apache")
+ sh "ln -s #{pwd} #{pwd}/spec/fixtures/modules/apache"
+ end
+end
+task :spec_prep => :librarian_spec_prep
+
+
+task :default => [:spec, :lint]
diff --git a/puppet/modules/apache/files/conf.d/CentOS/ssl.conf b/puppet/modules/apache/files/conf.d/CentOS/ssl.conf
new file mode 100644
index 00000000..7f9be957
--- /dev/null
+++ b/puppet/modules/apache/files/conf.d/CentOS/ssl.conf
@@ -0,0 +1,76 @@
+#
+# This is the Apache server configuration file providing SSL support.
+# It contains the configuration directives to instruct the server how to
+# serve pages over an https connection. For detailing information about these
+# directives see <URL:http://httpd.apache.org/docs/2.2/mod/mod_ssl.html>
+#
+# Do NOT simply read the instructions in here without understanding
+# what they do. They're here only as hints or reminders. If you are unsure
+# consult the online docs. You have been warned.
+#
+
+LoadModule ssl_module modules/mod_ssl.so
+
+#
+# When we also provide SSL we have to listen to the
+# the HTTPS port in addition.
+#
+Listen 443
+NameVirtualHost *:443
+
+##
+## SSL Global Context
+##
+## All SSL configuration in this context applies both to
+## the main server and all SSL-enabled virtual hosts.
+##
+
+#
+# Some MIME-types for downloading Certificates and CRLs
+#
+AddType application/x-x509-ca-cert .crt
+AddType application/x-pkcs7-crl .crl
+
+# Pass Phrase Dialog:
+# Configure the pass phrase gathering process.
+# The filtering dialog program (`builtin' is a internal
+# terminal dialog) has to provide the pass phrase on stdout.
+SSLPassPhraseDialog builtin
+
+# Inter-Process Session Cache:
+# Configure the SSL Session Cache: First the mechanism
+# to use and second the expiring timeout (in seconds).
+#SSLSessionCache dc:UNIX:/var/cache/mod_ssl/distcache
+SSLSessionCache shmcb:/var/cache/mod_ssl/scache(512000)
+SSLSessionCacheTimeout 300
+
+# Semaphore:
+# Configure the path to the mutual exclusion semaphore the
+# SSL engine uses internally for inter-process synchronization.
+SSLMutex default
+
+# Pseudo Random Number Generator (PRNG):
+# Configure one or more sources to seed the PRNG of the
+# SSL library. The seed data should be of good random quality.
+# WARNING! On some platforms /dev/random blocks if not enough entropy
+# is available. This means you then cannot use the /dev/random device
+# because it would lead to very long connection times (as long as
+# it requires to make more entropy available). But usually those
+# platforms additionally provide a /dev/urandom device which doesn't
+# block. So, if available, use this one instead. Read the mod_ssl User
+# Manual for more details.
+SSLRandomSeed startup file:/dev/urandom 256
+SSLRandomSeed connect builtin
+#SSLRandomSeed startup file:/dev/random 512
+#SSLRandomSeed connect file:/dev/random 512
+#SSLRandomSeed connect file:/dev/urandom 512
+
+#
+# Use "SSLCryptoDevice" to enable any supported hardware
+# accelerators. Use "openssl engine -v" to list supported
+# engine names. NOTE: If you enable an accelerator and the
+# server does not start, consult the error logs and ensure
+# your accelerator is functioning properly.
+#
+SSLCryptoDevice builtin
+#SSLCryptoDevice ubsec
diff --git a/puppet/modules/apache/files/conf.d/CentOS/welcome.conf b/puppet/modules/apache/files/conf.d/CentOS/welcome.conf
new file mode 100644
index 00000000..7d7b0cd6
--- /dev/null
+++ b/puppet/modules/apache/files/conf.d/CentOS/welcome.conf
@@ -0,0 +1,10 @@
+#
+# This configuration file enables the default "Welcome"
+# page if there is no default index page present for
+# the root URL. To disable the Welcome page, comment
+# out all the lines below.
+#
+#<LocationMatch "^/+$">
+# Options -Indexes
+# ErrorDocument 403 /error/noindex.html
+#</LocationMatch>
diff --git a/puppet/modules/apache/files/conf.d/Debian/charset b/puppet/modules/apache/files/conf.d/Debian/charset
new file mode 100644
index 00000000..40d7198b
--- /dev/null
+++ b/puppet/modules/apache/files/conf.d/Debian/charset
@@ -0,0 +1,6 @@
+# Read the documentation before enabling AddDefaultCharset.
+# In general, it is only a good idea if you know that all your files
+# have this encoding. It will override any encoding given in the files
+# in meta http-equiv or xml encoding tags.
+
+#AddDefaultCharset UTF-8
diff --git a/puppet/modules/apache/files/conf.d/Debian/security b/puppet/modules/apache/files/conf.d/Debian/security
new file mode 100644
index 00000000..55b3e519
--- /dev/null
+++ b/puppet/modules/apache/files/conf.d/Debian/security
@@ -0,0 +1,50 @@
+#
+# Disable access to the entire file system except for the directories that
+# are explicitly allowed later.
+#
+# This currently breaks the configurations that come with some web application
+# Debian packages. It will be made the default for the release after lenny.
+#
+#<Directory />
+# AllowOverride None
+# Order Deny,Allow
+# Deny from all
+#</Directory>
+
+
+# Changing the following options will not really affect the security of the
+# server, but might make attacks slightly more difficult in some cases.
+
+#
+# ServerTokens
+# This directive configures what you return as the Server HTTP response
+# Header. The default is 'Full' which sends information about the OS-Type
+# and compiled in modules.
+# Set to one of: Full | OS | Minimal | Minor | Major | Prod
+# where Full conveys the most information, and Prod the least.
+#
+#ServerTokens Minimal
+ServerTokens Full
+
+#
+# Optionally add a line containing the server version and virtual host
+# name to server-generated pages (internal error documents, FTP directory
+# listings, mod_status and mod_info output etc., but not CGI generated
+# documents or custom error documents).
+# Set to "EMail" to also include a mailto: link to the ServerAdmin.
+# Set to one of: On | Off | EMail
+#
+#ServerSignature Off
+ServerSignature On
+
+#
+# Allow TRACE method
+#
+# Set to "extended" to also reflect the request body (only for testing and
+# diagnostic purposes).
+#
+# Set to one of: On | Off | extended
+#
+#TraceEnable Off
+TraceEnable On
+
diff --git a/puppet/modules/apache/files/conf.d/Debian/ssl.conf b/puppet/modules/apache/files/conf.d/Debian/ssl.conf
new file mode 100644
index 00000000..bcfe8201
--- /dev/null
+++ b/puppet/modules/apache/files/conf.d/Debian/ssl.conf
@@ -0,0 +1 @@
+NameVirtualHost *:443
diff --git a/puppet/modules/apache/files/conf.d/do_includes.conf b/puppet/modules/apache/files/conf.d/do_includes.conf
new file mode 100644
index 00000000..f44d9d4a
--- /dev/null
+++ b/puppet/modules/apache/files/conf.d/do_includes.conf
@@ -0,0 +1,5 @@
+#
+# Add index.shtml to the list of files that will be served as directory
+# indexes.
+#
+DirectoryIndex index.shtml
diff --git a/puppet/modules/apache/files/conf.d/git.conf b/puppet/modules/apache/files/conf.d/git.conf
new file mode 100644
index 00000000..c03ee2b5
--- /dev/null
+++ b/puppet/modules/apache/files/conf.d/git.conf
@@ -0,0 +1,5 @@
+# deny access to git repository folders
+<DirectoryMatch .*\.git/.*>
+ Order allow,deny
+ Deny From All
+</DirectoryMatch>
diff --git a/puppet/modules/apache/files/conf.d/mozilla_autoconfig.conf b/puppet/modules/apache/files/conf.d/mozilla_autoconfig.conf
new file mode 100644
index 00000000..6e4f7db8
--- /dev/null
+++ b/puppet/modules/apache/files/conf.d/mozilla_autoconfig.conf
@@ -0,0 +1,6 @@
+Alias /.well-known/autoconfig/mail/config-v1.1.xml /var/www/autoconfig/config.shtml
+<Directory /var/www/autoconfig/>
+ Options +Includes
+ AddType application/xml .shtml
+ AddOutputFilter INCLUDES .shtml
+</Directory>
diff --git a/puppet/modules/apache/files/conf.d/status.conf b/puppet/modules/apache/files/conf.d/status.conf
new file mode 100644
index 00000000..fb706cc1
--- /dev/null
+++ b/puppet/modules/apache/files/conf.d/status.conf
@@ -0,0 +1,24 @@
+###########################################################
+### this file is managed by PUPPET ####
+### only modify it in puppet repo or you will ####
+### loose the changes ! ####
+###########################################################
+
+# Allow server status reports generated by mod_status,
+# with the URL of http://servername/server-status
+<Location /server-status>
+ SetHandler server-status
+ Order deny,allow
+ Deny from all
+ Allow from 127.0.0.1
+
+ <IfModule mod_security2.c>
+ SecRuleEngine Off
+ </IfModule>
+</Location>
+
+# ExtendedStatus controls whether Apache will generate "full" status
+# information (ExtendedStatus On) or just basic information (ExtendedStatus
+# Off) when the "server-status" handler is called.
+ExtendedStatus On
+
diff --git a/puppet/modules/apache/files/conf.d/vhosts.conf b/puppet/modules/apache/files/conf.d/vhosts.conf
new file mode 100644
index 00000000..86485501
--- /dev/null
+++ b/puppet/modules/apache/files/conf.d/vhosts.conf
@@ -0,0 +1,8 @@
+###########################################################
+### this file is managed by PUPPET ####
+### only modify it in puppet repo or you will ####
+### loose the changes ! ####
+###########################################################
+
+NameVirtualHost *:80
+Include vhosts.d/*.conf
diff --git a/puppet/modules/apache/files/config/Debian.jessie/apache2.conf b/puppet/modules/apache/files/config/Debian.jessie/apache2.conf
new file mode 100644
index 00000000..7b1f96f5
--- /dev/null
+++ b/puppet/modules/apache/files/config/Debian.jessie/apache2.conf
@@ -0,0 +1,221 @@
+# This is the main Apache server configuration file. It contains the
+# configuration directives that give the server its instructions.
+# See http://httpd.apache.org/docs/2.4/ for detailed information about
+# the directives and /usr/share/doc/apache2/README.Debian about Debian specific
+# hints.
+#
+#
+# Summary of how the Apache 2 configuration works in Debian:
+# The Apache 2 web server configuration in Debian is quite different to
+# upstream's suggested way to configure the web server. This is because Debian's
+# default Apache2 installation attempts to make adding and removing modules,
+# virtual hosts, and extra configuration directives as flexible as possible, in
+# order to make automating the changes and administering the server as easy as
+# possible.
+
+# It is split into several files forming the configuration hierarchy outlined
+# below, all located in the /etc/apache2/ directory:
+#
+# /etc/apache2/
+# |-- apache2.conf
+# | `-- ports.conf
+# |-- mods-enabled
+# | |-- *.load
+# | `-- *.conf
+# |-- conf-enabled
+# | `-- *.conf
+# `-- sites-enabled
+# `-- *.conf
+#
+#
+# * apache2.conf is the main configuration file (this file). It puts the pieces
+# together by including all remaining configuration files when starting up the
+# web server.
+#
+# * ports.conf is always included from the main configuration file. It is
+# supposed to determine listening ports for incoming connections which can be
+# customized anytime.
+#
+# * Configuration files in the mods-enabled/, conf-enabled/ and sites-enabled/
+# directories contain particular configuration snippets which manage modules,
+# global configuration fragments, or virtual host configurations,
+# respectively.
+#
+# They are activated by symlinking available configuration files from their
+# respective *-available/ counterparts. These should be managed by using our
+# helpers a2enmod/a2dismod, a2ensite/a2dissite and a2enconf/a2disconf. See
+# their respective man pages for detailed information.
+#
+# * The binary is called apache2. Due to the use of environment variables, in
+# the default configuration, apache2 needs to be started/stopped with
+# /etc/init.d/apache2 or apache2ctl. Calling /usr/bin/apache2 directly will not
+# work with the default configuration.
+
+
+# Global configuration
+#
+
+#
+# ServerRoot: The top of the directory tree under which the server's
+# configuration, error, and log files are kept.
+#
+# NOTE! If you intend to place this on an NFS (or otherwise network)
+# mounted filesystem then please read the Mutex documentation (available
+# at <URL:http://httpd.apache.org/docs/2.4/mod/core.html#mutex>);
+# you will save yourself a lot of trouble.
+#
+# Do NOT add a slash at the end of the directory path.
+#
+#ServerRoot "/etc/apache2"
+
+#
+# The accept serialization lock file MUST BE STORED ON A LOCAL DISK.
+#
+Mutex file:${APACHE_LOCK_DIR} default
+
+#
+# PidFile: The file in which the server should record its process
+# identification number when it starts.
+# This needs to be set in /etc/apache2/envvars
+#
+PidFile ${APACHE_PID_FILE}
+
+#
+# Timeout: The number of seconds before receives and sends time out.
+#
+Timeout 300
+
+#
+# KeepAlive: Whether or not to allow persistent connections (more than
+# one request per connection). Set to "Off" to deactivate.
+#
+KeepAlive On
+
+#
+# MaxKeepAliveRequests: The maximum number of requests to allow
+# during a persistent connection. Set to 0 to allow an unlimited amount.
+# We recommend you leave this number high, for maximum performance.
+#
+MaxKeepAliveRequests 100
+
+#
+# KeepAliveTimeout: Number of seconds to wait for the next request from the
+# same client on the same connection.
+#
+KeepAliveTimeout 5
+
+
+# These need to be set in /etc/apache2/envvars
+User ${APACHE_RUN_USER}
+Group ${APACHE_RUN_GROUP}
+
+#
+# HostnameLookups: Log the names of clients or just their IP addresses
+# e.g., www.apache.org (on) or 204.62.129.132 (off).
+# The default is off because it'd be overall better for the net if people
+# had to knowingly turn this feature on, since enabling it means that
+# each client request will result in AT LEAST one lookup request to the
+# nameserver.
+#
+HostnameLookups Off
+
+# ErrorLog: The location of the error log file.
+# If you do not specify an ErrorLog directive within a <VirtualHost>
+# container, error messages relating to that virtual host will be
+# logged here. If you *do* define an error logfile for a <VirtualHost>
+# container, that host's errors will be logged there and not here.
+#
+ErrorLog ${APACHE_LOG_DIR}/error.log
+
+#
+# LogLevel: Control the severity of messages logged to the error_log.
+# Available values: trace8, ..., trace1, debug, info, notice, warn,
+# error, crit, alert, emerg.
+# It is also possible to configure the log level for particular modules, e.g.
+# "LogLevel info ssl:warn"
+#
+LogLevel warn
+
+# Include module configuration:
+IncludeOptional mods-enabled/*.load
+IncludeOptional mods-enabled/*.conf
+
+# Include list of ports to listen on
+Include ports.conf
+
+
+# Sets the default security model of the Apache2 HTTPD server. It does
+# not allow access to the root filesystem outside of /usr/share and /var/www.
+# The former is used by web applications packaged in Debian,
+# the latter may be used for local directories served by the web server. If
+# your system is serving content from a sub-directory in /srv you must allow
+# access here, or in any related virtual host.
+<Directory />
+ Options FollowSymLinks
+ AllowOverride None
+ Require all denied
+</Directory>
+
+<Directory /usr/share>
+ AllowOverride None
+ Require all granted
+</Directory>
+
+<Directory /var/www/>
+ Options Indexes FollowSymLinks
+ AllowOverride None
+ Require all granted
+</Directory>
+
+#<Directory /srv/>
+# Options Indexes FollowSymLinks
+# AllowOverride None
+# Require all granted
+#</Directory>
+
+
+
+
+# AccessFileName: The name of the file to look for in each directory
+# for additional configuration directives. See also the AllowOverride
+# directive.
+#
+AccessFileName .htaccess
+
+#
+# The following lines prevent .htaccess and .htpasswd files from being
+# viewed by Web clients.
+#
+<FilesMatch "^\.ht">
+ Require all denied
+</FilesMatch>
+
+
+#
+# The following directives define some format nicknames for use with
+# a CustomLog directive.
+#
+# These deviate from the Common Log Format definitions in that they use %O
+# (the actual bytes sent including headers) instead of %b (the size of the
+# requested file), because the latter makes it impossible to detect partial
+# requests.
+#
+# Note that the use of %{X-Forwarded-For}i instead of %h is not recommended.
+# Use mod_remoteip instead.
+#
+LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
+LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
+LogFormat "%h %l %u %t \"%r\" %>s %O" common
+LogFormat "%{Referer}i -> %U" referer
+LogFormat "%{User-agent}i" agent
+
+# Include of directories ignores editors' and dpkg's backup files,
+# see README.Debian for details.
+
+# Include generic snippets of statements
+IncludeOptional conf-enabled/*.conf
+
+# Include the virtual host configurations:
+IncludeOptional sites-enabled/*.conf
+
+# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
diff --git a/puppet/modules/apache/files/config/Debian.wheezy/apache2.conf b/puppet/modules/apache/files/config/Debian.wheezy/apache2.conf
new file mode 100644
index 00000000..50545671
--- /dev/null
+++ b/puppet/modules/apache/files/config/Debian.wheezy/apache2.conf
@@ -0,0 +1,268 @@
+# This is the main Apache server configuration file. It contains the
+# configuration directives that give the server its instructions.
+# See http://httpd.apache.org/docs/2.2/ for detailed information about
+# the directives and /usr/share/doc/apache2-common/README.Debian.gz about
+# Debian specific hints.
+#
+#
+# Summary of how the Apache 2 configuration works in Debian:
+# The Apache 2 web server configuration in Debian is quite different to
+# upstream's suggested way to configure the web server. This is because Debian's
+# default Apache2 installation attempts to make adding and removing modules,
+# virtual hosts, and extra configuration directives as flexible as possible, in
+# order to make automating the changes and administering the server as easy as
+# possible.
+
+# It is split into several files forming the configuration hierarchy outlined
+# below, all located in the /etc/apache2/ directory:
+#
+# /etc/apache2/
+# |-- apache2.conf
+# | `-- ports.conf
+# |-- mods-enabled
+# | |-- *.load
+# | `-- *.conf
+# |-- conf.d
+# | `-- *
+# `-- sites-enabled
+# `-- *
+#
+#
+# * apache2.conf is the main configuration file (this file). It puts the pieces
+# together by including all remaining configuration files when starting up the
+# web server.
+#
+# In order to avoid conflicts with backup files, the Include directive is
+# adapted to ignore files that:
+# - do not begin with a letter or number
+# - contain a character that is neither letter nor number nor _-:.
+# - contain .dpkg
+#
+# Yet we strongly suggest that all configuration files either end with a
+# .conf or .load suffix in the file name. The next Debian release will
+# ignore files not ending with .conf (or .load for mods-enabled).
+#
+# * ports.conf is always included from the main configuration file. It is
+# supposed to determine listening ports for incoming connections, and which
+# of these ports are used for name based virtual hosts.
+#
+# * Configuration files in the mods-enabled/ and sites-enabled/ directories
+# contain particular configuration snippets which manage modules or virtual
+# host configurations, respectively.
+#
+# They are activated by symlinking available configuration files from their
+# respective *-available/ counterparts. These should be managed by using our
+# helpers a2enmod/a2dismod, a2ensite/a2dissite. See
+# their respective man pages for detailed information.
+#
+# * Configuration files in the conf.d directory are either provided by other
+# packages or may be added by the local administrator. Local additions
+# should start with local- or end with .local.conf to avoid name clashes. All
+# files in conf.d are considered (excluding the exceptions noted above) by
+# the Apache 2 web server.
+#
+# * The binary is called apache2. Due to the use of environment variables, in
+# the default configuration, apache2 needs to be started/stopped with
+# /etc/init.d/apache2 or apache2ctl. Calling /usr/bin/apache2 directly will not
+# work with the default configuration.
+
+
+# Global configuration
+#
+
+#
+# ServerRoot: The top of the directory tree under which the server's
+# configuration, error, and log files are kept.
+#
+# NOTE! If you intend to place this on an NFS (or otherwise network)
+# mounted filesystem then please read the LockFile documentation (available
+# at <URL:http://httpd.apache.org/docs/2.2/mod/mpm_common.html#lockfile>);
+# you will save yourself a lot of trouble.
+#
+# Do NOT add a slash at the end of the directory path.
+#
+#ServerRoot "/etc/apache2"
+
+#
+# The accept serialization lock file MUST BE STORED ON A LOCAL DISK.
+#
+LockFile ${APACHE_LOCK_DIR}/accept.lock
+
+#
+# PidFile: The file in which the server should record its process
+# identification number when it starts.
+# This needs to be set in /etc/apache2/envvars
+#
+PidFile ${APACHE_PID_FILE}
+
+#
+# Timeout: The number of seconds before receives and sends time out.
+#
+Timeout 300
+
+#
+# KeepAlive: Whether or not to allow persistent connections (more than
+# one request per connection). Set to "Off" to deactivate.
+#
+KeepAlive On
+
+#
+# MaxKeepAliveRequests: The maximum number of requests to allow
+# during a persistent connection. Set to 0 to allow an unlimited amount.
+# We recommend you leave this number high, for maximum performance.
+#
+MaxKeepAliveRequests 100
+
+#
+# KeepAliveTimeout: Number of seconds to wait for the next request from the
+# same client on the same connection.
+#
+KeepAliveTimeout 5
+
+##
+## Server-Pool Size Regulation (MPM specific)
+##
+
+# prefork MPM
+# StartServers: number of server processes to start
+# MinSpareServers: minimum number of server processes which are kept spare
+# MaxSpareServers: maximum number of server processes which are kept spare
+# MaxClients: maximum number of server processes allowed to start
+# MaxRequestsPerChild: maximum number of requests a server process serves
+<IfModule mpm_prefork_module>
+ StartServers 5
+ MinSpareServers 5
+ MaxSpareServers 10
+ MaxClients 150
+ MaxRequestsPerChild 0
+</IfModule>
+
+# worker MPM
+# StartServers: initial number of server processes to start
+# MinSpareThreads: minimum number of worker threads which are kept spare
+# MaxSpareThreads: maximum number of worker threads which are kept spare
+# ThreadLimit: ThreadsPerChild can be changed to this maximum value during a
+# graceful restart. ThreadLimit can only be changed by stopping
+# and starting Apache.
+# ThreadsPerChild: constant number of worker threads in each server process
+# MaxClients: maximum number of simultaneous client connections
+# MaxRequestsPerChild: maximum number of requests a server process serves
+<IfModule mpm_worker_module>
+ StartServers 2
+ MinSpareThreads 25
+ MaxSpareThreads 75
+ ThreadLimit 64
+ ThreadsPerChild 25
+ MaxClients 150
+ MaxRequestsPerChild 0
+</IfModule>
+
+# event MPM
+# StartServers: initial number of server processes to start
+# MinSpareThreads: minimum number of worker threads which are kept spare
+# MaxSpareThreads: maximum number of worker threads which are kept spare
+# ThreadsPerChild: constant number of worker threads in each server process
+# MaxClients: maximum number of simultaneous client connections
+# MaxRequestsPerChild: maximum number of requests a server process serves
+<IfModule mpm_event_module>
+ StartServers 2
+ MinSpareThreads 25
+ MaxSpareThreads 75
+ ThreadLimit 64
+ ThreadsPerChild 25
+ MaxClients 150
+ MaxRequestsPerChild 0
+</IfModule>
+
+# These need to be set in /etc/apache2/envvars
+User ${APACHE_RUN_USER}
+Group ${APACHE_RUN_GROUP}
+
+#
+# AccessFileName: The name of the file to look for in each directory
+# for additional configuration directives. See also the AllowOverride
+# directive.
+#
+
+AccessFileName .htaccess
+
+#
+# The following lines prevent .htaccess and .htpasswd files from being
+# viewed by Web clients.
+#
+<Files ~ "^\.ht">
+ Order allow,deny
+ Deny from all
+ Satisfy all
+</Files>
+
+#
+# DefaultType is the default MIME type the server will use for a document
+# if it cannot otherwise determine one, such as from filename extensions.
+# If your server contains mostly text or HTML documents, "text/plain" is
+# a good value. If most of your content is binary, such as applications
+# or images, you may want to use "application/octet-stream" instead to
+# keep browsers from trying to display binary files as though they are
+# text.
+#
+# It is also possible to omit any default MIME type and let the
+# client's browser guess an appropriate action instead. Typically the
+# browser will decide based on the file's extension then. In cases
+# where no good assumption can be made, letting the default MIME type
+# unset is suggested instead of forcing the browser to accept
+# incorrect metadata.
+#
+DefaultType None
+
+
+#
+# HostnameLookups: Log the names of clients or just their IP addresses
+# e.g., www.apache.org (on) or 204.62.129.132 (off).
+# The default is off because it'd be overall better for the net if people
+# had to knowingly turn this feature on, since enabling it means that
+# each client request will result in AT LEAST one lookup request to the
+# nameserver.
+#
+HostnameLookups Off
+
+# ErrorLog: The location of the error log file.
+# If you do not specify an ErrorLog directive within a <VirtualHost>
+# container, error messages relating to that virtual host will be
+# logged here. If you *do* define an error logfile for a <VirtualHost>
+# container, that host's errors will be logged there and not here.
+#
+ErrorLog ${APACHE_LOG_DIR}/error.log
+
+#
+# LogLevel: Control the number of messages logged to the error_log.
+# Possible values include: debug, info, notice, warn, error, crit,
+# alert, emerg.
+#
+LogLevel warn
+
+# Include module configuration:
+Include mods-enabled/*.load
+Include mods-enabled/*.conf
+
+# Include list of ports to listen on and which to use for name based vhosts
+Include ports.conf
+
+#
+# The following directives define some format nicknames for use with
+# a CustomLog directive (see below).
+# If you are behind a reverse proxy, you might want to change %h into %{X-Forwarded-For}i
+#
+LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
+LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
+LogFormat "%h %l %u %t \"%r\" %>s %O" common
+LogFormat "%{Referer}i -> %U" referer
+LogFormat "%{User-agent}i" agent
+
+# Include of directories ignores editors' and dpkg's backup files,
+# see the comments above for details.
+
+# Include generic snippets of statements
+Include conf.d/
+
+# Include the virtual host configurations:
+Include sites-enabled/
diff --git a/puppet/modules/apache/files/config/Debian/apache2.conf b/puppet/modules/apache/files/config/Debian/apache2.conf
new file mode 100644
index 00000000..1e97b4eb
--- /dev/null
+++ b/puppet/modules/apache/files/config/Debian/apache2.conf
@@ -0,0 +1,230 @@
+#
+# Based upon the NCSA server configuration files originally by Rob McCool.
+#
+# This is the main Apache server configuration file. It contains the
+# configuration directives that give the server its instructions.
+# See http://httpd.apache.org/docs/2.2/ for detailed information about
+# the directives.
+#
+# Do NOT simply read the instructions in here without understanding
+# what they do. They're here only as hints or reminders. If you are unsure
+# consult the online docs. You have been warned.
+#
+# The configuration directives are grouped into three basic sections:
+# 1. Directives that control the operation of the Apache server process as a
+# whole (the 'global environment').
+# 2. Directives that define the parameters of the 'main' or 'default' server,
+# which responds to requests that aren't handled by a virtual host.
+# These directives also provide default values for the settings
+# of all virtual hosts.
+# 3. Settings for virtual hosts, which allow Web requests to be sent to
+# different IP addresses or hostnames and have them handled by the
+# same Apache server process.
+#
+# Configuration and logfile names: If the filenames you specify for many
+# of the server's control files begin with "/" (or "drive:/" for Win32), the
+# server will use that explicit path. If the filenames do *not* begin
+# with "/", the value of ServerRoot is prepended -- so "foo.log"
+# with ServerRoot set to "/etc/apache2" will be interpreted by the
+# server as "/etc/apache2/foo.log".
+#
+
+### Section 1: Global Environment
+#
+# The directives in this section affect the overall operation of Apache,
+# such as the number of concurrent requests it can handle or where it
+# can find its configuration files.
+#
+
+#
+# ServerRoot: The top of the directory tree under which the server's
+# configuration, error, and log files are kept.
+#
+# NOTE! If you intend to place this on an NFS (or otherwise network)
+# mounted filesystem then please read the LockFile documentation (available
+# at <URL:http://httpd.apache.org/docs/2.2/mod/mpm_common.html#lockfile>);
+# you will save yourself a lot of trouble.
+#
+# Do NOT add a slash at the end of the directory path.
+#
+#ServerRoot "/etc/apache2"
+
+#
+# The accept serialization lock file MUST BE STORED ON A LOCAL DISK.
+#
+LockFile ${APACHE_LOCK_DIR}/accept.lock
+
+#
+# PidFile: The file in which the server should record its process
+# identification number when it starts.
+# This needs to be set in /etc/apache2/envvars
+#
+PidFile ${APACHE_PID_FILE}
+
+#
+# Timeout: The number of seconds before receives and sends time out.
+#
+Timeout 300
+
+#
+# KeepAlive: Whether or not to allow persistent connections (more than
+# one request per connection). Set to "Off" to deactivate.
+#
+KeepAlive On
+
+#
+# MaxKeepAliveRequests: The maximum number of requests to allow
+# during a persistent connection. Set to 0 to allow an unlimited amount.
+# We recommend you leave this number high, for maximum performance.
+#
+MaxKeepAliveRequests 100
+
+#
+# KeepAliveTimeout: Number of seconds to wait for the next request from the
+# same client on the same connection.
+#
+KeepAliveTimeout 15
+
+##
+## Server-Pool Size Regulation (MPM specific)
+##
+
+# prefork MPM
+# StartServers: number of server processes to start
+# MinSpareServers: minimum number of server processes which are kept spare
+# MaxSpareServers: maximum number of server processes which are kept spare
+# MaxClients: maximum number of server processes allowed to start
+# MaxRequestsPerChild: maximum number of requests a server process serves
+<IfModule mpm_prefork_module>
+ StartServers 5
+ MinSpareServers 5
+ MaxSpareServers 10
+ MaxClients 150
+ MaxRequestsPerChild 0
+</IfModule>
+
+# worker MPM
+# StartServers: initial number of server processes to start
+# MaxClients: maximum number of simultaneous client connections
+# MinSpareThreads: minimum number of worker threads which are kept spare
+# MaxSpareThreads: maximum number of worker threads which are kept spare
+# ThreadLimit: ThreadsPerChild can be changed to this maximum value during a
+# graceful restart. ThreadLimit can only be changed by stopping
+# and starting Apache.
+# ThreadsPerChild: constant number of worker threads in each server process
+# MaxRequestsPerChild: maximum number of requests a server process serves
+<IfModule mpm_worker_module>
+ StartServers 2
+ MinSpareThreads 25
+ MaxSpareThreads 75
+ ThreadLimit 64
+ ThreadsPerChild 25
+ MaxClients 150
+ MaxRequestsPerChild 0
+</IfModule>
+
+# event MPM
+# StartServers: initial number of server processes to start
+# MaxClients: maximum number of simultaneous client connections
+# MinSpareThreads: minimum number of worker threads which are kept spare
+# MaxSpareThreads: maximum number of worker threads which are kept spare
+# ThreadsPerChild: constant number of worker threads in each server process
+# MaxRequestsPerChild: maximum number of requests a server process serves
+<IfModule mpm_event_module>
+ StartServers 2
+ MaxClients 150
+ MinSpareThreads 25
+ MaxSpareThreads 75
+ ThreadLimit 64
+ ThreadsPerChild 25
+ MaxRequestsPerChild 0
+</IfModule>
+
+# These need to be set in /etc/apache2/envvars
+User ${APACHE_RUN_USER}
+Group ${APACHE_RUN_GROUP}
+
+#
+# AccessFileName: The name of the file to look for in each directory
+# for additional configuration directives. See also the AllowOverride
+# directive.
+#
+
+AccessFileName .htaccess
+
+#
+# The following lines prevent .htaccess and .htpasswd files from being
+# viewed by Web clients.
+#
+<Files ~ "^\.ht">
+ Order allow,deny
+ Deny from all
+ Satisfy all
+</Files>
+
+#
+# DefaultType is the default MIME type the server will use for a document
+# if it cannot otherwise determine one, such as from filename extensions.
+# If your server contains mostly text or HTML documents, "text/plain" is
+# a good value. If most of your content is binary, such as applications
+# or images, you may want to use "application/octet-stream" instead to
+# keep browsers from trying to display binary files as though they are
+# text.
+#
+DefaultType text/plain
+
+
+#
+# HostnameLookups: Log the names of clients or just their IP addresses
+# e.g., www.apache.org (on) or 204.62.129.132 (off).
+# The default is off because it'd be overall better for the net if people
+# had to knowingly turn this feature on, since enabling it means that
+# each client request will result in AT LEAST one lookup request to the
+# nameserver.
+#
+HostnameLookups Off
+
+# ErrorLog: The location of the error log file.
+# If you do not specify an ErrorLog directive within a <VirtualHost>
+# container, error messages relating to that virtual host will be
+# logged here. If you *do* define an error logfile for a <VirtualHost>
+# container, that host's errors will be logged there and not here.
+#
+ErrorLog ${APACHE_LOG_DIR}/error.log
+
+#
+# LogLevel: Control the number of messages logged to the error_log.
+# Possible values include: debug, info, notice, warn, error, crit,
+# alert, emerg.
+#
+LogLevel warn
+
+# Include module configuration:
+Include mods-enabled/*.load
+Include mods-enabled/*.conf
+
+# Include all the user configurations:
+Include httpd.conf
+
+# Include ports listing
+Include ports.conf
+
+#
+# The following directives define some format nicknames for use with
+# a CustomLog directive (see below).
+# If you are behind a reverse proxy, you might want to change %h into %{X-Forwarded-For}i
+#
+LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
+LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
+LogFormat "%h %l %u %t \"%r\" %>s %O" common
+LogFormat "%{Referer}i -> %U" referer
+LogFormat "%{User-agent}i" agent
+
+# Include of directories ignores editors' and dpkg's backup files,
+# see README.Debian for details.
+
+# Include generic snippets of statements
+Include conf.d/
+
+# Include the virtual host configurations:
+Include sites-enabled/
diff --git a/puppet/modules/apache/files/config/OpenBSD/httpd.conf b/puppet/modules/apache/files/config/OpenBSD/httpd.conf
new file mode 100644
index 00000000..09e452e6
--- /dev/null
+++ b/puppet/modules/apache/files/config/OpenBSD/httpd.conf
@@ -0,0 +1,1120 @@
+# $OpenBSD: httpd.conf,v 1.22 2008/01/25 09:59:57 sthen Exp $
+#
+# Based upon the NCSA server configuration files originally by Rob McCool.
+#
+# This is the main Apache server configuration file. It contains the
+# configuration directives that give the server its instructions.
+# See <URL:http://www.apache.org/docs/> for detailed information about
+# the directives.
+#
+# Do NOT simply read the instructions in here without understanding
+# what they do. They're here only as hints or reminders. If you are unsure
+# consult the online docs. You have been warned.
+#
+# After this file is processed, the server will look for and process
+# /var/www/conf/srm.conf and then /var/www/conf/access.conf
+# unless you have overridden these with ResourceConfig and/or
+# AccessConfig directives here.
+#
+# The configuration directives are grouped into three basic sections:
+# 1. Directives that control the operation of the Apache server process as a
+# whole (the 'global environment').
+# 2. Directives that define the parameters of the 'main' or 'default' server,
+# which responds to requests that aren't handled by a virtual host.
+# These directives also provide default values for the settings
+# of all virtual hosts.
+# 3. Settings for virtual hosts, which allow Web requests to be sent to
+# different IP addresses or hostnames and have them handled by the
+# same Apache server process.
+#
+# Configuration and logfile names: If the filenames you specify for many
+# of the server's control files begin with "/" (or "drive:/" for Win32), the
+# server will use that explicit path. If the filenames do *not* begin
+# with "/", the value of ServerRoot is prepended -- so "logs/foo.log"
+# with ServerRoot set to "/usr/local/apache" will be interpreted by the
+# server as "/usr/local/apache/logs/foo.log".
+#
+
+### Section 1: Global Environment
+#
+# The directives in this section affect the overall operation of Apache,
+# such as the number of concurrent requests it can handle or where it
+# can find its configuration files.
+#
+
+#
+# ServerType is either inetd, or standalone. Inetd mode is only supported on
+# Unix platforms.
+#
+ServerType standalone
+
+#
+# ServerTokens is either Full, OS, Minimal, or ProductOnly.
+# The values define what version information is returned in the
+# Server header in HTTP responses.
+#
+# ServerTokens ProductOnly
+
+#
+# ServerRoot: The top of the directory tree under which the server's
+# configuration, error, and log files are kept.
+#
+# NOTE! If you intend to place this on an NFS (or otherwise network)
+# mounted filesystem then please read the LockFile documentation
+# (available at <URL:http://www.apache.org/docs/mod/core.html#lockfile>);
+# you will save yourself a lot of trouble.
+#
+# Do NOT add a slash at the end of the directory path.
+#
+ServerRoot "/var/www"
+
+#
+# The LockFile directive sets the path to the lockfile used when Apache
+# is compiled with either USE_FCNTL_SERIALIZED_ACCEPT or
+# USE_FLOCK_SERIALIZED_ACCEPT. This directive should normally be left at
+# its default value. The main reason for changing it is if the logs
+# directory is NFS mounted, since the lockfile MUST BE STORED ON A LOCAL
+# DISK. The PID of the main server process is automatically appended to
+# the filename.
+#
+#LockFile logs/accept.lock
+
+#
+# PidFile: The file in which the server should record its process
+# identification number when it starts.
+#
+PidFile logs/httpd.pid
+#
+# ScoreBoardFile: File used to store internal server process information.
+# Not all architectures require this. But if yours does (you'll know because
+# this file will be created when you run Apache) then you *must* ensure that
+# no two invocations of Apache share the same scoreboard file.
+#
+ScoreBoardFile logs/apache_runtime_status
+
+#
+# In the standard configuration, the server will process httpd.conf,
+# srm.conf, and access.conf in that order. The latter two files are
+# now deprecated and not installed any more, as it is recommended that
+# all directives be kept in a single file for simplicity.
+#
+#ResourceConfig conf/srm.conf
+#AccessConfig conf/access.conf
+
+#
+# Timeout: The number of seconds before receives and sends time out.
+#
+Timeout 300
+
+#
+# KeepAlive: Whether or not to allow persistent connections (more than
+# one request per connection). Set to "Off" to deactivate.
+#
+KeepAlive On
+
+#
+# MaxKeepAliveRequests: The maximum number of requests to allow
+# during a persistent connection. Set to 0 to allow an unlimited amount.
+# We recommend you leave this number high, for maximum performance.
+#
+MaxKeepAliveRequests 100
+
+#
+# KeepAliveTimeout: Number of seconds to wait for the next request from the
+# same client on the same connection.
+#
+KeepAliveTimeout 15
+
+#
+# Server-pool size regulation. Rather than making you guess how many
+# server processes you need, Apache dynamically adapts to the load it
+# sees --- that is, it tries to maintain enough server processes to
+# handle the current load, plus a few spare servers to handle transient
+# load spikes (e.g., multiple simultaneous requests from a single
+# Netscape browser).
+#
+# It does this by periodically checking how many servers are waiting
+# for a request. If there are fewer than MinSpareServers, it creates
+# a new spare. If there are more than MaxSpareServers, some of the
+# spares die off. The default values in httpd.conf-dist are probably OK
+# for most sites.
+#
+MinSpareServers 5
+MaxSpareServers 10
+
+#
+# Number of servers to start initially --- should be a reasonable ballpark
+# figure.
+#
+StartServers 5
+
+#
+# Limit on total number of servers running, i.e., limit on the number
+# of clients who can simultaneously connect --- if this limit is ever
+# reached, clients will be LOCKED OUT, so it should NOT BE SET TOO LOW.
+# It is intended mainly as a brake to keep a runaway server from taking
+# the system with it as it spirals down...
+#
+MaxClients 150
+
+#
+# MaxRequestsPerChild: the number of requests each child process is
+# allowed to process before the child dies. The child will exit so
+# as to avoid problems after prolonged use when Apache (and maybe the
+# libraries it uses) leak memory or other resources. On most systems, this
+# isn't really needed, but a few (such as Solaris) do have notable leaks
+# in the libraries.
+#
+MaxRequestsPerChild 0
+
+#
+# MaxFOOPerChild: these directives set the current and hard rlimits for
+# the child processes. Attempts to exceed them will cause the the OS to
+# take appropriate action. See the setrlimit(2) and signal(3).
+#
+MaxCPUPerChild 0
+MaxDATAPerChild 0
+MaxNOFILEPerChild 0
+MaxRSSPerChild 0
+MaxSTACKPerChild 0
+
+#
+# Listen: Allows you to bind Apache to specific IP addresses and/or
+# ports, in addition to the default. See also the <VirtualHost>
+# directive.
+#
+#Listen 3000
+#Listen 12.34.56.78:80
+
+#
+# BindAddress: You can support virtual hosts with this option. This directive
+# is used to tell the server which IP address to listen to. It can either
+# contain "*", an IP address, or a fully qualified Internet domain name.
+# See also the <VirtualHost> and Listen directives.
+#
+#BindAddress *
+
+#
+# Dynamic Shared Object (DSO) Support
+#
+# To be able to use the functionality of a module which was built as a DSO you
+# have to place corresponding `LoadModule' lines at this location so the
+# directives contained in it are actually available _before_ they are used.
+# Please read the file README.DSO in the Apache 1.3 distribution for more
+# details about the DSO mechanism and run `httpd -l' for the list of already
+# built-in (statically linked and thus always available) modules in your httpd
+# binary.
+#
+# Note: The order is which modules are loaded is important. Don't change
+# the order below without expert advice.
+#
+# Example:
+# LoadModule foo_module libexec/mod_foo.so
+
+# "anonymous" user access to authenticated areas
+# LoadModule anon_auth_module /usr/lib/apache/modules/mod_auth_anon.so
+
+# user authentication using Berkeley DB files
+# LoadModule db_auth_module /usr/lib/apache/modules/mod_auth_db.so
+
+# user authentication using DBM files
+# LoadModule dbm_auth_module /usr/lib/apache/modules/mod_auth_dbm.so
+
+# authentication using new-style MD5 Digest Authentication (experimental)
+# LoadModule digest_auth_module /usr/lib/apache/modules/mod_auth_digest.so
+
+# CERN httpd metafile semantics
+# LoadModule cern_meta_module /usr/lib/apache/modules/mod_cern_meta.so
+
+# configuration defines ($xxx)
+# LoadModule define_module /usr/lib/apache/modules/mod_define.so
+
+# user authentication using old-style MD5 Digest Authentication
+# LoadModule digest_module /usr/lib/apache/modules/mod_digest.so
+
+# generation of Expires HTTP headers according to user-specified criteria
+# LoadModule expires_module /usr/lib/apache/modules/mod_expires.so
+
+# customization of HTTP response headers
+# LoadModule headers_module /usr/lib/apache/modules/mod_headers.so
+
+# comprehensive overview of the server configuration
+# LoadModule info_module /usr/lib/apache/modules/mod_info.so
+
+# logging of the client user agents (deprecated in favor of mod_log_config)
+# LoadModule agent_log_module /usr/lib/apache/modules/mod_log_agent.so
+
+# logging of referers (deprecated in favor of mod_log_config)
+# LoadModule referer_log_module /usr/lib/apache/modules/mod_log_referer.so
+
+# determining the MIME type of a file by looking at a few bytes of its contents
+# LoadModule mime_magic_module /usr/lib/apache/modules/mod_mime_magic.so
+
+# mmap()ing of a statically configured list of frequently requested but
+# not changed files (experimental)
+# LoadModule mmap_static_module /usr/lib/apache/modules/mod_mmap_static.so
+
+# rule-based rewriting engine to rewrite requested URLs on the fly
+# LoadModule rewrite_module /usr/lib/apache/modules/mod_rewrite.so
+
+# attempt to correct misspellings of URLs that users might have entered
+# LoadModule speling_module /usr/lib/apache/modules/mod_speling.so
+
+# provides an environment variable with a unique identifier for each request
+# LoadModule unique_id_module /usr/lib/apache/modules/mod_unique_id.so
+
+# uses cookies to provide for a clickstream log of user activity on a site
+# LoadModule usertrack_module /usr/lib/apache/modules/mod_usertrack.so
+
+# dynamically configured mass virtual hosting
+# LoadModule vhost_alias_module /usr/lib/apache/modules/mod_vhost_alias.so
+
+# caching proxy
+# LoadModule proxy_module /usr/lib/apache/modules/libproxy.so
+
+#
+# Include extra module configuration files
+#
+Include /var/www/conf/modules/*.conf
+
+#
+# ExtendedStatus controls whether Apache will generate "full" status
+# information (ExtendedStatus On) or just basic information (ExtendedStatus
+# Off) when the "server-status" handler is called. The default is Off.
+#
+#ExtendedStatus On
+
+### Section 2: 'Main' server configuration
+#
+# The directives in this section set up the values used by the 'main'
+# server, which responds to any requests that aren't handled by a
+# <VirtualHost> definition. These values also provide defaults for
+# any <VirtualHost> containers you may define later in the file.
+#
+# All of these directives may appear inside <VirtualHost> containers,
+# in which case these default settings will be overridden for the
+# virtual host being defined.
+#
+
+#
+# If your ServerType directive (set earlier in the 'Global Environment'
+# section) is set to "inetd", the next few directives don't have any
+# effect since their settings are defined by the inetd configuration.
+# Skip ahead to the ServerAdmin directive.
+#
+
+#
+# Port: The port to which the standalone server listens. For
+# ports < 1023, you will need httpd to be run as root initially.
+#
+Port 80
+
+##
+## SSL Support
+##
+## When we also provide SSL we have to listen to the
+## standard HTTP port (see above) and to the HTTPS port
+##
+<IfDefine SSL>
+Listen 80
+Listen 443
+</IfDefine>
+
+#
+# If you wish httpd to run as a different user or group, you must run
+# httpd as root initially and it will switch.
+#
+# User/Group: The name (or #number) of the user/group to run httpd as.
+# . On SCO (ODT 3) use "User nouser" and "Group nogroup".
+# . On HPUX you may not be able to use shared memory as nobody, and the
+# suggested workaround is to create a user www and use that user.
+# NOTE that some kernels refuse to setgid(Group) or semctl(IPC_SET)
+# when the value of (unsigned)Group is above 60000;
+# don't use Group #-1 on these systems!
+# On OpenBSD, use user www, group www.
+#
+User www
+Group www
+
+#
+# ServerAdmin: Your address, where problems with the server should be
+# e-mailed. This address appears on some server-generated pages, such
+# as error documents.
+#
+ServerAdmin you@your.address
+
+#
+# ServerName allows you to set a host name which is sent back to clients for
+# your server if it's different than the one the program would get (i.e., use
+# "www" instead of the host's real name).
+#
+# Note: You cannot just invent host names and hope they work. The name you
+# define here must be a valid DNS name for your host. If you don't understand
+# this, ask your network administrator.
+# If your host doesn't have a registered DNS name, enter its IP address here.
+# You will have to access it by its address (e.g., http://123.45.67.89/)
+# anyway, and this will make redirections work in a sensible way.
+#
+#ServerName new.host.name
+
+#
+# DocumentRoot: The directory out of which you will serve your
+# documents. By default, all requests are taken from this directory, but
+# symbolic links and aliases may be used to point to other locations.
+#
+DocumentRoot "/var/www/htdocs"
+
+#
+# Each directory to which Apache has access, can be configured with respect
+# to which services and features are allowed and/or disabled in that
+# directory (and its subdirectories).
+#
+# First, we configure the "default" to be a very restrictive set of
+# permissions.
+#
+<Directory />
+ Options FollowSymLinks
+ AllowOverride None
+</Directory>
+
+#
+# Note that from this point forward you must specifically allow
+# particular features to be enabled - so if something's not working as
+# you might expect, make sure that you have specifically enabled it
+# below.
+#
+
+#
+# This should be changed to whatever you set DocumentRoot to.
+#
+<Directory "/var/www/htdocs">
+
+#
+# This may also be "None", "All", or any combination of "Indexes",
+# "Includes", "FollowSymLinks", "ExecCGI", or "MultiViews".
+#
+# Note that "MultiViews" must be named *explicitly* --- "Options All"
+# doesn't give it to you.
+#
+ Options Indexes FollowSymLinks
+
+#
+# This controls which options the .htaccess files in directories can
+# override. Can also be "All", or any combination of "Options", "FileInfo",
+# "AuthConfig", and "Limit"
+#
+ AllowOverride None
+
+#
+# Controls who can get stuff from this server.
+#
+ Order allow,deny
+ Allow from all
+</Directory>
+
+#
+# UserDir: The directory which is prepended onto a users username, within
+# which a users's web pages are looked for if a ~user request is received.
+# Relative pathes are relative to the user's home directory.
+#
+# "disabled" turns this feature off.
+#
+# Since httpd will chroot(2) to the ServerRoot path by default,
+# you should use
+# UserDir /var/www/users
+# and create per user directories in /var/www/users/<username>
+#
+
+UserDir disabled
+
+#
+# Control access to UserDir directories. The following is an example
+# for a site where these directories are restricted to read-only and
+# are located under /users/<username>
+# You will need to change this to match your site's home directories.
+#
+#<Directory /users/*>
+# AllowOverride FileInfo AuthConfig Limit
+# Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
+# <Limit GET POST OPTIONS PROPFIND>
+# Order allow,deny
+# Allow from all
+# </Limit>
+# <Limit PUT DELETE PATCH PROPPATCH MKCOL COPY MOVE LOCK UNLOCK>
+# Order deny,allow
+# Deny from all
+# </Limit>
+#</Directory>
+
+#
+# DirectoryIndex: Name of the file or files to use as a pre-written HTML
+# directory index. Separate multiple entries with spaces.
+#
+DirectoryIndex index.html
+
+#
+# AccessFileName: The name of the file to look for in each directory
+# for access control information.
+#
+AccessFileName .htaccess
+
+#
+# The following lines prevent .htaccess files from being viewed by
+# Web clients. Since .htaccess files often contain authorization
+# information, access is disallowed for security reasons. Comment
+# these lines out if you want Web visitors to see the contents of
+# .htaccess files. If you change the AccessFileName directive above,
+# be sure to make the corresponding changes here.
+#
+<Files .htaccess>
+ Order allow,deny
+ Deny from all
+</Files>
+
+#
+# CacheNegotiatedDocs: By default, Apache sends "Pragma: no-cache" with each
+# document that was negotiated on the basis of content. This asks proxy
+# servers not to cache the document. Uncommenting the following line disables
+# this behavior, and proxies will be allowed to cache the documents.
+#
+#CacheNegotiatedDocs
+
+#
+# UseCanonicalName: (new for 1.3) With this setting turned on, whenever
+# Apache needs to construct a self-referencing URL (a URL that refers back
+# to the server the response is coming from) it will use ServerName and
+# Port to form a "canonical" name. With this setting off, Apache will
+# use the hostname:port that the client supplied, when possible. This
+# also affects SERVER_NAME and SERVER_PORT in CGI scripts.
+#
+UseCanonicalName On
+
+#
+# TypesConfig describes where the mime.types file (or equivalent) is
+# to be found.
+#
+TypesConfig conf/mime.types
+
+#
+# DefaultType is the default MIME type the server will use for a document
+# if it cannot otherwise determine one, such as from filename extensions.
+# If your server contains mostly text or HTML documents, "text/plain" is
+# a good value. If most of your content is binary, such as applications
+# or images, you may want to use "application/octet-stream" instead to
+# keep browsers from trying to display binary files as though they are
+# text.
+#
+DefaultType text/plain
+
+#
+# The mod_mime_magic module allows the server to use various hints from the
+# contents of the file itself to determine its type. The MIMEMagicFile
+# directive tells the module where the hint definitions are located.
+# mod_mime_magic is not part of the default server (you have to add
+# it yourself with a LoadModule [see the DSO paragraph in the 'Global
+# Environment' section], or recompile the server and include mod_mime_magic
+# as part of the configuration), so it's enclosed in an <IfModule> container.
+# This means that the MIMEMagicFile directive will only be processed if the
+# module is part of the server.
+#
+<IfModule mod_mime_magic.c>
+ MIMEMagicFile conf/magic
+</IfModule>
+
+#
+# HostnameLookups: Log the names of clients or just their IP addresses
+# e.g., www.apache.org (on) or 204.62.129.132 (off).
+# The default is off because it'd be overall better for the net if people
+# had to knowingly turn this feature on, since enabling it means that
+# each client request will result in AT LEAST one lookup request to the
+# nameserver.
+#
+HostnameLookups Off
+
+#
+# ErrorLog: The location of the error log file.
+# If you do not specify an ErrorLog directive within a <VirtualHost>
+# container, error messages relating to that virtual host will be
+# logged here. If you *do* define an error logfile for a <VirtualHost>
+# container, that host's errors will be logged there and not here.
+# Either a filename or the text "syslog:" followed by a facility
+# name may be specified here.
+#
+#ErrorLog syslog:daemon
+ErrorLog logs/error_log
+
+#
+# LogLevel: Control the number of messages logged to the error_log.
+# Possible values include: debug, info, notice, warn, error, crit,
+# alert, emerg.
+#
+LogLevel warn
+
+#
+# The following directives define some format nicknames for use with
+# a CustomLog directive (see below).
+#
+LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
+LogFormat "%h %l %u %t \"%r\" %>s %b" common
+LogFormat "%{Referer}i -> %U" referer
+LogFormat "%{User-agent}i" agent
+
+#
+# The location and format of the access logfile (Common Logfile Format).
+# If you do not define any access logfiles within a <VirtualHost>
+# container, they will be logged here. Contrariwise, if you *do*
+# define per-<VirtualHost> access logfiles, transactions will be
+# logged therein and *not* in this file.
+#
+CustomLog logs/access_log common
+
+#
+# If you would like to have agent and referer logfiles, uncomment the
+# following directives.
+#
+#CustomLog logs/referer_log referer
+#CustomLog logs/agent_log agent
+
+#
+# If you prefer a single logfile with access, agent, and referer information
+# (Combined Logfile Format) you can use the following directive.
+#
+#CustomLog logs/access_log combined
+
+#
+# Optionally add a line containing the server version and virtual host
+# name to server-generated pages (error documents, FTP directory listings,
+# mod_status and mod_info output etc., but not CGI generated documents).
+# Set to "EMail" to also include a mailto: link to the ServerAdmin.
+# Set to one of: On | Off | EMail
+#
+# ServerSignature Off
+
+#
+# Aliases: Add here as many aliases as you need (with no limit). The format is
+# Alias fakename realname
+#
+# Note that if you include a trailing / on fakename then the server will
+# require it to be present in the URL. So "/icons" isn't aliased in this
+# example, only "/icons/"..
+#
+Alias /icons/ "/var/www/icons/"
+
+<Directory "/var/www/icons">
+ Options Indexes MultiViews
+ AllowOverride None
+ Order allow,deny
+ Allow from all
+</Directory>
+
+<Directory "/var/www/htdocs/manual">
+ Options MultiViews
+ AllowOverride None
+ Order allow,deny
+ Allow from all
+</Directory>
+
+#
+# ScriptAlias: This controls which directories contain server scripts.
+# ScriptAliases are essentially the same as Aliases, except that
+# documents in the realname directory are treated as applications and
+# run by the server when requested rather than as documents sent to the client.
+# The same rules about trailing "/" apply to ScriptAlias directives as to
+# Alias.
+#
+ScriptAlias /cgi-bin/ "/var/www/cgi-bin/"
+
+#
+# "/var/www/cgi-bin" should be changed to whatever your ScriptAliased
+# CGI directory exists, if you have that configured.
+#
+<Directory "/var/www/cgi-bin">
+ AllowOverride None
+ Options None
+ Order allow,deny
+ Allow from all
+</Directory>
+
+#
+# Redirect allows you to tell clients about documents which used to exist in
+# your server's namespace, but do not anymore. This allows you to tell the
+# clients where to look for the relocated document.
+# Format: Redirect old-URI new-URL
+#
+
+#
+# Directives controlling the display of server-generated directory listings.
+#
+
+#
+# FancyIndexing is whether you want fancy directory indexing or standard
+#
+IndexOptions FancyIndexing
+
+#
+# AddIcon* directives tell the server which icon to show for different
+# files or filename extensions. These are only displayed for
+# FancyIndexed directories.
+#
+AddIconByEncoding (CMP,/icons/compressed.gif) x-compress x-gzip
+
+AddIconByType (TXT,/icons/text.gif) text/*
+AddIconByType (IMG,/icons/image2.gif) image/*
+AddIconByType (SND,/icons/sound2.gif) audio/*
+AddIconByType (VID,/icons/movie.gif) video/*
+
+AddIcon /icons/binary.gif .bin .exe
+AddIcon /icons/binhex.gif .hqx
+AddIcon /icons/tar.gif .tar
+AddIcon /icons/world2.gif .wrl .wrl.gz .vrml .vrm .iv
+AddIcon /icons/compressed.gif .Z .z .tgz .gz .zip
+AddIcon /icons/a.gif .ps .ai .eps
+AddIcon /icons/layout.gif .html .shtml .htm .pdf
+AddIcon /icons/text.gif .txt
+AddIcon /icons/c.gif .c
+AddIcon /icons/p.gif .pl .py
+AddIcon /icons/f.gif .for
+AddIcon /icons/dvi.gif .dvi
+AddIcon /icons/uuencoded.gif .uu
+AddIcon /icons/script.gif .conf .sh .shar .csh .ksh .tcl
+AddIcon /icons/tex.gif .tex
+AddIcon /icons/bomb.gif core
+
+AddIcon /icons/back.gif ..
+AddIcon /icons/hand.right.gif README
+AddIcon /icons/folder.gif ^^DIRECTORY^^
+AddIcon /icons/blank.gif ^^BLANKICON^^
+
+#
+# DefaultIcon is which icon to show for files which do not have an icon
+# explicitly set.
+#
+DefaultIcon /icons/unknown.gif
+
+#
+# AddDescription allows you to place a short description after a file in
+# server-generated indexes. These are only displayed for FancyIndexed
+# directories.
+# Format: AddDescription "description" filename
+#
+#AddDescription "GZIP compressed document" .gz
+#AddDescription "tar archive" .tar
+#AddDescription "GZIP compressed tar archive" .tgz
+
+#
+# ReadmeName is the name of the README file the server will look for by
+# default, and append to directory listings.
+#
+# HeaderName is the name of a file which should be prepended to
+# directory indexes.
+#
+# The server will first look for name.html and include it if found.
+# If name.html doesn't exist, the server will then look for name.txt
+# and include it as plaintext if found.
+#
+ReadmeName README
+HeaderName HEADER
+
+#
+# IndexIgnore is a set of filenames which directory indexing should ignore
+# and not include in the listing. Shell-style wildcarding is permitted.
+#
+IndexIgnore .??* *~ *# HEADER* README* RCS CVS *,v *,t
+
+#
+# AddEncoding allows you to have certain browsers (Mosaic/X 2.1+) uncompress
+# information on the fly. Note: Not all browsers support this.
+# Despite the name similarity, the following Add* directives have nothing
+# to do with the FancyIndexing customization directives above.
+#
+AddEncoding x-compress Z
+AddEncoding x-gzip gz
+
+#
+# AddLanguage allows you to specify the language of a document. You can
+# then use content negotiation to give a browser a file in a language
+# it can understand. Note that the suffix does not have to be the same
+# as the language keyword --- those with documents in Polish (whose
+# net-standard language code is pl) may wish to use "AddLanguage pl .po"
+# to avoid the ambiguity with the common suffix for perl scripts.
+#
+AddLanguage en .en
+AddLanguage fr .fr
+AddLanguage de .de
+AddLanguage da .da
+AddLanguage el .el
+AddLanguage it .it
+
+#
+# LanguagePriority allows you to give precedence to some languages
+# in case of a tie during content negotiation.
+# Just list the languages in decreasing order of preference.
+#
+LanguagePriority en fr de
+
+#
+# AddType allows you to tweak mime.types without actually editing it, or to
+# make certain files to be certain types.
+#
+# For example, the PHP module (not part of the Apache distribution)
+# will typically use:
+#
+#AddType application/x-httpd-php .php
+
+#
+# AddHandler allows you to map certain file extensions to "handlers",
+# actions unrelated to filetype. These can be either built into the server
+# or added with the Action command (see below)
+#
+# If you want to use server side includes, or CGI outside
+# ScriptAliased directories, uncomment the following lines.
+#
+# To use CGI scripts:
+#
+#AddHandler cgi-script .cgi
+
+#
+# To use server-parsed HTML files
+#
+#AddType text/html .shtml
+#AddHandler server-parsed .shtml
+
+#
+# Uncomment the following line to enable Apache's send-asis HTTP file
+# feature
+#
+#AddHandler send-as-is asis
+
+#
+# If you wish to use server-parsed imagemap files, use
+#
+#AddHandler imap-file map
+
+#
+# To enable type maps, you might want to use
+#
+#AddHandler type-map var
+
+#
+# Action lets you define media types that will execute a script whenever
+# a matching file is called. This eliminates the need for repeated URL
+# pathnames for oft-used CGI file processors.
+# Format: Action media/type /cgi-script/location
+# Format: Action handler-name /cgi-script/location
+#
+
+#
+# MetaDir: specifies the name of the directory in which Apache can find
+# meta information files. These files contain additional HTTP headers
+# to include when sending the document
+#
+#MetaDir .web
+
+#
+# MetaSuffix: specifies the file name suffix for the file containing the
+# meta information.
+#
+#MetaSuffix .meta
+
+#
+# Customizable error response (Apache style)
+# these come in three flavors
+#
+# 1) plain text
+#ErrorDocument 500 "The server made a boo boo.
+# n.b. the (") marks it as text, it does not get output
+#
+# 2) local redirects
+#ErrorDocument 404 /missing.html
+# to redirect to local URL /missing.html
+#ErrorDocument 404 /cgi-bin/missing_handler.pl
+# N.B.: You can redirect to a script or a document using server-side-includes.
+#
+# 3) external redirects
+#ErrorDocument 402 http://some.other_server.com/subscription_info.html
+# N.B.: Many of the environment variables associated with the original
+# request will *not* be available to such a script.
+
+#
+# The following directives modify normal HTTP response behavior.
+# The first directive disables keepalive for Netscape 2.x and browsers that
+# spoof it. There are known problems with these browser implementations.
+# The second directive is for Microsoft Internet Explorer 4.0b2
+# which has a broken HTTP/1.1 implementation and does not properly
+# support keepalive when it is used on 301 or 302 (redirect) responses.
+#
+BrowserMatch "Mozilla/2" nokeepalive
+BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0
+
+#
+# The following directive disables HTTP/1.1 responses to browsers which
+# are in violation of the HTTP/1.0 spec by not being able to grok a
+# basic 1.1 response.
+#
+BrowserMatch "RealPlayer 4\.0" force-response-1.0
+BrowserMatch "Java/1\.0" force-response-1.0
+BrowserMatch "JDK/1\.0" force-response-1.0
+
+#
+# Allow server status reports, with the URL of http://servername/server-status
+# Change the ".your_domain.com" to match your domain to enable.
+#
+#<Location /server-status>
+# SetHandler server-status
+# Order deny,allow
+# Deny from all
+# Allow from .your_domain.com
+#</Location>
+
+#
+# Allow remote server configuration reports, with the URL of
+# http://servername/server-info (requires that mod_info.c be loaded).
+# Change the ".your_domain.com" to match your domain to enable.
+#
+#<Location /server-info>
+# SetHandler server-info
+# Order deny,allow
+# Deny from all
+# Allow from .your_domain.com
+#</Location>
+
+#
+# There have been reports of people trying to abuse an old bug from pre-1.1
+# days. This bug involved a CGI script distributed as a part of Apache.
+# By uncommenting these lines you can redirect these attacks to a logging
+# script on phf.apache.org. Or, you can record them yourself, using the script
+# support/phf_abuse_log.cgi.
+#
+#<Location /cgi-bin/phf*>
+# Deny from all
+# ErrorDocument 403 http://phf.apache.org/phf_abuse_log.cgi
+#</Location>
+
+#
+# Proxy Server directives. Uncomment the following lines to
+# enable the proxy server:
+#
+#<IfModule mod_proxy.c>
+#ProxyRequests On
+#
+#<Directory proxy:*>
+# Order deny,allow
+# Deny from all
+# Allow from .your_domain.com
+#</Directory>
+
+#
+# Enable/disable the handling of HTTP/1.1 "Via:" headers.
+# ("Full" adds the server version; "Block" removes all outgoing Via: headers)
+# Set to one of: Off | On | Full | Block
+#
+#ProxyVia On
+
+#
+# To enable the cache as well, edit and uncomment the following lines:
+# (no cacheing without CacheRoot)
+#
+#CacheRoot "/var/www/proxy"
+#CacheSize 5
+#CacheGcInterval 4
+#CacheMaxExpire 24
+#CacheLastModifiedFactor 0.1
+#CacheDefaultExpire 1
+#NoCache a_domain.com another_domain.edu joes.garage_sale.com
+
+#</IfModule>
+# End of proxy directives.
+
+### Section 3: Virtual Hosts
+#
+# VirtualHost: If you want to maintain multiple domains/hostnames on your
+# machine you can setup VirtualHost containers for them.
+# Please see the documentation at <URL:http://www.apache.org/docs/vhosts/>
+# for further details before you try to setup virtual hosts.
+# You may use the command line option '-S' to verify your virtual host
+# configuration.
+
+#
+# If you want to use name-based virtual hosts you need to define at
+# least one IP address (and port number) for them.
+#
+#NameVirtualHost 12.34.56.78:80
+#NameVirtualHost 12.34.56.78
+
+#
+# VirtualHost example:
+# Almost any Apache directive may go into a VirtualHost container.
+#
+#<VirtualHost ip.address.of.host.some_domain.com>
+# ServerAdmin webmaster@host.some_domain.com
+# DocumentRoot /www/docs/host.some_domain.com
+# ServerName host.some_domain.com
+# ErrorLog logs/host.some_domain.com-error_log
+# CustomLog logs/host.some_domain.com-access_log common
+#</VirtualHost>
+
+#<VirtualHost _default_:*>
+#</VirtualHost>
+
+
+##
+## SSL Global Context
+##
+## All SSL configuration in this context applies both to
+## the main server and all SSL-enabled virtual hosts.
+##
+
+#
+# Some MIME-types for downloading Certificates and CRLs
+#
+<IfDefine SSL>
+AddType application/x-x509-ca-cert .crt
+AddType application/x-pkcs7-crl .crl
+</IfDefine>
+
+<IfModule mod_ssl.c>
+
+# Pass Phrase Dialog:
+# Configure the pass phrase gathering process.
+# The filtering dialog program (`builtin' is a internal
+# terminal dialog) has to provide the pass phrase on stdout.
+SSLPassPhraseDialog builtin
+
+# Inter-Process Session Cache:
+# Configure the SSL Session Cache: First either `none'
+# or `dbm:/path/to/file' for the mechanism to use and
+# second the expiring timeout (in seconds).
+SSLSessionCache dbm:logs/ssl_scache
+SSLSessionCacheTimeout 300
+
+# Semaphore:
+# Configure the path to the mutual exclusion semaphore the
+# SSL engine uses internally for inter-process synchronization.
+SSLMutex sem
+
+# Pseudo Random Number Generator (PRNG):
+# Configure one or more sources to seed the PRNG of the
+# SSL library. The seed data should be of good random quality.
+SSLRandomSeed startup builtin
+SSLRandomSeed connect builtin
+#SSLRandomSeed startup file:/dev/random 512
+#SSLRandomSeed startup file:/dev/urandom 512
+#SSLRandomSeed connect file:/dev/random 512
+#SSLRandomSeed connect file:/dev/urandom 512
+SSLRandomSeed startup file:/dev/arandom 512
+
+# Logging:
+# The home of the dedicated SSL protocol logfile. Errors are
+# additionally duplicated in the general error log file. Put
+# this somewhere where it cannot be used for symlink attacks on
+# a real server (i.e. somewhere where only root can write).
+# Log levels are (ascending order: higher ones include lower ones):
+# none, error, warn, info, trace, debug.
+SSLLog logs/ssl_engine_log
+SSLLogLevel info
+
+</IfModule>
+
+<IfDefine SSL>
+
+##
+## SSL Virtual Host Context
+##
+
+<VirtualHost _default_:443>
+
+# General setup for the virtual host
+DocumentRoot /var/www/htdocs
+ServerName new.host.name
+ServerAdmin you@your.address
+ErrorLog logs/error_log
+TransferLog logs/access_log
+
+# SSL Engine Switch:
+# Enable/Disable SSL for this virtual host.
+SSLEngine on
+
+# SSL Cipher Suite:
+# List the ciphers that the client is permitted to negotiate.
+# See the mod_ssl documentation for a complete list.
+#SSLCipherSuite ALL:!ADH:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP
+
+# Server Certificate:
+# Point SSLCertificateFile at a PEM encoded certificate. If
+# the certificate is encrypted, then you will be prompted for a
+# pass phrase. Note that a kill -HUP will prompt again. A test
+# certificate can be generated with `make certificate' under
+# built time.
+SSLCertificateFile /etc/ssl/server.crt
+
+# Server Private Key:
+# If the key is not combined with the certificate, use this
+# directive to point at the key file.
+SSLCertificateKeyFile /etc/ssl/private/server.key
+
+# Certificate Authority (CA):
+# Set the CA certificate verification path where to find CA
+# certificates for client authentication or alternatively one
+# huge file containing all of them (file must be PEM encoded)
+# Note: Inside SSLCACertificatePath you need hash symlinks
+# to point to the certificate files. Use the provided
+# Makefile to update the hash symlinks after changes.
+#SSLCACertificatePath /var/www/conf/ssl.crt
+#SSLCACertificateFile /var/www/conf/ssl.crt/ca-bundle.crt
+
+# Client Authentication (Type):
+# Client certificate verification type and depth. Types are
+# none, optional, require and optional_no_ca. Depth is a
+# number which specifies how deeply to verify the certificate
+# issuer chain before deciding the certificate is not valid.
+#SSLVerifyClient require
+#SSLVerifyDepth 10
+
+# Access Control:
+# With SSLRequire you can do per-directory access control based
+# on arbitrary complex boolean expressions containing server
+# variable checks and other lookup directives. The syntax is a
+# mixture between C and Perl. See the mod_ssl documentation
+# for more details.
+#<Location />
+#SSLRequire ( %{SSL_CIPHER} !~ m/^(EXP|NULL)-/ \
+# and %{SSL_CLIENT_S_DN_O} eq "Snake Oil, Ltd." \
+# and %{SSL_CLIENT_S_DN_OU} in {"Staff", "CA", "Dev"} \
+# and %{TIME_WDAY} >= 1 and %{TIME_WDAY} <= 5 \
+# and %{TIME_HOUR} >= 8 and %{TIME_HOUR} <= 20 ) \
+# or %{REMOTE_ADDR} =~ m/^192\.76\.162\.[0-9]+$/
+#</Location>
+
+# SSL Engine Options:
+# Set various options for the SSL engine.
+# FakeBasicAuth:
+# Translate the client X.509 into a Basic Authorisation. This means that
+# the standard Auth/DBMAuth methods can be used for access control. The
+# user name is the `one line' version of the client's X.509 certificate.
+# Note that no password is obtained from the user. Every entry in the user
+# file needs this password: `xxj31ZMTZzkVA'.
+# ExportCertData:
+# This exports two additional environment variables: SSL_CLIENT_CERT and
+# SSL_SERVER_CERT. These contain the PEM-encoded certificates of the
+# server (always existing) and the client (only existing when client
+# authentication is used). This can be used to import the certificates
+# into CGI scripts.
+# CompatEnvVars:
+# This exports obsolete environment variables for backward compatibility
+# to Apache-SSL 1.x, mod_ssl 2.0.x, Sioux 1.0 and Stronghold 2.x. Use this
+# to provide compatibility to existing CGI scripts.
+#SSLOptions +FakeBasicAuth +ExportCertData +CompatEnvVars
+
+# Per-Server Logging:
+# The home of a custom SSL log file. Use this when you want a
+# compact non-error SSL logfile on a virtual host basis.
+CustomLog logs/ssl_request_log \
+ "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
+
+</VirtualHost>
+
+</IfDefine>
+
+# include additional things
+Include conf.d/*.conf
+Include vhosts.d/*.conf
diff --git a/puppet/modules/apache/files/include.d/defaults.inc b/puppet/modules/apache/files/include.d/defaults.inc
new file mode 100644
index 00000000..3e5e7d73
--- /dev/null
+++ b/puppet/modules/apache/files/include.d/defaults.inc
@@ -0,0 +1,5 @@
+RewriteEngine on
+RewriteCond %{REQUEST_METHOD} ^(TRACE|TRACK)
+RewriteRule .* - [F]
+
+ServerSignature Off
diff --git a/puppet/modules/apache/files/include.d/joomla.inc b/puppet/modules/apache/files/include.d/joomla.inc
new file mode 100644
index 00000000..1535ce37
--- /dev/null
+++ b/puppet/modules/apache/files/include.d/joomla.inc
@@ -0,0 +1,30 @@
+########## Begin - Rewrite rules to block out some common exploits
+# against joomla's
+#
+# Block out any script trying to set a mosConfig value through the URL
+RewriteCond %{QUERY_STRING} mosConfig_[a-zA-Z_]{1,21}(=|\%3D) [OR]
+
+# Block out any script trying to base64_encode crap to send via URL
+RewriteCond %{QUERY_STRING} base64_encode.*\(.*\) [OR]
+
+# Block out any script that includes a <script> tag in URL
+RewriteCond %{QUERY_STRING} (\<|%3C).*script.*(\>|%3E) [NC,OR]
+
+# Block out any script trying to set a PHP GLOBALS variable via URL
+RewriteCond %{QUERY_STRING} GLOBALS(=|\[|\%[0-9A-Z]{0,2}) [OR]
+
+# Block out any script trying to modify a _REQUEST variable via URL
+RewriteCond %{QUERY_STRING} _REQUEST(=|\[|\%[0-9A-Z]{0,2}) [OR]
+
+# Block out any script that tries to set CONFIG_EXT (com_extcal2 issue)
+RewriteCond %{QUERY_STRING} CONFIG_EXT(\[|\%20|\%5B).*= [NC,OR]
+
+# Block out any script that tries to set sbp or sb_authorname via URL (simpleboard)
+RewriteCond %{QUERY_STRING} sbp(=|\%20|\%3D) [OR]
+RewriteCond %{QUERY_STRING} sb_authorname(=|\%20|\%3D)
+
+# Send all blocked request to homepage with 403 Forbidden error!
+RewriteRule ^(.*)$ index.php [F,L]
+#
+########## End - Rewrite rules to block out some common exploits
+
diff --git a/puppet/modules/apache/files/include.d/silverstripe.inc b/puppet/modules/apache/files/include.d/silverstripe.inc
new file mode 100644
index 00000000..40c44e46
--- /dev/null
+++ b/puppet/modules/apache/files/include.d/silverstripe.inc
@@ -0,0 +1,17 @@
+# silverstripe .htaccess
+<Files *.ss>
+ Order deny,allow
+ Deny from all
+ #Allow from 127.0.0.1
+</Files>
+
+<IfModule mod_rewrite.c>
+ RewriteEngine On
+ #RewriteBase /
+
+ RewriteCond %{REQUEST_URI} !(\.gif$)|(\.jpg$)|(\.png$)|(\.css$)|(\.js$)
+
+ RewriteCond %{REQUEST_URI} ^(.*)$
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteRule .* sapphire/main.php?url=%1&%{QUERY_STRING} [L]
+</IfModule>
diff --git a/puppet/modules/apache/files/itk_plus/conf.d/CentOS/ssl.conf b/puppet/modules/apache/files/itk_plus/conf.d/CentOS/ssl.conf
new file mode 100644
index 00000000..fb0c915a
--- /dev/null
+++ b/puppet/modules/apache/files/itk_plus/conf.d/CentOS/ssl.conf
@@ -0,0 +1,75 @@
+#
+# This is the Apache server configuration file providing SSL support.
+# It contains the configuration directives to instruct the server how to
+# serve pages over an https connection. For detailing information about these
+# directives see <URL:http://httpd.apache.org/docs/2.2/mod/mod_ssl.html>
+#
+# Do NOT simply read the instructions in here without understanding
+# what they do. They're here only as hints or reminders. If you are unsure
+# consult the online docs. You have been warned.
+#
+
+LoadModule ssl_module modules/mod_ssl.so
+
+#
+# When we also provide SSL we have to listen to the
+# the HTTPS port in addition.
+#
+NameVirtualHost *:443
+
+##
+## SSL Global Context
+##
+## All SSL configuration in this context applies both to
+## the main server and all SSL-enabled virtual hosts.
+##
+
+#
+# Some MIME-types for downloading Certificates and CRLs
+#
+AddType application/x-x509-ca-cert .crt
+AddType application/x-pkcs7-crl .crl
+
+# Pass Phrase Dialog:
+# Configure the pass phrase gathering process.
+# The filtering dialog program (`builtin' is a internal
+# terminal dialog) has to provide the pass phrase on stdout.
+SSLPassPhraseDialog builtin
+
+# Inter-Process Session Cache:
+# Configure the SSL Session Cache: First the mechanism
+# to use and second the expiring timeout (in seconds).
+#SSLSessionCache dc:UNIX:/var/cache/mod_ssl/distcache
+SSLSessionCache shmcb:/var/cache/mod_ssl/scache(512000)
+SSLSessionCacheTimeout 300
+
+# Semaphore:
+# Configure the path to the mutual exclusion semaphore the
+# SSL engine uses internally for inter-process synchronization.
+SSLMutex default
+
+# Pseudo Random Number Generator (PRNG):
+# Configure one or more sources to seed the PRNG of the
+# SSL library. The seed data should be of good random quality.
+# WARNING! On some platforms /dev/random blocks if not enough entropy
+# is available. This means you then cannot use the /dev/random device
+# because it would lead to very long connection times (as long as
+# it requires to make more entropy available). But usually those
+# platforms additionally provide a /dev/urandom device which doesn't
+# block. So, if available, use this one instead. Read the mod_ssl User
+# Manual for more details.
+SSLRandomSeed startup file:/dev/urandom 256
+SSLRandomSeed connect builtin
+#SSLRandomSeed startup file:/dev/random 512
+#SSLRandomSeed connect file:/dev/random 512
+#SSLRandomSeed connect file:/dev/urandom 512
+
+#
+# Use "SSLCryptoDevice" to enable any supported hardware
+# accelerators. Use "openssl engine -v" to list supported
+# engine names. NOTE: If you enable an accelerator and the
+# server does not start, consult the error logs and ensure
+# your accelerator is functioning properly.
+#
+SSLCryptoDevice builtin
+#SSLCryptoDevice ubsec
diff --git a/puppet/modules/apache/files/modules.d/Gentoo/00_default_settings.conf b/puppet/modules/apache/files/modules.d/Gentoo/00_default_settings.conf
new file mode 100644
index 00000000..5315fcb7
--- /dev/null
+++ b/puppet/modules/apache/files/modules.d/Gentoo/00_default_settings.conf
@@ -0,0 +1,105 @@
+# This configuration file reflects default settings for Apache HTTP Server.
+# You may change these, but chances are that you may not need to.
+
+# Timeout: The number of seconds before receives and sends time out.
+Timeout 300
+
+# KeepAlive: Whether or not to allow persistent connections (more than
+# one request per connection). Set to "Off" to deactivate.
+KeepAlive On
+
+# MaxKeepAliveRequests: The maximum number of requests to allow
+# during a persistent connection. Set to 0 to allow an unlimited amount.
+# We recommend you leave this number high, for maximum performance.
+MaxKeepAliveRequests 100
+
+# KeepAliveTimeout: Number of seconds to wait for the next request from the
+# same client on the same connection.
+KeepAliveTimeout 15
+
+# UseCanonicalName: Determines how Apache constructs self-referencing
+# URLs and the SERVER_NAME and SERVER_PORT variables.
+# When set "Off", Apache will use the Hostname and Port supplied
+# by the client. When set "On", Apache will use the value of the
+# ServerName directive.
+UseCanonicalName Off
+
+# AccessFileName: The name of the file to look for in each directory
+# for additional configuration directives. See also the AllowOverride
+# directive.
+AccessFileName .htaccess
+
+# ServerTokens
+# This directive configures what you return as the Server HTTP response
+# Header. The default is 'Full' which sends information about the OS-Type
+# and compiled in modules.
+# Set to one of: Full | OS | Minor | Minimal | Major | Prod
+# where Full conveys the most information, and Prod the least.
+ServerTokens Prod
+
+# Optionally add a line containing the server version and virtual host
+# name to server-generated pages (internal error documents, FTP directory
+# listings, mod_status and mod_info output etc., but not CGI generated
+# documents or custom error documents).
+# Set to "EMail" to also include a mailto: link to the ServerAdmin.
+# Set to one of: On | Off | EMail
+ServerSignature Off
+
+# HostnameLookups: Log the names of clients or just their IP addresses
+# e.g., www.apache.org (on) or 204.62.129.132 (off).
+# The default is off because it'd be overall better for the net if people
+# had to knowingly turn this feature on, since enabling it means that
+# each client request will result in AT LEAST one lookup request to the
+# nameserver.
+HostnameLookups Off
+
+# EnableMMAP and EnableSendfile: On systems that support it,
+# memory-mapping or the sendfile syscall is used to deliver
+# files. This usually improves server performance, but must
+# be turned off when serving from networked-mounted
+# filesystems or if support for these functions is otherwise
+# broken on your system.
+#EnableMMAP off
+#EnableSendfile off
+
+# ErrorLog: The location of the error log file.
+# If you do not specify an ErrorLog directive within a <VirtualHost>
+# container, error messages relating to that virtual host will be
+# logged here. If you *do* define an error logfile for a <VirtualHost>
+# container, that host's errors will be logged there and not here.
+ErrorLog /var/log/apache2/error_log
+
+# LogLevel: Control the number of messages logged to the error_log.
+# Possible values include: debug, info, notice, warn, error, crit,
+# alert, emerg.
+LogLevel warn
+
+# We configure the "default" to be a very restrictive set of features.
+<Directory />
+ Options FollowSymLinks
+ AllowOverride None
+ Order deny,allow
+ Deny from all
+</Directory>
+
+# DirectoryIndex: sets the file that Apache will serve if a directory
+# is requested.
+#
+# The index.html.var file (a type-map) is used to deliver content-
+# negotiated documents. The MultiViews Options can be used for the
+# same purpose, but it is much slower.
+#
+# To add files to that list use AddDirectoryIndex in a custom config
+# file. Do not change this entry unless you know what you are doing.
+<IfModule dir_module>
+ DirectoryIndex index.html index.html.var
+</IfModule>
+
+# The following lines prevent .htaccess and .htpasswd files from being
+# viewed by Web clients.
+<FilesMatch "^\.ht">
+ Order allow,deny
+ Deny from all
+</FilesMatch>
+
+# vim: ts=4 filetype=apache
diff --git a/puppet/modules/apache/files/modules.d/Gentoo/00_error_documents.conf b/puppet/modules/apache/files/modules.d/Gentoo/00_error_documents.conf
new file mode 100644
index 00000000..90900269
--- /dev/null
+++ b/puppet/modules/apache/files/modules.d/Gentoo/00_error_documents.conf
@@ -0,0 +1,66 @@
+# The configuration below implements multi-language error documents through
+# content-negotiation.
+
+# Customizable error responses come in three flavors:
+# 1) plain text 2) local redirects 3) external redirects
+# Some examples:
+#ErrorDocument 500 "The server made a boo boo."
+#ErrorDocument 404 /missing.html
+#ErrorDocument 404 "/cgi-bin/missing_handler.pl"
+#ErrorDocument 402 http://www.example.com/subscription_info.html
+
+# Required modules: mod_alias, mod_include, mod_negotiation
+# We use Alias to redirect any /error/HTTP_<error>.html.var response to
+# our collection of by-error message multi-language collections. We use
+# includes to substitute the appropriate text.
+# You can modify the messages' appearance without changing any of the
+# default HTTP_<error>.html.var files by adding the line:
+# Alias /error/include/ "/your/include/path/"
+# which allows you to create your own set of files by starting with the
+# /var/www/localhost/error/include/ files and copying them to /your/include/path/,
+# even on a per-VirtualHost basis. The default include files will display
+# your Apache version number and your ServerAdmin email address regardless
+# of the setting of ServerSignature.
+
+<IfDefine ERRORDOCS>
+<IfModule alias_module>
+<IfModule mime_module>
+<IfModule negotiation_module>
+
+Alias /error/ "/var/www/localhost/error/"
+
+<Directory "/var/www/localhost/error">
+ AllowOverride None
+ Options IncludesNoExec
+ AddOutputFilter Includes html
+ AddHandler type-map var
+ Order allow,deny
+ Allow from all
+ LanguagePriority en cs de es fr it ja ko nl pl pt-br ro sv tr
+ ForceLanguagePriority Prefer Fallback
+</Directory>
+
+ErrorDocument 400 /error/HTTP_BAD_REQUEST.html.var
+ErrorDocument 401 /error/HTTP_UNAUTHORIZED.html.var
+ErrorDocument 403 /error/HTTP_FORBIDDEN.html.var
+ErrorDocument 404 /error/HTTP_NOT_FOUND.html.var
+ErrorDocument 405 /error/HTTP_METHOD_NOT_ALLOWED.html.var
+ErrorDocument 408 /error/HTTP_REQUEST_TIME_OUT.html.var
+ErrorDocument 410 /error/HTTP_GONE.html.var
+ErrorDocument 411 /error/HTTP_LENGTH_REQUIRED.html.var
+ErrorDocument 412 /error/HTTP_PRECONDITION_FAILED.html.var
+ErrorDocument 413 /error/HTTP_REQUEST_ENTITY_TOO_LARGE.html.var
+ErrorDocument 414 /error/HTTP_REQUEST_URI_TOO_LARGE.html.var
+ErrorDocument 415 /error/HTTP_UNSUPPORTED_MEDIA_TYPE.html.var
+ErrorDocument 500 /error/HTTP_INTERNAL_SERVER_ERROR.html.var
+ErrorDocument 501 /error/HTTP_NOT_IMPLEMENTED.html.var
+ErrorDocument 502 /error/HTTP_BAD_GATEWAY.html.var
+ErrorDocument 503 /error/HTTP_SERVICE_UNAVAILABLE.html.var
+ErrorDocument 506 /error/HTTP_VARIANT_ALSO_VARIES.html.var
+
+</IfModule>
+</IfModule>
+</IfModule>
+</IfDefine>
+
+# vim: ts=4 filetype=apache
diff --git a/puppet/modules/apache/files/modules.d/Gentoo/00_languages.conf b/puppet/modules/apache/files/modules.d/Gentoo/00_languages.conf
new file mode 100644
index 00000000..287f6544
--- /dev/null
+++ b/puppet/modules/apache/files/modules.d/Gentoo/00_languages.conf
@@ -0,0 +1,137 @@
+# Settings for hosting different languages.
+<IfDefine LANGUAGE>
+<IfModule mime_module>
+<IfModule negotiation_module>
+# DefaultLanguage and AddLanguage allows you to specify the language of
+# a document. You can then use content negotiation to give a browser a
+# file in a language the user can understand.
+#
+# Specify a default language. This means that all data
+# going out without a specific language tag (see below) will
+# be marked with this one. You probably do NOT want to set
+# this unless you are sure it is correct for all cases.
+#
+# It is generally better to not mark a page as
+# being a certain language than marking it with the wrong
+# language!
+#
+# DefaultLanguage nl
+#
+# Note 1: The suffix does not have to be the same as the language
+# keyword --- those with documents in Polish (whose net-standard
+# language code is pl) may wish to use "AddLanguage pl .po" to
+# avoid the ambiguity with the common suffix for perl scripts.
+#
+# Note 2: The example entries below illustrate that in some cases
+# the two character 'Language' abbreviation is not identical to
+# the two character 'Country' code for its country,
+# E.g. 'Danmark/dk' versus 'Danish/da'.
+#
+# Note 3: In the case of 'ltz' we violate the RFC by using a three char
+# specifier. There is 'work in progress' to fix this and get
+# the reference data for rfc1766 cleaned up.
+#
+# Catalan (ca) - Croatian (hr) - Czech (cs) - Danish (da) - Dutch (nl)
+# English (en) - Esperanto (eo) - Estonian (et) - French (fr) - German (de)
+# Greek-Modern (el) - Hebrew (he) - Italian (it) - Japanese (ja)
+# Korean (ko) - Luxembourgeois* (ltz) - Norwegian Nynorsk (nn)
+# Norwegian (no) - Polish (pl) - Portugese (pt)
+# Brazilian Portuguese (pt-BR) - Russian (ru) - Swedish (sv)
+# Simplified Chinese (zh-CN) - Spanish (es) - Traditional Chinese (zh-TW)
+AddLanguage ca .ca
+AddLanguage cs .cz .cs
+AddLanguage da .dk
+AddLanguage de .de
+AddLanguage el .el
+AddLanguage en .en
+AddLanguage eo .eo
+AddLanguage es .es
+AddLanguage et .et
+AddLanguage fr .fr
+AddLanguage he .he
+AddLanguage hr .hr
+AddLanguage it .it
+AddLanguage ja .ja
+AddLanguage ko .ko
+AddLanguage ltz .ltz
+AddLanguage nl .nl
+AddLanguage nn .nn
+AddLanguage no .no
+AddLanguage pl .po
+AddLanguage pt .pt
+AddLanguage pt-BR .pt-br
+AddLanguage ru .ru
+AddLanguage sv .sv
+AddLanguage zh-CN .zh-cn
+AddLanguage zh-TW .zh-tw
+
+# LanguagePriority allows you to give precedence to some languages
+# in case of a tie during content negotiation.
+#
+# Just list the languages in decreasing order of preference. We have
+# more or less alphabetized them here. You probably want to change this.
+LanguagePriority en ca cs da de el eo es et fr he hr it ja ko ltz nl nn no pl pt pt-BR ru sv zh-CN zh-TW
+
+# ForceLanguagePriority allows you to serve a result page rather than
+# MULTIPLE CHOICES (Prefer) [in case of a tie] or NOT ACCEPTABLE (Fallback)
+# [in case no accepted languages matched the available variants]
+ForceLanguagePriority Prefer Fallback
+
+# Commonly used filename extensions to character sets. You probably
+# want to avoid clashes with the language extensions, unless you
+# are good at carefully testing your setup after each change.
+# See http://www.iana.org/assignments/character-sets for the
+# official list of charset names and their respective RFCs.
+AddCharset us-ascii.ascii .us-ascii
+AddCharset ISO-8859-1 .iso8859-1 .latin1
+AddCharset ISO-8859-2 .iso8859-2 .latin2 .cen
+AddCharset ISO-8859-3 .iso8859-3 .latin3
+AddCharset ISO-8859-4 .iso8859-4 .latin4
+AddCharset ISO-8859-5 .iso8859-5 .cyr .iso-ru
+AddCharset ISO-8859-6 .iso8859-6 .arb .arabic
+AddCharset ISO-8859-7 .iso8859-7 .grk .greek
+AddCharset ISO-8859-8 .iso8859-8 .heb .hebrew
+AddCharset ISO-8859-9 .iso8859-9 .latin5 .trk
+AddCharset ISO-8859-10 .iso8859-10 .latin6
+AddCharset ISO-8859-13 .iso8859-13
+AddCharset ISO-8859-14 .iso8859-14 .latin8
+AddCharset ISO-8859-15 .iso8859-15 .latin9
+AddCharset ISO-8859-16 .iso8859-16 .latin10
+AddCharset ISO-2022-JP .iso2022-jp .jis
+AddCharset ISO-2022-KR .iso2022-kr .kis
+AddCharset ISO-2022-CN .iso2022-cn .cis
+AddCharset Big5.Big5 .big5 .b5
+AddCharset cn-Big5 .cn-big5
+# For russian, more than one charset is used (depends on client, mostly):
+AddCharset WINDOWS-1251 .cp-1251 .win-1251
+AddCharset CP866 .cp866
+AddCharset KOI8 .koi8
+AddCharset KOI8-E .koi8-e
+AddCharset KOI8-r .koi8-r .koi8-ru
+AddCharset KOI8-U .koi8-u
+AddCharset KOI8-ru .koi8-uk .ua
+AddCharset ISO-10646-UCS-2 .ucs2
+AddCharset ISO-10646-UCS-4 .ucs4
+AddCharset UTF-7 .utf7
+AddCharset UTF-8 .utf8
+AddCharset UTF-16 .utf16
+AddCharset UTF-16BE .utf16be
+AddCharset UTF-16LE .utf16le
+AddCharset UTF-32 .utf32
+AddCharset UTF-32BE .utf32be
+AddCharset UTF-32LE .utf32le
+AddCharset euc-cn .euc-cn
+AddCharset euc-gb .euc-gb
+AddCharset euc-jp .euc-jp
+AddCharset euc-kr .euc-kr
+# Not sure how euc-tw got in - IANA doesn't list it???
+AddCharset EUC-TW .euc-tw
+AddCharset gb2312 .gb2312 .gb
+AddCharset iso-10646-ucs-2 .ucs-2 .iso-10646-ucs-2
+AddCharset iso-10646-ucs-4 .ucs-4 .iso-10646-ucs-4
+AddCharset shift_jis .shift_jis .sjis
+</IfModule>
+</IfModule>
+</IfDefine>
+
+# vim: ts=4 filetype=apache
diff --git a/puppet/modules/apache/files/modules.d/Gentoo/00_mod_autoindex.conf b/puppet/modules/apache/files/modules.d/Gentoo/00_mod_autoindex.conf
new file mode 100644
index 00000000..2512357d
--- /dev/null
+++ b/puppet/modules/apache/files/modules.d/Gentoo/00_mod_autoindex.conf
@@ -0,0 +1,83 @@
+<IfModule autoindex_module>
+<IfModule alias_module>
+# We include the /icons/ alias for FancyIndexed directory listings. If
+# you do not use FancyIndexing, you may comment this out.
+Alias /icons/ "/var/www/localhost/icons/"
+
+<Directory "/var/www/localhost/icons">
+ Options Indexes MultiViews
+ AllowOverride None
+ Order allow,deny
+ Allow from all
+</Directory>
+</IfModule>
+
+# Directives controlling the display of server-generated directory listings.
+#
+# To see the listing of a directory, the Options directive for the
+# directory must include "Indexes", and the directory must not contain
+# a file matching those listed in the DirectoryIndex directive.
+
+# IndexOptions: Controls the appearance of server-generated directory
+# listings.
+IndexOptions FancyIndexing VersionSort
+
+# AddIcon* directives tell the server which icon to show for different
+# files or filename extensions. These are only displayed for
+# FancyIndexed directories.
+AddIconByEncoding (CMP,/icons/compressed.gif) x-compress x-gzip
+
+AddIconByType (TXT,/icons/text.gif) text/*
+AddIconByType (IMG,/icons/image2.gif) image/*
+AddIconByType (SND,/icons/sound2.gif) audio/*
+AddIconByType (VID,/icons/movie.gif) video/*
+
+AddIcon /icons/binary.gif .bin .exe
+AddIcon /icons/binhex.gif .hqx
+AddIcon /icons/tar.gif .tar
+AddIcon /icons/world2.gif .wrl .wrl.gz .vrml .vrm .iv
+AddIcon /icons/compressed.gif .Z .z .tgz .gz .zip
+AddIcon /icons/a.gif .ps .ai .eps
+AddIcon /icons/layout.gif .html .shtml .htm .pdf
+AddIcon /icons/text.gif .txt
+AddIcon /icons/c.gif .c
+AddIcon /icons/p.gif .pl .py
+AddIcon /icons/f.gif .for
+AddIcon /icons/dvi.gif .dvi
+AddIcon /icons/uuencoded.gif .uu
+AddIcon /icons/script.gif .conf .sh .shar .csh .ksh .tcl
+AddIcon /icons/tex.gif .tex
+AddIcon /icons/bomb.gif core
+
+AddIcon /icons/back.gif ..
+AddIcon /icons/hand.right.gif README
+AddIcon /icons/folder.gif ^^DIRECTORY^^
+AddIcon /icons/blank.gif ^^BLANKICON^^
+
+# DefaultIcon is which icon to show for files which do not have an icon
+# explicitly set.
+DefaultIcon /icons/unknown.gif
+
+# AddDescription allows you to place a short description after a file in
+# server-generated indexes. These are only displayed for FancyIndexed
+# directories.
+# Format: AddDescription "description" filename
+
+#AddDescription "GZIP compressed document" .gz
+#AddDescription "tar archive" .tar
+#AddDescription "GZIP compressed tar archive" .tgz
+
+# ReadmeName is the name of the README file the server will look for by
+# default, and append to directory listings.
+
+# HeaderName is the name of a file which should be prepended to
+# directory indexes.
+ReadmeName README.html
+HeaderName HEADER.html
+
+# IndexIgnore is a set of filenames which directory indexing should ignore
+# and not include in the listing. Shell-style wildcarding is permitted.
+IndexIgnore .??* *~ *# HEADER* README* RCS CVS *,v *,t
+</IfModule>
+
+# vim: ts=4 filetype=apache
diff --git a/puppet/modules/apache/files/modules.d/Gentoo/00_mod_info.conf b/puppet/modules/apache/files/modules.d/Gentoo/00_mod_info.conf
new file mode 100644
index 00000000..53fd7aea
--- /dev/null
+++ b/puppet/modules/apache/files/modules.d/Gentoo/00_mod_info.conf
@@ -0,0 +1,14 @@
+<IfDefine INFO>
+<IfModule info_module>
+# Allow remote server configuration reports, with the URL of
+# http://servername/server-info
+<Location /server-info>
+ SetHandler server-info
+ Order deny,allow
+ Deny from all
+ Allow from 127.0.0.1
+</Location>
+</IfModule>
+</IfDefine>
+
+# vim: ts=4 filetype=apache
diff --git a/puppet/modules/apache/files/modules.d/Gentoo/00_mod_log_config.conf b/puppet/modules/apache/files/modules.d/Gentoo/00_mod_log_config.conf
new file mode 100644
index 00000000..2f4244c9
--- /dev/null
+++ b/puppet/modules/apache/files/modules.d/Gentoo/00_mod_log_config.conf
@@ -0,0 +1,35 @@
+<IfModule log_config_module>
+# The following directives define some format nicknames for use with
+# a CustomLog directive (see below).
+LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
+LogFormat "%h %l %u %t \"%r\" %>s %b" common
+
+LogFormat "%{Referer}i -> %U" referer
+LogFormat "%{User-Agent}i" agent
+LogFormat "%v %h %l %u %t \"%r\" %>s %b %T" script
+LogFormat "%v %h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i VLOG=%{VLOG}e" vhost
+
+<IfModule logio_module>
+# You need to enable mod_logio.c to use %I and %O
+LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
+LogFormat "%v %h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" vhostio
+</IfModule>
+
+# The location and format of the access logfile (Common Logfile Format).
+# If you do not define any access logfiles within a <VirtualHost>
+# container, they will be logged here. Contrariwise, if you *do*
+# define per-<VirtualHost> access logfiles, transactions will be
+# logged therein and *not* in this file.
+CustomLog /var/log/apache2/access_log common
+
+# If you would like to have agent and referer logfiles,
+# uncomment the following directives.
+#CustomLog /var/log/apache2/referer_log referer
+#CustomLog /var/log/apache2/agent_logs agent
+
+# If you prefer a logfile with access, agent, and referer information
+# (Combined Logfile Format) you can use the following directive.
+#CustomLog /var/log/apache2/access_log combined
+</IfModule>
+
+# vim: ts=4 filetype=apache
diff --git a/puppet/modules/apache/files/modules.d/Gentoo/00_mod_mime.conf b/puppet/modules/apache/files/modules.d/Gentoo/00_mod_mime.conf
new file mode 100644
index 00000000..51f23d55
--- /dev/null
+++ b/puppet/modules/apache/files/modules.d/Gentoo/00_mod_mime.conf
@@ -0,0 +1,55 @@
+# DefaultType: the default MIME type the server will use for a document
+# if it cannot otherwise determine one, such as from filename extensions.
+# If your server contains mostly text or HTML documents, "text/plain" is
+# a good value. If most of your content is binary, such as applications
+# or images, you may want to use "application/octet-stream" instead to
+# keep browsers from trying to display binary files as though they are
+# text.
+DefaultType text/plain
+
+<IfModule mime_module>
+# TypesConfig points to the file containing the list of mappings from
+# filename extension to MIME-type.
+TypesConfig /etc/mime.types
+
+# AddType allows you to add to or override the MIME configuration
+# file specified in TypesConfig for specific file types.
+#AddType application/x-gzip .tgz
+
+# AddEncoding allows you to have certain browsers uncompress
+# information on the fly. Note: Not all browsers support this.
+#AddEncoding x-compress .Z
+#AddEncoding x-gzip .gz .tgz
+
+# If the AddEncoding directives above are commented-out, then you
+# probably should define those extensions to indicate media types:
+AddType application/x-compress .Z
+AddType application/x-gzip .gz .tgz
+
+# AddHandler allows you to map certain file extensions to "handlers":
+# actions unrelated to filetype. These can be either built into the server
+# or added with the Action directive (see below)
+
+# To use CGI scripts outside of ScriptAliased directories:
+# (You will also need to add "ExecCGI" to the "Options" directive.)
+#AddHandler cgi-script .cgi
+
+# For type maps (negotiated resources):
+#AddHandler type-map var
+
+# Filters allow you to process content before it is sent to the client.
+#
+# To parse .shtml files for server-side includes (SSI):
+# (You will also need to add "Includes" to the "Options" directive.)
+#AddType text/html .shtml
+#AddOutputFilter INCLUDES .shtml
+</IfModule>
+
+<IfModule mime_magic_module>
+# The mod_mime_magic module allows the server to use various hints from the
+# contents of the file itself to determine its type. The MIMEMagicFile
+# directive tells the module where the hint definitions are located.
+MIMEMagicFile /etc/apache2/magic
+</IfModule>
+
+# vim: ts=4 filetype=apache
diff --git a/puppet/modules/apache/files/modules.d/Gentoo/00_mod_status.conf b/puppet/modules/apache/files/modules.d/Gentoo/00_mod_status.conf
new file mode 100644
index 00000000..fa906766
--- /dev/null
+++ b/puppet/modules/apache/files/modules.d/Gentoo/00_mod_status.conf
@@ -0,0 +1,19 @@
+<IfDefine STATUS>
+<IfModule status_module>
+# Allow server status reports generated by mod_status,
+# with the URL of http://servername/server-status
+<Location /server-status>
+ SetHandler server-status
+ Order deny,allow
+ Deny from all
+ Allow from 127.0.0.1
+</Location>
+
+# ExtendedStatus controls whether Apache will generate "full" status
+# information (ExtendedStatus On) or just basic information (ExtendedStatus
+# Off) when the "server-status" handler is called.
+ExtendedStatus On
+</IfModule>
+</IfDefine>
+
+# vim: ts=4 filetype=apache
diff --git a/puppet/modules/apache/files/modules.d/Gentoo/00_mod_userdir.conf b/puppet/modules/apache/files/modules.d/Gentoo/00_mod_userdir.conf
new file mode 100644
index 00000000..3fb69117
--- /dev/null
+++ b/puppet/modules/apache/files/modules.d/Gentoo/00_mod_userdir.conf
@@ -0,0 +1,40 @@
+# Settings for user home directories
+
+<IfDefine USERDIR>
+<IfModule userdir_module>
+
+# UserDir: The name of the directory that is appended onto a user's home
+# directory if a ~user request is received. Note that you must also set
+# the default access control for these directories, as in the example below.
+UserDir public_html
+
+# Control access to UserDir directories. The following is an example
+# for a site where these directories are restricted to read-only.
+<Directory /home/*/public_html>
+ AllowOverride FileInfo AuthConfig Limit Indexes
+ Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
+ <Limit GET POST OPTIONS>
+ Order allow,deny
+ Allow from all
+ </Limit>
+ <LimitExcept GET POST OPTIONS>
+ Order deny,allow
+ Deny from all
+ </LimitExcept>
+</Directory>
+
+# Suexec isn't really required to run cgi-scripts, but it's a really good
+# idea if you have multiple users serving websites...
+<IfDefine SUEXEC>
+<IfModule suexec_module>
+<Directory /home/*/public_html/cgi-bin>
+ Options ExecCGI
+ SetHandler cgi-script
+</Directory>
+</IfModule>
+</IfDefine>
+
+</IfModule>
+</IfDefine>
+
+# vim: ts=4 filetype=apache
diff --git a/puppet/modules/apache/files/modules.d/Gentoo/00_mpm.conf b/puppet/modules/apache/files/modules.d/Gentoo/00_mpm.conf
new file mode 100644
index 00000000..01833059
--- /dev/null
+++ b/puppet/modules/apache/files/modules.d/Gentoo/00_mpm.conf
@@ -0,0 +1,102 @@
+# Server-Pool Management (MPM specific)
+
+# PidFile: The file in which the server should record its process
+# identification number when it starts.
+#
+# Note that this is the default PidFile for most MPMs.
+PidFile /var/run/apache2.pid
+
+# The accept serialization lock file MUST BE STORED ON A LOCAL DISK.
+#LockFile /var/run/apache2.lock
+
+# Only one of the below sections will be relevant on your
+# installed httpd. Use "/usr/sbin/apache2 -l" to find out the
+# active mpm.
+
+# common MPM configuration
+# These configuration directives apply to all MPMs
+#
+# StartServers: Number of child server processes created at startup
+# MaxClients: Maximum number of child processes to serve requests
+# MaxRequestsPerChild: Limit on the number of requests that an individual child
+# server will handle during its life
+
+
+# prefork MPM
+# This is the default MPM if USE=-threads
+#
+# MinSpareServers: Minimum number of idle child server processes
+# MaxSpareServers: Maximum number of idle child server processes
+<IfModule mpm_prefork_module>
+ StartServers 5
+ MinSpareServers 5
+ MaxSpareServers 10
+ MaxClients 150
+ MaxRequestsPerChild 10000
+</IfModule>
+
+# worker MPM
+# This is the default MPM if USE=threads
+#
+# MinSpareThreads: Minimum number of idle threads available to handle request spikes
+# MaxSpareThreads: Maximum number of idle threads
+# ThreadsPerChild: Number of threads created by each child process
+<IfModule mpm_worker_module>
+ StartServers 2
+ MinSpareThreads 25
+ MaxSpareThreads 75
+ ThreadsPerChild 25
+ MaxClients 150
+ MaxRequestsPerChild 10000
+</IfModule>
+
+# event MPM
+#
+# MinSpareThreads: Minimum number of idle threads available to handle request spikes
+# MaxSpareThreads: Maximum number of idle threads
+# ThreadsPerChild: Number of threads created by each child process
+<IfModule mpm_event_module>
+ StartServers 2
+ MinSpareThreads 25
+ MaxSpareThreads 75
+ ThreadsPerChild 25
+ MaxClients 150
+ MaxRequestsPerChild 10000
+</IfModule>
+
+# peruser MPM
+#
+# MinSpareProcessors: Minimum number of idle child server processes
+# MinProcessors: Minimum number of processors per virtual host
+# MaxProcessors: Maximum number of processors per virtual host
+# ExpireTimeout: Maximum idle time before a child is killed, 0 to disable
+# Multiplexer: Specify a Multiplexer child configuration.
+# Processor: Specify a user and group for a specific child process
+<IfModule mpm_peruser_module>
+ MinSpareProcessors 2
+ MinProcessors 2
+ MaxProcessors 10
+ MaxClients 150
+ MaxRequestsPerChild 1000
+ ExpireTimeout 1800
+
+ # KeepAlive *MUST* be set to off
+ KeepAlive Off
+
+ Multiplexer nobody nobody
+ Processor apache apache
+</IfModule>
+
+# itk MPM
+#
+# MinSpareServers: Minimum number of idle child server processes
+# MaxSpareServers: Maximum number of idle child server processes
+<IfModule mpm_itk_module>
+ StartServers 5
+ MinSpareServers 5
+ MaxSpareServers 10
+ MaxClients 150
+ MaxRequestsPerChild 10000
+</IfModule>
+
+# vim: ts=4 filetype=apache
diff --git a/puppet/modules/apache/files/modules.d/Gentoo/10_mod_mem_cache.conf b/puppet/modules/apache/files/modules.d/Gentoo/10_mod_mem_cache.conf
new file mode 100644
index 00000000..ad7fa9e0
--- /dev/null
+++ b/puppet/modules/apache/files/modules.d/Gentoo/10_mod_mem_cache.conf
@@ -0,0 +1,10 @@
+<IfDefine MEM_CACHE>
+# 128MB cache for objects < 2MB
+CacheEnable mem /
+MCacheSize 131072
+MCacheMaxObjectCount 1000
+MCacheMinObjectSize 1
+MCacheMaxObjectSize 2048
+</IfDefine>
+
+# vim: ts=4 filetype=apache
diff --git a/puppet/modules/apache/files/modules.d/Gentoo/40_mod_ssl.conf b/puppet/modules/apache/files/modules.d/Gentoo/40_mod_ssl.conf
new file mode 100644
index 00000000..331783a6
--- /dev/null
+++ b/puppet/modules/apache/files/modules.d/Gentoo/40_mod_ssl.conf
@@ -0,0 +1,65 @@
+# Note: The following must must be present to support
+# starting without SSL on platforms with no /dev/random equivalent
+# but a statically compiled-in mod_ssl.
+<IfModule ssl_module>
+SSLRandomSeed startup builtin
+SSLRandomSeed connect builtin
+</IfModule>
+
+<IfDefine SSL>
+<IfModule ssl_module>
+# This is the Apache server configuration file providing SSL support.
+# It contains the configuration directives to instruct the server how to
+# serve pages over an https connection. For detailing information about these
+# directives see <URL:http://httpd.apache.org/docs/2.2/mod/mod_ssl.html>
+
+# Do NOT simply read the instructions in here without understanding
+# what they do. They're here only as hints or reminders. If you are unsure
+# consult the online docs. You have been warned.
+
+## Pseudo Random Number Generator (PRNG):
+# Configure one or more sources to seed the PRNG of the SSL library.
+# The seed data should be of good random quality.
+# WARNING! On some platforms /dev/random blocks if not enough entropy
+# is available. This means you then cannot use the /dev/random device
+# because it would lead to very long connection times (as long as
+# it requires to make more entropy available). But usually those
+# platforms additionally provide a /dev/urandom device which doesn't
+# block. So, if available, use this one instead. Read the mod_ssl User
+# Manual for more details.
+#SSLRandomSeed startup file:/dev/random 512
+#SSLRandomSeed startup file:/dev/urandom 512
+#SSLRandomSeed connect file:/dev/random 512
+#SSLRandomSeed connect file:/dev/urandom 512
+
+## SSL Global Context:
+# All SSL configuration in this context applies both to the main server and
+# all SSL-enabled virtual hosts.
+
+# Some MIME-types for downloading Certificates and CRLs
+<IfModule mime_module>
+ AddType application/x-x509-ca-cert .crt
+ AddType application/x-pkcs7-crl .crl
+</IfModule>
+
+## Pass Phrase Dialog:
+# Configure the pass phrase gathering process. The filtering dialog program
+# (`builtin' is a internal terminal dialog) has to provide the pass phrase on
+# stdout.
+SSLPassPhraseDialog builtin
+
+## Inter-Process Session Cache:
+# Configure the SSL Session Cache: First the mechanism to use and second the
+# expiring timeout (in seconds).
+#SSLSessionCache dbm:/var/run/ssl_scache
+SSLSessionCache shmcb:/var/run/ssl_scache(512000)
+SSLSessionCacheTimeout 300
+
+## Semaphore:
+# Configure the path to the mutual exclusion semaphore the SSL engine uses
+# internally for inter-process synchronization.
+SSLMutex file:/var/run/ssl_mutex
+</IfModule>
+</IfDefine>
+
+# vim: ts=4 filetype=apache
diff --git a/puppet/modules/apache/files/modules.d/Gentoo/45_mod_dav.conf b/puppet/modules/apache/files/modules.d/Gentoo/45_mod_dav.conf
new file mode 100644
index 00000000..b15ca017
--- /dev/null
+++ b/puppet/modules/apache/files/modules.d/Gentoo/45_mod_dav.conf
@@ -0,0 +1,56 @@
+<IfDefine DAV>
+<IfModule dav_module>
+<IfModule dav_fs_module>
+DavLockDB "/var/lib/dav/lockdb"
+
+# The following example gives DAV write access to a directory called
+# "uploads" under the ServerRoot directory.
+<IfModule alias_module>
+<IfModule auth_digest_module>
+<IfModule authn_file_module>
+Alias /uploads "/var/www/uploads"
+
+<Directory "/var/www/uploads">
+ Dav On
+
+ AuthType Digest
+ AuthName DAV-upload
+
+ # You can use the htdigest program to create the password database:
+ # htdigest -c "/var/www/.htpasswd-dav" DAV-upload admin
+ AuthUserFile "/var/www/.htpasswd-dav"
+
+ # Allow access from any host
+ Order allow,deny
+ Allow from all
+
+ # Allow universal read-access, but writes are restricted
+ # to the admin user.
+ <LimitExcept GET OPTIONS>
+ require user admin
+ </LimitExcept>
+</Directory>
+</IfModule>
+</IfModule>
+</IfModule>
+
+</IfModule>
+</IfModule>
+
+# The following directives disable redirects on non-GET requests for
+# a directory that does not include the trailing slash. This fixes a
+# problem with several clients that do not appropriately handle
+# redirects for folders with DAV methods.
+<IfModule setenvif_module>
+BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully
+BrowserMatch "MS FrontPage" redirect-carefully
+BrowserMatch "^WebDrive" redirect-carefully
+BrowserMatch "^WebDAVFS/1.[012345]" redirect-carefully
+BrowserMatch "^gnome-vfs/1.0" redirect-carefully
+BrowserMatch "^XML Spy" redirect-carefully
+BrowserMatch "^Dreamweaver-WebDAV-SCM1" redirect-carefully
+</IfModule>
+
+</IfDefine>
+
+# vim: ts=4 filetype=apache
diff --git a/puppet/modules/apache/files/modules.d/Gentoo/46_mod_ldap.conf b/puppet/modules/apache/files/modules.d/Gentoo/46_mod_ldap.conf
new file mode 100644
index 00000000..837bc6e6
--- /dev/null
+++ b/puppet/modules/apache/files/modules.d/Gentoo/46_mod_ldap.conf
@@ -0,0 +1,29 @@
+# Examples below are taken from the online documentation
+# Refer to:
+# http://localhost/manual/mod/mod_ldap.html
+# http://localhost/manual/mod/mod_auth_ldap.html
+<IfDefine LDAP>
+<IfModule ldap_module>
+LDAPSharedCacheSize 200000
+LDAPCacheEntries 1024
+LDAPCacheTTL 600
+LDAPOpCacheEntries 1024
+LDAPOpCacheTTL 600
+
+<Location /ldap-status>
+ SetHandler ldap-status
+ Order deny,allow
+ Deny from all
+ Allow from 127.0.0.1
+</Location>
+</IfModule>
+</IfDefine>
+
+<IfDefine AUTHNZ_LDAP>
+<IfModule authnz_ldap_module>
+ #AuthLDAPURL ldap://ldap1.airius.com:389/ou=People, o=Airius?uid?sub?(objectClass=*)
+ #require valid-user
+</IfModule>
+</IfDefine>
+
+# vim: ts=4 filetype=apache
diff --git a/puppet/modules/apache/files/modules.d/Gentoo/70_mod_php5.conf b/puppet/modules/apache/files/modules.d/Gentoo/70_mod_php5.conf
new file mode 100644
index 00000000..a8254359
--- /dev/null
+++ b/puppet/modules/apache/files/modules.d/Gentoo/70_mod_php5.conf
@@ -0,0 +1,18 @@
+<IfDefine PHP5>
+ # Load the module first
+ <IfModule !mod_php5.c>
+ LoadModule php5_module modules/libphp5.so
+ </IfModule>
+
+ # Set it to handle the files
+ <IfModule mod_mime.c>
+ AddType application/x-httpd-php .php
+ AddType application/x-httpd-php .phtml
+ AddType application/x-httpd-php .php3
+ AddType application/x-httpd-php .php4
+ AddType application/x-httpd-php .php5
+ AddType application/x-httpd-php-source .phps
+ </IfModule>
+
+ DirectoryIndex index.php index.phtml
+</IfDefine>
diff --git a/puppet/modules/apache/files/munin/apache_activity b/puppet/modules/apache/files/munin/apache_activity
new file mode 100755
index 00000000..65fc0722
--- /dev/null
+++ b/puppet/modules/apache/files/munin/apache_activity
@@ -0,0 +1,99 @@
+#!/usr/bin/perl
+#
+# Parameters supported:
+#
+# config
+# autoconf
+#
+# Configurable variables
+#
+# url - Override default status-url
+#
+# Magic markers:
+#%# family=auto
+#%# capabilities=autoconf
+
+my $ret = undef;
+if (!eval "require LWP::UserAgent;") {
+ $ret = "LWP::UserAgent not found";
+}
+
+my $URL = exists $ENV{'url'} ? $ENV{'url'} : "http://127.0.0.1:%d/server-status?auto";
+my @PORTS = exists $ENV{'ports'} ? split(' ', $ENV{'ports'}) : (80);
+my %chars = (
+ # '\_' => 'Waiting',
+ # 'S' => 'Starting up',
+ 'R' => 'Reading request',
+ 'W' => 'Sending reply',
+ 'K' => 'Keepalive',
+ 'D' => 'DNS lookup',
+ 'C' => 'Closing',
+ # 'L' => 'Logging',
+ # 'G' => 'Gracefully finishing',
+ # 'I' => 'Idle cleanup',
+ # '\.' => 'Open slot',
+ );
+
+# "_" Waiting for Connection, "S" Starting up, "R" Reading Request,
+# "W" Sending Reply, "K" Keepalive (read), "D" DNS Lookup,
+# "C" Closing connection, "L" Logging, "G" Gracefully finishing,
+# "I" Idle cleanup of worker, "." Open slot with no current process
+
+if (exists $ARGV[0] and $ARGV[0] eq "autoconf") {
+ if ($ret) {
+ print "no ($ret)\n";
+ exit 1;
+ }
+ my $ua = LWP::UserAgent->new(timeout => 30);
+ my @badports;
+
+ foreach my $port (@PORTS) {
+ my $url = sprintf $URL, $port;
+ my $response = $ua->request(HTTP::Request->new('GET',$url));
+ push @badports, $port unless $response->is_success and $response->content =~ /Scoreboard/im;
+ }
+
+ if (@badports) {
+ print "no (no apache server-status on ports @badports)\n";
+ exit 1;
+ } else {
+ print "yes\n";
+ exit 0;
+ }
+}
+
+if (exists $ARGV[0] and $ARGV[0] eq "config") {
+ print "graph_title Apache activity\n";
+ print "graph_args --base 1000 -l 0\n";
+ print "graph_category apache\n";
+ print "graph_vlabel processes\n";
+ foreach my $port (@PORTS) {
+ while (my ($char, $val) = each (%chars)) {
+ $char =~ s/\\\./dot/;
+ $char =~ s/\\\_/underline/;
+ print "activity_${port}_${char}.label ";
+ print $val, "\n";
+ print "activity_${port}_${char}.type GAUGE\n";
+ }
+ }
+ exit 0;
+}
+
+foreach my $port (@PORTS) {
+ my $ua = LWP::UserAgent->new (timeout => 30);
+ my $url = sprintf $URL, $port;
+ my $response = $ua->request (HTTP::Request->new('GET',$url));
+ if ($response->content =~ /^Scoreboard\:\s?(.*)$/sm) {
+ my $string = $1;
+ chomp $string;
+ my @act = split (//, $string);
+ foreach my $char (keys (%chars)) {
+ my $num = scalar (grep (/$char/, @act));
+ $char =~ s/\\\./dot/;
+ $char =~ s/\\\_/underline/;
+ print "activity_${port}_${char}.value $num\n";
+ }
+ }
+}
+
+
diff --git a/puppet/modules/apache/files/scripts/OpenBSD/bin/apache_logrotate.sh b/puppet/modules/apache/files/scripts/OpenBSD/bin/apache_logrotate.sh
new file mode 100644
index 00000000..c2fcad97
--- /dev/null
+++ b/puppet/modules/apache/files/scripts/OpenBSD/bin/apache_logrotate.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+WEBROOT="/var/www/htdocs"
+#PIDFILE="/var/www/logs/httpd.pid"
+echo "#Autogenrated newsyslog.conf\n# logfile_name owner:group mode count size when flags"
+find /var/www/logs -name '*_log' -exec perl -e 'print "\n{}\twww:www\t644\t30\t*\t\$D0\tZ" ' \;
+find $WEBROOT -name '*_log' -exec perl -e 'print "\n{}\twww:www\t644\t30\t*\t\$D0\tZ" ' \;
+perl -e 'print "\t\t \"/bin/sh /opt/bin/restart_apache.sh\"";'
diff --git a/puppet/modules/apache/files/scripts/OpenBSD/bin/restart_apache.sh b/puppet/modules/apache/files/scripts/OpenBSD/bin/restart_apache.sh
new file mode 100644
index 00000000..4dc936d3
--- /dev/null
+++ b/puppet/modules/apache/files/scripts/OpenBSD/bin/restart_apache.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+ignoreout='Processing config'
+apachectl restart 2>&1 | (egrep -v -e "_default_ VirtualHost overlap on port 443" -e "$ignoreout" -e "/usr/sbin/apachectl restart: httpd restarted" || true )
+sleep 10
+apachectl start 2>&1 | (egrep -v -e "_default_ VirtualHost overlap on port 443" -e "$ignoreout" -e "/usr/sbin/apachectl startssl: httpd started" || true )
diff --git a/puppet/modules/apache/files/scripts/OpenBSD/bin/restart_apache_ssl.sh b/puppet/modules/apache/files/scripts/OpenBSD/bin/restart_apache_ssl.sh
new file mode 100644
index 00000000..314018b6
--- /dev/null
+++ b/puppet/modules/apache/files/scripts/OpenBSD/bin/restart_apache_ssl.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+ignoreout='Processing config'
+apachectl restart 2>&1 | (egrep -v -e "_default_ VirtualHost overlap on port 443" -e "$ignoreout" -e "/usr/sbin/apachectl restart: httpd restarted" || true )
+sleep 10
+apachectl startssl 2>&1 | (egrep -v -e "_default_ VirtualHost overlap on port 443" -e "$ignoreout" -e "/usr/sbin/apachectl startssl: httpd started" || true )
diff --git a/puppet/modules/apache/files/service/CentOS/httpd b/puppet/modules/apache/files/service/CentOS/httpd
new file mode 100644
index 00000000..7102c611
--- /dev/null
+++ b/puppet/modules/apache/files/service/CentOS/httpd
@@ -0,0 +1,22 @@
+# Configuration file for the httpd service.
+
+#
+# The default processing model (MPM) is the process-based
+# 'prefork' model. A thread-based model, 'worker', is also
+# available, but does not work with some modules (such as PHP).
+# The service must be stopped before changing this variable.
+#
+#HTTPD=/usr/sbin/httpd.worker
+
+#
+# To pass additional options (for instance, -D definitions) to the
+# httpd binary at startup, set OPTIONS here.
+#
+#OPTIONS=
+
+#
+# By default, the httpd process is started in the C locale; to
+# change the locale in which the server runs, the HTTPD_LANG
+# variable can be set.
+#
+#HTTPD_LANG=C
diff --git a/puppet/modules/apache/files/service/CentOS/httpd.itk b/puppet/modules/apache/files/service/CentOS/httpd.itk
new file mode 100644
index 00000000..62a2d24f
--- /dev/null
+++ b/puppet/modules/apache/files/service/CentOS/httpd.itk
@@ -0,0 +1,23 @@
+# Configuration file for the httpd service.
+
+#
+# The default processing model (MPM) is the process-based
+# 'prefork' model. A thread-based model, 'worker', is also
+# available, but does not work with some modules (such as PHP).
+# The service must be stopped before changing this variable.
+#
+#HTTPD=/usr/sbin/httpd.worker
+HTTPD=/usr/sbin/httpd.itk
+
+#
+# To pass additional options (for instance, -D definitions) to the
+# httpd binary at startup, set OPTIONS here.
+#
+#OPTIONS=
+
+#
+# By default, the httpd process is started in the C locale; to
+# change the locale in which the server runs, the HTTPD_LANG
+# variable can be set.
+#
+#HTTPD_LANG=C
diff --git a/puppet/modules/apache/files/service/CentOS/httpd.itk_plus b/puppet/modules/apache/files/service/CentOS/httpd.itk_plus
new file mode 100644
index 00000000..4d74de2c
--- /dev/null
+++ b/puppet/modules/apache/files/service/CentOS/httpd.itk_plus
@@ -0,0 +1,24 @@
+# Configuration file for the httpd service.
+
+#
+# The default processing model (MPM) is the process-based
+# 'prefork' model. A thread-based model, 'worker', is also
+# available, but does not work with some modules (such as PHP).
+# The service must be stopped before changing this variable.
+#
+#HTTPD=/usr/sbin/httpd.worker
+HTTPD=/usr/sbin/httpd
+HTTPD_LOCAL=/usr/sbin/httpd.itk
+
+#
+# To pass additional options (for instance, -D definitions) to the
+# httpd binary at startup, set OPTIONS here.
+#
+#OPTIONS=
+
+#
+# By default, the httpd process is started in the C locale; to
+# change the locale in which the server runs, the HTTPD_LANG
+# variable can be set.
+#
+#HTTPD_LANG=C
diff --git a/puppet/modules/apache/files/service/CentOS/httpd.worker b/puppet/modules/apache/files/service/CentOS/httpd.worker
new file mode 100644
index 00000000..290923f5
--- /dev/null
+++ b/puppet/modules/apache/files/service/CentOS/httpd.worker
@@ -0,0 +1,22 @@
+# Configuration file for the httpd service.
+
+#
+# The default processing model (MPM) is the process-based
+# 'prefork' model. A thread-based model, 'worker', is also
+# available, but does not work with some modules (such as PHP).
+# The service must be stopped before changing this variable.
+#
+HTTPD=/usr/sbin/httpd.worker
+
+#
+# To pass additional options (for instance, -D definitions) to the
+# httpd binary at startup, set OPTIONS here.
+#
+#OPTIONS=
+
+#
+# By default, the httpd process is started in the C locale; to
+# change the locale in which the server runs, the HTTPD_LANG
+# variable can be set.
+#
+#HTTPD_LANG=C
diff --git a/puppet/modules/apache/files/vhosts.d/CentOS/0-default.conf b/puppet/modules/apache/files/vhosts.d/CentOS/0-default.conf
new file mode 100644
index 00000000..a8a84813
--- /dev/null
+++ b/puppet/modules/apache/files/vhosts.d/CentOS/0-default.conf
@@ -0,0 +1,11 @@
+############################################################
+### This file is managed by PUPPET! ####
+### Only modify in repo or you will loose the changes! ####
+############################################################
+
+<VirtualHost *:80>
+ Include include.d/defaults.inc
+ DocumentRoot /var/www/html
+</VirtualHost>
+
+# vim: ts=4 filetype=apache
diff --git a/puppet/modules/apache/files/vhosts.d/Debian/0-default.conf b/puppet/modules/apache/files/vhosts.d/Debian/0-default.conf
new file mode 100644
index 00000000..2cbd90fe
--- /dev/null
+++ b/puppet/modules/apache/files/vhosts.d/Debian/0-default.conf
@@ -0,0 +1,41 @@
+<VirtualHost *:80>
+ ServerAdmin webmaster@localhost
+
+ DocumentRoot /var/www/
+ <Directory />
+ Options FollowSymLinks
+ AllowOverride None
+ </Directory>
+ <Directory /var/www/>
+ Options Indexes FollowSymLinks MultiViews
+ AllowOverride None
+ Order allow,deny
+ allow from all
+ </Directory>
+
+ ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
+ <Directory "/usr/lib/cgi-bin">
+ AllowOverride None
+ Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
+ Order allow,deny
+ Allow from all
+ </Directory>
+
+ ErrorLog /var/log/apache2/error.log
+
+ # Possible values include: debug, info, notice, warn, error, crit,
+ # alert, emerg.
+ LogLevel warn
+
+ CustomLog /var/log/apache2/access.log combined
+
+ Alias /doc/ "/usr/share/doc/"
+ <Directory "/usr/share/doc/">
+ Options Indexes MultiViews FollowSymLinks
+ AllowOverride None
+ Order deny,allow
+ Deny from all
+ Allow from 127.0.0.0/255.0.0.0 ::1/128
+ </Directory>
+
+</VirtualHost>
diff --git a/puppet/modules/apache/files/vhosts.d/Gentoo/0-default.conf b/puppet/modules/apache/files/vhosts.d/Gentoo/0-default.conf
new file mode 100644
index 00000000..03468459
--- /dev/null
+++ b/puppet/modules/apache/files/vhosts.d/Gentoo/0-default.conf
@@ -0,0 +1,51 @@
+# ###########################################################
+# ### this file is managed by PUPPET ####
+# ### only modify in svn or you will loose the changes ! ####
+# ###########################################################
+# Virtual Hosts
+#
+# If you want to maintain multiple domains/hostnames on your
+# machine you can setup VirtualHost containers for them. Most configurations
+# use only name-based virtual hosts so the server doesn't need to worry about
+# IP addresses. This is indicated by the asterisks in the directives below.
+#
+# Please see the documentation at
+# <URL:http://httpd.apache.org/docs/2.2/vhosts/>
+# for further details before you try to setup virtual hosts.
+#
+# You may use the command line option '-S' to verify your virtual host
+# configuration.
+
+<IfDefine DEFAULT_VHOST>
+# see bug #178966 why this is in here
+
+# Listen: Allows you to bind Apache to specific IP addresses and/or
+# ports, instead of the default. See also the <VirtualHost>
+# directive.
+#
+# Change this to Listen on specific IP addresses as shown below to
+# prevent Apache from glomming onto all bound IP addresses.
+#
+#Listen 12.34.56.78:80
+Listen 80
+
+# Use name-based virtual hosting.
+NameVirtualHost *:80
+
+# When virtual hosts are enabled, the main host defined in the default
+# httpd.conf configuration will go away. We redefine it here so that it is
+# still available.
+#
+# If you disable this vhost by removing -D DEFAULT_VHOST from
+# /etc/conf.d/apache2, the first defined virtual host elsewhere will be
+# the default.
+<VirtualHost *:80>
+ Include /etc/apache2/vhosts.d/default_vhost.include
+
+ <IfModule mpm_peruser_module>
+ ServerEnvironment apache apache
+ </IfModule>
+</VirtualHost>
+</IfDefine>
+
+# vim: ts=4 filetype=apache
diff --git a/puppet/modules/apache/files/vhosts.d/Gentoo/default_vhost.include b/puppet/modules/apache/files/vhosts.d/Gentoo/default_vhost.include
new file mode 100644
index 00000000..590c1848
--- /dev/null
+++ b/puppet/modules/apache/files/vhosts.d/Gentoo/default_vhost.include
@@ -0,0 +1,79 @@
+# ###########################################################
+# # copyleft 2008 immerda.ch
+# ###########################################################
+# ### this file is managed by PUPPET ####
+# ### only modify in svn or you will loose the changes ! ####
+# ###########################################################
+# ServerAdmin: Your address, where problems with the server should be
+# e-mailed. This address appears on some server-generated pages, such
+# as error documents. e.g. admin@your-domain.com
+ServerAdmin root@localhost
+
+# DocumentRoot: The directory out of which you will serve your
+# documents. By default, all requests are taken from this directory, but
+# symbolic links and aliases may be used to point to other locations.
+#
+# If you change this to something that isn't under /var/www then suexec
+# will no longer work.
+DocumentRoot "/var/www/localhost/htdocs"
+
+# This should be changed to whatever you set DocumentRoot to.
+<Directory "/var/www/localhost/htdocs">
+ # Possible values for the Options directive are "None", "All",
+ # or any combination of:
+ # Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews
+ #
+ # Note that "MultiViews" must be named *explicitly* --- "Options All"
+ # doesn't give it to you.
+ #
+ # The Options directive is both complicated and important. Please see
+ # http://httpd.apache.org/docs/2.2/mod/core.html#options
+ # for more information.
+ Options Indexes FollowSymLinks
+
+ # AllowOverride controls what directives may be placed in .htaccess files.
+ # It can be "All", "None", or any combination of the keywords:
+ # Options FileInfo AuthConfig Limit
+ AllowOverride All
+
+ # Controls who can get stuff from this server.
+ Order allow,deny
+ Allow from all
+</Directory>
+
+<IfModule alias_module>
+ # Redirect: Allows you to tell clients about documents that used to
+ # exist in your server's namespace, but do not anymore. The client
+ # will make a new request for the document at its new location.
+ # Example:
+ # Redirect permanent /foo http://www.example.com/bar
+
+ # Alias: Maps web paths into filesystem paths and is used to
+ # access content that does not live under the DocumentRoot.
+ # Example:
+ # Alias /webpath /full/filesystem/path
+ #
+ # If you include a trailing / on /webpath then the server will
+ # require it to be present in the URL. You will also likely
+ # need to provide a <Directory> section to allow access to
+ # the filesystem path.
+
+ # ScriptAlias: This controls which directories contain server scripts.
+ # ScriptAliases are essentially the same as Aliases, except that
+ # documents in the target directory are treated as applications and
+ # run by the server when requested rather than as documents sent to the
+ # client. The same rules about trailing "/" apply to ScriptAlias
+ # directives as to Alias.
+ ScriptAlias /cgi-bin/ "/var/www/localhost/cgi-bin/"
+</IfModule>
+
+# "/var/www/localhost/cgi-bin" should be changed to whatever your ScriptAliased
+# CGI directory exists, if you have that configured.
+<Directory "/var/www/localhost/cgi-bin">
+ AllowOverride None
+ Options None
+ Order allow,deny
+ Allow from all
+</Directory>
+
+# vim: ts=4 filetype=apache
diff --git a/puppet/modules/apache/files/vhosts.d/OpenBSD/0-default.conf b/puppet/modules/apache/files/vhosts.d/OpenBSD/0-default.conf
new file mode 100644
index 00000000..9c4aa9d5
--- /dev/null
+++ b/puppet/modules/apache/files/vhosts.d/OpenBSD/0-default.conf
@@ -0,0 +1,8 @@
+<VirtualHost *:80>
+ Include include.d/defaults.inc
+
+ DocumentRoot /var/www/htdocs/default/www/
+ ErrorLog /var/www/htdocs/default/logs/default_error_log
+ CustomLog /var/www/htdocs/default/logs/default_access_log combined
+</VirtualHost>
+
diff --git a/puppet/modules/apache/lib/facter/apache_version.rb b/puppet/modules/apache/lib/facter/apache_version.rb
new file mode 100644
index 00000000..f0521832
--- /dev/null
+++ b/puppet/modules/apache/lib/facter/apache_version.rb
@@ -0,0 +1,28 @@
+# determine the version of apache installed
+
+def parse_version(version_string)
+ version = ""
+ version_string.each_line do |line|
+ if line.match(/^Server version/)
+ version = line.scan(/Apache\/(.*) /)[0][0]
+ end
+ end
+ return version
+end
+
+Facter.add('apache_version') do
+ setcode do
+ case Facter.value('osfamily')
+ when /RedHat/
+ if File.exists?('/usr/sbin/httpd')
+ version = parse_version(%x(/usr/sbin/httpd -v))
+ end
+ when /Debian/
+ if File.exists?('/usr/sbin/apache2')
+ version = parse_version(%x(/usr/sbin/apache2 -v))
+ end
+ else
+ version = 'undef'
+ end
+ end
+end
diff --git a/puppet/modules/apache/lib/puppet/parser/functions/guess_apache_version.rb b/puppet/modules/apache/lib/puppet/parser/functions/guess_apache_version.rb
new file mode 100644
index 00000000..7537f6d9
--- /dev/null
+++ b/puppet/modules/apache/lib/puppet/parser/functions/guess_apache_version.rb
@@ -0,0 +1,39 @@
+# Try to guess the version of apache to be installed.
+# Certain apache modules depend on each other, so we
+# need to evaluate the apache version before it gets
+# installed. This function decides which apache version
+# is going to be installed based on the `operatingsystemrelease`
+# fact.
+module Puppet::Parser::Functions
+ newfunction(:guess_apache_version, :type => :rvalue) do |args|
+ release = lookupvar('operatingsystemrelease')
+ unknown = 'unknown'
+
+ case lookupvar('operatingsystem')
+
+ when 'Debian'
+ case release
+ when /^7.*/
+ version = '2.2'
+ when /^8.*/
+ version = '2.4'
+ else
+ version = unknown
+ end
+
+ when 'Ubuntu'
+ case release
+ when /(12.04|12.10|13.04|13.10)/
+ version = '2.2'
+ when /(14.04|14.10|15.04|15.10|16.04)/
+ version = '2.4'
+ else
+ version = unknown
+ end
+
+ else
+ version = unknown
+ end
+ version
+ end
+end
diff --git a/puppet/modules/apache/lib/puppet/parser/functions/htpasswd_sha1.rb b/puppet/modules/apache/lib/puppet/parser/functions/htpasswd_sha1.rb
new file mode 100644
index 00000000..937621d9
--- /dev/null
+++ b/puppet/modules/apache/lib/puppet/parser/functions/htpasswd_sha1.rb
@@ -0,0 +1,8 @@
+require 'digest/sha1'
+require 'base64'
+
+module Puppet::Parser::Functions
+ newfunction(:htpasswd_sha1, :type => :rvalue) do |args|
+ "{SHA}" + Base64.encode64(Digest::SHA1.digest(args[0]))
+ end
+end
diff --git a/puppet/modules/apache/manifests/base.pp b/puppet/modules/apache/manifests/base.pp
new file mode 100644
index 00000000..3f921599
--- /dev/null
+++ b/puppet/modules/apache/manifests/base.pp
@@ -0,0 +1,75 @@
+# setup base apache class
+class apache::base {
+ file{
+ 'vhosts_dir':
+ ensure => directory,
+ path => '/etc/apache2/vhosts.d',
+ purge => true,
+ recurse => true,
+ force => true,
+ notify => Service['apache'],
+ owner => root,
+ group => 0,
+ mode => '0644';
+ 'config_dir':
+ ensure => directory,
+ path => '/etc/apache2/conf.d',
+ owner => root,
+ group => 0,
+ mode => '0644';
+ 'include_dir':
+ ensure => directory,
+ path => '/etc/apache2/include.d',
+ purge => true,
+ recurse => true,
+ force => true,
+ notify => Service['apache'],
+ owner => root,
+ group => 0,
+ mode => '0644';
+ 'modules_dir':
+ ensure => directory,
+ path => '/etc/apache2/modules.d',
+ purge => true,
+ recurse => true,
+ force => true,
+ notify => Service['apache'],
+ owner => root,
+ group => 0,
+ mode => '0644';
+ 'htpasswd_dir':
+ ensure => directory,
+ path => '/var/www/htpasswds',
+ purge => true,
+ recurse => true,
+ force => true,
+ notify => Service['apache'],
+ owner => root,
+ group => 'apache',
+ mode => '0640';
+ 'web_dir':
+ ensure => directory,
+ path => '/var/www',
+ owner => root,
+ group => 0,
+ mode => '0644';
+ 'default_apache_index':
+ path => '/var/www/localhost/htdocs/index.html',
+ content => template('apache/default/default_index.erb'),
+ owner => root,
+ group => 0,
+ mode => '0644';
+ } -> anchor{'apache::basic_dirs::ready': }
+
+ apache::config::include{ 'defaults.inc': }
+ apache::config::global{ 'git.conf': }
+ if !$apache::no_default_site {
+ apache::vhost::file { '0-default': }
+ }
+
+ service{'apache':
+ ensure => running,
+ name => 'apache2',
+ enable => true,
+ }
+}
diff --git a/puppet/modules/apache/manifests/base/itk.pp b/puppet/modules/apache/manifests/base/itk.pp
new file mode 100644
index 00000000..7772bfdf
--- /dev/null
+++ b/puppet/modules/apache/manifests/base/itk.pp
@@ -0,0 +1,6 @@
+class apache::base::itk inherits apache::base {
+ File['htpasswd_dir']{
+ group => 0,
+ mode => 0644,
+ }
+}
diff --git a/puppet/modules/apache/manifests/centos.pp b/puppet/modules/apache/manifests/centos.pp
new file mode 100644
index 00000000..f4697155
--- /dev/null
+++ b/puppet/modules/apache/manifests/centos.pp
@@ -0,0 +1,86 @@
+### centos
+class apache::centos inherits apache::package {
+ $config_dir = '/etc/httpd'
+
+ Package[apache]{
+ name => 'httpd',
+ }
+ Service[apache]{
+ name => 'httpd',
+ restart => '/etc/init.d/httpd graceful',
+ }
+ File[vhosts_dir]{
+ path => "${config_dir}/vhosts.d",
+ }
+ File[config_dir]{
+ path => "${config_dir}/conf.d",
+ }
+ File[include_dir]{
+ path => "${config_dir}/include.d",
+ }
+ File[modules_dir]{
+ path => "${config_dir}/modules.d",
+ }
+ File[web_dir]{
+ path => '/var/www/vhosts',
+ }
+ File[default_apache_index]{
+ path => '/var/www/html/index.html',
+ }
+
+ if str2bool($::selinux) {
+ Selinux::Fcontext{
+ before => File[web_dir],
+ }
+ $seltype_rw = $::operatingsystemmajrelease ? {
+ 5 => 'httpd_sys_script_rw_t',
+ default => 'httpd_sys_rw_content_t'
+ }
+ selinux::fcontext{
+ [ '/var/www/vhosts/[^/]*/www(/.*)?',
+ '/var/www/vhosts/[^/]*/non_public(/.*)?',
+ '/var/www/vhosts/[^/]*/data(/.*)?',
+ '/var/www/vhosts/[^/]*/upload(/.*)?' ]:
+ require => Package['apache'],
+ setype => $seltype_rw;
+ '/var/www/vhosts/[^/]*/logs(/.*)?':
+ require => Package['apache'],
+ setype => 'httpd_log_t';
+ }
+ }
+ file{'apache_service_config':
+ path => '/etc/sysconfig/httpd',
+ source => [ "puppet:///modules/site_apache/service/CentOS/${::fqdn}/httpd",
+ 'puppet:///modules/site_apache/service/CentOS/httpd',
+ 'puppet:///modules/apache/service/CentOS/httpd' ],
+ require => Package['apache'],
+ notify => Service['apache'],
+ owner => root,
+ group => 0,
+ mode => '0644';
+ }
+
+ # this is for later fixes
+ exec{
+ 'adjust_pidfile':
+ command => 'sed -i "s/^#PidFile \(.*\)/PidFile \1/g" /etc/httpd/conf/httpd.conf',
+ unless => 'grep -qE \'^PidFile \' /etc/httpd/conf/httpd.conf',
+ require => Package['apache'],
+ notify => Service['apache'];
+ 'adjust_listen':
+ command => 'sed -i "s/^#Listen 80/Listen 80/g" /etc/httpd/conf/httpd.conf',
+ unless => 'grep -qE \'^Listen 80\' /etc/httpd/conf/httpd.conf',
+ require => Package['apache'],
+ notify => Service['apache'];
+ }
+
+ apache::config::global{'00-listen.conf':
+ ensure => absent,
+ }
+
+ include apache::logrotate::centos
+
+ apache::config::global{ 'welcome.conf': }
+ apache::config::global{ 'vhosts.conf': }
+}
+
diff --git a/puppet/modules/apache/manifests/centos/itk.pp b/puppet/modules/apache/manifests/centos/itk.pp
new file mode 100644
index 00000000..20f4270d
--- /dev/null
+++ b/puppet/modules/apache/manifests/centos/itk.pp
@@ -0,0 +1,10 @@
+# http://hostby.net/home/2008/07/12/centos-5-and-mpm-itk/
+class apache::centos::itk inherits apache::centos {
+ include ::apache::base::itk
+ Package['apache']{
+ name => 'httpd-itk',
+ }
+ File['apache_service_config']{
+ source => "puppet:///modules/apache/service/${::operatingsystem}/httpd.itk"
+ }
+}
diff --git a/puppet/modules/apache/manifests/centos/itk_plus.pp b/puppet/modules/apache/manifests/centos/itk_plus.pp
new file mode 100644
index 00000000..0df92c84
--- /dev/null
+++ b/puppet/modules/apache/manifests/centos/itk_plus.pp
@@ -0,0 +1,20 @@
+# http://hostby.net/home/2008/07/12/centos-5-and-mpm-itk/
+class apache::centos::itk_plus inherits apache::centos::itk {
+ Exec['adjust_pidfile']{
+ command => "sed -i 's/^PidFile \\(.*\\)/#PidFile \\1/g' /etc/httpd/conf/httpd.conf",
+ unless => "grep -qE '^#PidFile ' /etc/httpd/conf/httpd.conf",
+ }
+ Exec['adjust_listen']{
+ command => "sed -i 's/^Listen 80/#Listen 80/g' /etc/httpd/conf/httpd.conf",
+ unless => "grep -qE '^#Listen 80' /etc/httpd/conf/httpd.conf",
+ }
+
+ Apache::Config::Global['00-listen.conf']{
+ ensure => 'present',
+ content => template("apache/itk_plus/${::operatingsystem}/00-listen.conf.erb"),
+ }
+
+ File['apache_service_config']{
+ source => "puppet:///modules/apache/service/CentOS/httpd.itk_plus"
+ }
+}
diff --git a/puppet/modules/apache/manifests/centos/module.pp b/puppet/modules/apache/manifests/centos/module.pp
new file mode 100644
index 00000000..3220d1f8
--- /dev/null
+++ b/puppet/modules/apache/manifests/centos/module.pp
@@ -0,0 +1,30 @@
+define apache::centos::module(
+ $ensure = present,
+ $source = '',
+ $destination = ''
+){
+ $modules_dir = "${apache::centos::config_dir}/modules.d"
+ $real_destination = $destination ? {
+ '' => "${modules_dir}/${name}.so",
+ default => $destination,
+ }
+ $real_source = $source ? {
+ '' => [
+ "puppet:///modules/site_apache/modules.d/${::fqdn}/${name}.so",
+ "puppet:///modules/site_apache/modules.d/${apache::cluster_node}/${name}.so",
+ "puppet:///modules/site_apache/modules.d/${name}.so",
+ "puppet:///modules/apache/modules.d/${::operatingsystem}/${name}.so",
+ "puppet:///modules/apache/modules.d/${name}.so"
+ ],
+ default => "puppet:///$source",
+ }
+ file{"modules_${name}.conf":
+ ensure => $ensure,
+ path => $real_destination,
+ source => $real_source,
+ require => [ File[modules_dir], Package[apache] ],
+ notify => Service[apache],
+ owner => root, group => 0, mode => 0755;
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/centos/worker.pp b/puppet/modules/apache/manifests/centos/worker.pp
new file mode 100644
index 00000000..f374bb70
--- /dev/null
+++ b/puppet/modules/apache/manifests/centos/worker.pp
@@ -0,0 +1,5 @@
+class apache::centos::worker inherits apache::centos {
+ File['apache_service_config']{
+ source => "puppet:///modules/apache/service/${::operatingsystem}/httpd.worker"
+ }
+}
diff --git a/puppet/modules/apache/manifests/config/file.pp b/puppet/modules/apache/manifests/config/file.pp
new file mode 100644
index 00000000..7b058691
--- /dev/null
+++ b/puppet/modules/apache/manifests/config/file.pp
@@ -0,0 +1,106 @@
+# deploy apache configuration file
+# by default we assume it's a global configuration file
+define apache::config::file(
+ $ensure = present,
+ $target = false,
+ $type = 'global',
+ $source = 'absent',
+ $content = 'absent',
+ $destination = 'absent'
+){
+ case $type {
+ 'include': { $confdir = 'include.d' }
+ 'global': { $confdir = 'conf.d' }
+ default: { fail("Wrong config file type specified for ${name}") }
+ }
+ $real_destination = $destination ? {
+ 'absent' => $::operatingsystem ? {
+ centos => "${apache::centos::config_dir}/${confdir}/${name}",
+ gentoo => "${apache::gentoo::config_dir}/${name}",
+ debian => "${apache::debian::config_dir}/${confdir}/${name}",
+ ubuntu => "${apache::ubuntu::config_dir}/${confdir}/${name}",
+ openbsd => "${apache::openbsd::config_dir}/${confdir}/${name}",
+ default => "/etc/apache2/${confdir}/${name}",
+ },
+ default => $destination
+ }
+ file{"apache_${name}":
+ ensure => $ensure,
+ path => $real_destination,
+ notify => Service[apache],
+ owner => root,
+ group => 0,
+ mode => '0644';
+ }
+
+ case $ensure {
+ 'absent', 'purged': {
+ # We want to avoid all stuff related to source and content
+ }
+ 'link': {
+ if $target {
+ File["apache_${name}"] {
+ target => $target,
+ }
+ }
+ }
+ default: {
+ case $content {
+ 'absent': {
+ $real_source = $source ? {
+ 'absent' => [
+ "puppet:///modules/site_apache/${confdir}/${::fqdn}/${name}",
+ "puppet:///modules/site_apache/${confdir}/${apache::cluster_node}/${name}",
+ "puppet:///modules/site_apache/${confdir}/${::operatingsystem}.${::operatingsystemmajrelease}/${name}",
+ "puppet:///modules/site_apache/${confdir}/${::operatingsystem}/${name}",
+ "puppet:///modules/site_apache/${confdir}/${name}",
+ "puppet:///modules/apache/${confdir}/${::operatingsystem}.${::operatingsystemmajrelease}/${name}",
+ "puppet:///modules/apache/${confdir}/${::operatingsystem}/${name}",
+ "puppet:///modules/apache/${confdir}/${name}"
+ ],
+ default => $source
+ }
+ File["apache_${name}"]{
+ source => $real_source,
+ }
+ }
+ default: {
+ case $content {
+ 'absent': {
+ $real_source = $source ? {
+ 'absent' => [
+ "puppet:///modules/site-apache/${confdir}/${::fqdn}/${name}",
+ "puppet:///modules/site-apache/${confdir}/${apache::cluster_node}/${name}",
+ "puppet:///modules/site-apache/${confdir}/${::operatingsystem}.${::operatingsystemmajrelease}/${name}",
+ "puppet:///modules/site-apache/${confdir}/${::operatingsystem}/${name}",
+ "puppet:///modules/site-apache/${confdir}/${name}",
+ "puppet:///modules/apache/${confdir}/${::operatingsystem}.${::operatingsystemmajrelease}/${name}",
+ "puppet:///modules/apache/${confdir}/${::operatingsystem}/${name}",
+ "puppet:///modules/apache/${confdir}/${name}"
+ ],
+ default => $source,
+ }
+ File["apache_${name}"]{
+ source => $real_source,
+ }
+ }
+ default: {
+ File["apache_${name}"]{
+ content => $content,
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ case $::operatingsystem {
+ openbsd: { info("no package dependency on ${::operatingsystem} for ${name}") }
+ default: {
+ File["apache_${name}"]{
+ require => Package[apache],
+ }
+ }
+ }
+}
diff --git a/puppet/modules/apache/manifests/config/global.pp b/puppet/modules/apache/manifests/config/global.pp
new file mode 100644
index 00000000..8b0389be
--- /dev/null
+++ b/puppet/modules/apache/manifests/config/global.pp
@@ -0,0 +1,18 @@
+# deploy apache configuration file (global)
+# wrapper for apache::config::file
+define apache::config::global(
+ $ensure = present,
+ $target = false,
+ $source = 'absent',
+ $content = 'absent',
+ $destination = 'absent'
+){
+ apache::config::file { "${name}":
+ ensure => $ensure,
+ target => $target,
+ type => 'global',
+ source => $source,
+ content => $content,
+ destination => $destination,
+ }
+}
diff --git a/puppet/modules/apache/manifests/config/include.pp b/puppet/modules/apache/manifests/config/include.pp
new file mode 100644
index 00000000..4d676f05
--- /dev/null
+++ b/puppet/modules/apache/manifests/config/include.pp
@@ -0,0 +1,17 @@
+# deploy apache configuration file (includes for vhosts)
+define apache::config::include(
+ $ensure = present,
+ $target = false,
+ $source = 'absent',
+ $content = 'absent',
+ $destination = 'absent'
+){
+ apache::config::file { "${name}":
+ ensure => $ensure,
+ target => $target,
+ type => 'include',
+ source => $source,
+ content => $content,
+ destination => $destination,
+ }
+}
diff --git a/puppet/modules/apache/manifests/debian.pp b/puppet/modules/apache/manifests/debian.pp
new file mode 100644
index 00000000..6ae4cee8
--- /dev/null
+++ b/puppet/modules/apache/manifests/debian.pp
@@ -0,0 +1,44 @@
+### debian
+class apache::debian inherits apache::package {
+ $config_dir = '/etc/apache2'
+
+ Package[apache] {
+ name => 'apache2',
+ }
+ File[vhosts_dir] {
+ path => "${config_dir}/sites-enabled",
+ }
+ File[modules_dir] {
+ path => "${config_dir}/mods-enabled",
+ }
+ File[htpasswd_dir] {
+ path => '/var/www/htpasswds',
+ group => 'www-data',
+ }
+ File[default_apache_index] {
+ path => '/var/www/index.html',
+ }
+ file { 'apache_main_config':
+ path => "${config_dir}/apache2.conf",
+ source => [ "puppet:///modules/site_apache/config/Debian.${::lsbdistcodename}/${::fqdn}/apache2.conf",
+ "puppet:///modules/site_apache/config/Debian/${::fqdn}/apache2.conf",
+ "puppet:///modules/site_apache/config/Debian.${::lsbdistcodename}/apache2.conf",
+ 'puppet:///modules/site_apache/config/Debian/apache2.conf',
+ "puppet:///modules/apache/config/Debian.${::lsbdistcodename}/${::fqdn}/apache2.conf",
+ "puppet:///modules/apache/config/Debian/${::fqdn}/apache2.conf",
+ "puppet:///modules/apache/config/Debian.${::lsbdistcodename}/apache2.conf",
+ 'puppet:///modules/apache/config/Debian/apache2.conf' ],
+ require => Package['apache'],
+ notify => Service['apache'],
+ owner => root,
+ group => 0,
+ mode => '0644';
+ }
+ apache::config::global{ 'charset': }
+ apache::config::global{ 'security': }
+ file { 'default_debian_apache_vhost':
+ ensure => absent,
+ path => '/etc/apache2/sites-enabled/000-default',
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/debian/itk.pp b/puppet/modules/apache/manifests/debian/itk.pp
new file mode 100644
index 00000000..718a81b3
--- /dev/null
+++ b/puppet/modules/apache/manifests/debian/itk.pp
@@ -0,0 +1,9 @@
+class apache::debian::itk inherits apache::debian {
+ File['htpasswd_dir']{
+ group => 0,
+ mode => 0644,
+ }
+ Package['apache']{
+ name => 'apache2-mpm-itk',
+ }
+}
diff --git a/puppet/modules/apache/manifests/debian/module.pp b/puppet/modules/apache/manifests/debian/module.pp
new file mode 100644
index 00000000..ed255155
--- /dev/null
+++ b/puppet/modules/apache/manifests/debian/module.pp
@@ -0,0 +1,48 @@
+# install/remove apache module on debian/ubuntu systems
+define apache::debian::module(
+ $ensure = present,
+ $package_name = 'absent',
+ $conf_source = '',
+ $conf_content = '',
+){
+ $modules_dir = "${apache::debian::config_dir}/mods"
+
+ if ($package_name != 'absent') {
+ package { $package_name:
+ ensure => $ensure,
+ notify => Service['apache'],
+ require => [ File['modules_dir'], Package['apache'] ],
+ }
+ $required_packages = [ 'apache', $package_name ]
+ }
+ else {
+ $required_packages = [ 'apache' ]
+ }
+
+ file {
+ "${modules_dir}-enabled/${name}.load":
+ ensure => "../mods-available/${name}.load",
+ notify => Service['apache'],
+ require => [ File['modules_dir'], Package[$required_packages] ];
+ "${modules_dir}-enabled/${name}.conf":
+ ensure => "../mods-available/${name}.conf",
+ notify => Service['apache'],
+ require => [ File['modules_dir'], Package[$required_packages] ];
+ "${modules_dir}-available/${name}.conf":
+ ensure => file,
+ notify => Service['apache'],
+ require => [ File['modules_dir'], Package[$required_packages] ];
+ }
+
+ if $conf_content != '' {
+ File["${modules_dir}-available/${name}.conf"] {
+ content => $conf_content,
+ }
+ }
+ elsif $conf_source != '' {
+ File["${modules_dir}-available/${name}.conf"] {
+ source => $conf_source,
+ }
+ }
+
+}
diff --git a/puppet/modules/apache/manifests/defaultdavdbdir.pp b/puppet/modules/apache/manifests/defaultdavdbdir.pp
new file mode 100644
index 00000000..c0e2a81a
--- /dev/null
+++ b/puppet/modules/apache/manifests/defaultdavdbdir.pp
@@ -0,0 +1,17 @@
+class apache::defaultdavdbdir {
+ file {
+ '/var/www/dav_db_dir' :
+ ensure => directory,
+ require => Package['apache'],
+ owner => root,
+ group => 0,
+ mode => 0755 ;
+ }
+ if $::selinux != 'false' {
+ selinux::fcontext {
+ ['/var/www/dav_db_dir/.+(/.*)?'] :
+ setype => 'httpd_var_lib_t',
+ before => File['/var/www/dav_db_dir'] ;
+ }
+ }
+}
diff --git a/puppet/modules/apache/manifests/defaultphpdirs.pp b/puppet/modules/apache/manifests/defaultphpdirs.pp
new file mode 100644
index 00000000..595744bb
--- /dev/null
+++ b/puppet/modules/apache/manifests/defaultphpdirs.pp
@@ -0,0 +1,31 @@
+# setup some directories for php
+class apache::defaultphpdirs {
+ file{
+ '/var/www/upload_tmp_dir':
+ ensure => directory,
+ require => Package['apache'],
+ owner => root,
+ group => 0,
+ mode => '0755';
+ '/var/www/session.save_path':
+ ensure => directory,
+ require => Package['apache'],
+ owner => root,
+ group => 0,
+ mode => '0755';
+ }
+
+ if str2bool($::selinux) {
+ $seltype_rw = $::operatingsystemmajrelease ? {
+ 5 => 'httpd_sys_script_rw_t',
+ default => 'httpd_sys_rw_content_t'
+ }
+ selinux::fcontext{
+ [ '/var/www/upload_tmp_dir/.+(/.*)?',
+ '/var/www/session.save_path/.+(/.*)?' ]:
+ require => Package['apache'],
+ setype => $seltype_rw,
+ before => File['/var/www/upload_tmp_dir','/var/www/session.save_path'];
+ }
+ }
+}
diff --git a/puppet/modules/apache/manifests/file.pp b/puppet/modules/apache/manifests/file.pp
new file mode 100644
index 00000000..b0a60ecb
--- /dev/null
+++ b/puppet/modules/apache/manifests/file.pp
@@ -0,0 +1,15 @@
+define apache::file(
+ $owner = root,
+ $group = 0,
+ $mode = 0640
+) {
+ file{$name:
+# as long as there are significant memory problems using
+# recurse we avoid it
+# recurse => true,
+ backup => false,
+ checksum => undef,
+ owner => $owner, group => $group, mode => $mode;
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/file/readonly.pp b/puppet/modules/apache/manifests/file/readonly.pp
new file mode 100644
index 00000000..6308d889
--- /dev/null
+++ b/puppet/modules/apache/manifests/file/readonly.pp
@@ -0,0 +1,12 @@
+define apache::file::readonly(
+ $owner = root,
+ $group = 0,
+ $mode = 0640
+) {
+ apache::file{$name:
+ owner => $owner,
+ group => $group,
+ mode => $mode,
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/file/rw.pp b/puppet/modules/apache/manifests/file/rw.pp
new file mode 100644
index 00000000..0f258bf3
--- /dev/null
+++ b/puppet/modules/apache/manifests/file/rw.pp
@@ -0,0 +1,13 @@
+# a file that is writable by apache
+define apache::file::rw(
+ $owner = root,
+ $group = 0,
+ $mode = '0660',
+) {
+ apache::file{$name:
+ owner => $owner,
+ group => $group,
+ mode => $mode,
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/gentoo.pp b/puppet/modules/apache/manifests/gentoo.pp
new file mode 100644
index 00000000..3a13977f
--- /dev/null
+++ b/puppet/modules/apache/manifests/gentoo.pp
@@ -0,0 +1,39 @@
+### gentoo
+class apache::gentoo inherits apache::package {
+ $config_dir = '/etc/apache2'
+
+ # needs module gentoo
+ gentoo::etcconfd {
+ 'apache2':
+ require => Package['apache'],
+ notify => Service['apache'],
+ }
+ Package['apache']{
+ category => 'www-servers',
+ }
+ File[vhosts_dir]{
+ path => "${config_dir}/vhosts.d",
+ }
+ File[modules_dir]{
+ path => "${config_dir}/modules.d",
+ }
+
+ apache::gentoo::module{
+ '00_default_settings':;
+ '00_error_documents':;
+ }
+ apache::config::file { 'default_vhost.include':
+ source => 'apache/vhosts.d/default_vhost.include',
+ destination => "${config_dir}/vhosts.d/default_vhost.include",
+ }
+
+ # set the default for the ServerName
+ file{"${config_dir}/modules.d/00_default_settings_ServerName.conf":
+ content => "ServerName ${::fqdn}\n",
+ require => Package[apache],
+ owner => root,
+ group => 0,
+ mode => '0644';
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/gentoo/module.pp b/puppet/modules/apache/manifests/gentoo/module.pp
new file mode 100644
index 00000000..1e9d03a6
--- /dev/null
+++ b/puppet/modules/apache/manifests/gentoo/module.pp
@@ -0,0 +1,30 @@
+define apache::gentoo::module(
+ $ensure = present,
+ $source = '',
+ $destination = ''
+){
+ $modules_dir = "${apache::gentoo::config_dir}/modules.d"
+ $real_destination = $destination ? {
+ '' => "${modules_dir}/${name}.conf",
+ default => $destination,
+ }
+ $real_source = $source ? {
+ '' => [
+ "puppet:///modules/site_apache/modules.d/${::fqdn}/${name}.conf",
+ "puppet:///modules/site_apache/modules.d/${apache::cluster_node}/${name}.conf",
+ "puppet:///modules/site_apache/modules.d/${name}.conf",
+ "puppet:///modules/apache/modules.d/${::operatingsystem}/${name}.conf",
+ "puppet:///modules/apache/modules.d/${name}.conf"
+ ],
+ default => "puppet:///$source",
+ }
+ file{"modules_${name}.conf":
+ ensure => $ensure,
+ path => $real_destination,
+ source => $real_source,
+ require => [ File[modules_dir], Package[apache] ],
+ notify => Service[apache],
+ owner => root, group => 0, mode => 0644;
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/htpasswd_user.pp b/puppet/modules/apache/manifests/htpasswd_user.pp
new file mode 100644
index 00000000..82fbce45
--- /dev/null
+++ b/puppet/modules/apache/manifests/htpasswd_user.pp
@@ -0,0 +1,34 @@
+# ToDo: This should be rewritten as native type
+define apache::htpasswd_user(
+ $password,
+ $password_iscrypted = false,
+ $ensure = 'present',
+ $site = 'absent',
+ $username = 'absent',
+ $path = 'absent'
+){
+ case $username {
+ 'absent': { $real_username = $name }
+ default: { $real_username = $username }
+ }
+ case $site {
+ 'absent': { $real_site = $name }
+ default: { $real_site = $site }
+ }
+ if $password_iscrypted {
+ $real_password = $password
+ } else {
+ $real_password = htpasswd_sha1($password)
+ }
+
+ case $path {
+ 'absent': { $real_path = "/var/www/htpasswds/${real_site}" }
+ default: { $real_path = $path }
+ }
+
+ file_line{"htpasswd_for_${real_site}":
+ ensure => $ensure,
+ path => $real_path,
+ line => "${username}:${real_password}",
+ }
+}
diff --git a/puppet/modules/apache/manifests/include/joomla.pp b/puppet/modules/apache/manifests/include/joomla.pp
new file mode 100644
index 00000000..5adae30a
--- /dev/null
+++ b/puppet/modules/apache/manifests/include/joomla.pp
@@ -0,0 +1,3 @@
+class apache::include::joomla {
+ apache::config::include{'joomla.inc': }
+}
diff --git a/puppet/modules/apache/manifests/include/mod_fcgid.pp b/puppet/modules/apache/manifests/include/mod_fcgid.pp
new file mode 100644
index 00000000..b3c1cdc2
--- /dev/null
+++ b/puppet/modules/apache/manifests/include/mod_fcgid.pp
@@ -0,0 +1,7 @@
+class apache::include::mod_fcgid {
+ apache::config::global{'mod_fcgid.conf':
+ content => "<IfModule mod_fcgid.c>
+ FcgidFixPathinfo 1
+</IfModule>\n"
+ }
+}
diff --git a/puppet/modules/apache/manifests/include/silverstripe.pp b/puppet/modules/apache/manifests/include/silverstripe.pp
new file mode 100644
index 00000000..fd2484b7
--- /dev/null
+++ b/puppet/modules/apache/manifests/include/silverstripe.pp
@@ -0,0 +1,3 @@
+class apache::include::silverstripe {
+ apache::config::include{'silverstripe.inc': }
+}
diff --git a/puppet/modules/apache/manifests/includes.pp b/puppet/modules/apache/manifests/includes.pp
new file mode 100644
index 00000000..02502f82
--- /dev/null
+++ b/puppet/modules/apache/manifests/includes.pp
@@ -0,0 +1,5 @@
+# manifests/includes.pp
+
+class apache::includes {
+ apache::config::global{'do_includes.conf':}
+}
diff --git a/puppet/modules/apache/manifests/init.pp b/puppet/modules/apache/manifests/init.pp
new file mode 100644
index 00000000..ad1478a1
--- /dev/null
+++ b/puppet/modules/apache/manifests/init.pp
@@ -0,0 +1,44 @@
+#
+# apache module
+#
+# Copyright 2008, admin(at)immerda.ch
+# Copyright 2008, Puzzle ITC GmbH
+# Marcel Haerry haerry+puppet(at)puzzle.ch
+# Simon Josi josi+puppet(at)puzzle.ch
+#
+# This program is free software; you can redistribute
+# it and/or modify it under the terms of the GNU
+# General Public License version 3 as published by
+# the Free Software Foundation.
+#
+
+# manage a simple apache
+class apache(
+ $cluster_node = '',
+ $manage_shorewall = false,
+ $manage_munin = false,
+ $no_default_site = false,
+ $ssl = false,
+ $default_ssl_certificate_file = absent,
+ $default_ssl_certificate_key_file = absent,
+ $default_ssl_certificate_chain_file = absent,
+ $ssl_cipher_suite = 'HIGH:MEDIUM:!aNULL:!MD5'
+) {
+ case $::operatingsystem {
+ centos: { include apache::centos }
+ gentoo: { include apache::gentoo }
+ debian,ubuntu: { include apache::debian }
+ openbsd: { include apache::openbsd }
+ default: { include apache::base }
+ }
+ if $apache::manage_munin {
+ include apache::status
+ }
+ if $apache::manage_shorewall {
+ include shorewall::rules::http
+ }
+ if $ssl {
+ include apache::ssl
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/itk.pp b/puppet/modules/apache/manifests/itk.pp
new file mode 100644
index 00000000..5292343d
--- /dev/null
+++ b/puppet/modules/apache/manifests/itk.pp
@@ -0,0 +1,11 @@
+# manifests/itk.pp
+#
+# see: http://mpm-itk.sesse.net/
+
+class apache::itk inherits apache {
+ case $::operatingsystem {
+ centos: { include ::apache::centos::itk }
+ debian: { include ::apache::debian::itk }
+ default: { include ::apache::base::itk }
+ }
+}
diff --git a/puppet/modules/apache/manifests/itk/lock.pp b/puppet/modules/apache/manifests/itk/lock.pp
new file mode 100644
index 00000000..4ad95faf
--- /dev/null
+++ b/puppet/modules/apache/manifests/itk/lock.pp
@@ -0,0 +1,4 @@
+class apache::itk::lock {
+ # This file resource is used to ensure that only one itk mode is used per host
+ file{'/var/www/.itk_mode_lock': ensure => absent }
+}
diff --git a/puppet/modules/apache/manifests/itk_plus.pp b/puppet/modules/apache/manifests/itk_plus.pp
new file mode 100644
index 00000000..7d9f721a
--- /dev/null
+++ b/puppet/modules/apache/manifests/itk_plus.pp
@@ -0,0 +1,10 @@
+# manifests/itk.pp
+#
+# see: http://mpm-itk.sesse.net/
+
+class apache::itk_plus inherits apache::itk {
+ case $::operatingsystem {
+ centos: { include ::apache::centos::itk_plus }
+ default: { fail("itk plus mode is currently only implemented for CentOS") }
+ }
+}
diff --git a/puppet/modules/apache/manifests/itk_plus/lock.pp b/puppet/modules/apache/manifests/itk_plus/lock.pp
new file mode 100644
index 00000000..d540939d
--- /dev/null
+++ b/puppet/modules/apache/manifests/itk_plus/lock.pp
@@ -0,0 +1,4 @@
+class apache::itk_plus::lock {
+ # This file resource is used to ensure that only one itk mode is used per host
+ file{'/var/www/.itk_mode_lock': ensure => absent }
+}
diff --git a/puppet/modules/apache/manifests/logrotate/centos.pp b/puppet/modules/apache/manifests/logrotate/centos.pp
new file mode 100644
index 00000000..4381205d
--- /dev/null
+++ b/puppet/modules/apache/manifests/logrotate/centos.pp
@@ -0,0 +1,10 @@
+# add vhost folders to logrotation
+class apache::logrotate::centos {
+ augeas{'logrotate_httpd':
+ changes => [ 'rm /files/etc/logrotate.d/httpd/rule/file',
+ 'ins file before /files/etc/logrotate.d/httpd/rule/*[1]',
+ 'set /files/etc/logrotate.d/httpd/rule/file[1] /var/log/httpd/*log' ],
+ onlyif => 'get /files/etc/logrotate.d/httpd/rule/file[1] != "/var/log/httpd/*log"',
+ require => Package['apache'],
+ }
+}
diff --git a/puppet/modules/apache/manifests/logrotate/centos/vhosts.pp b/puppet/modules/apache/manifests/logrotate/centos/vhosts.pp
new file mode 100644
index 00000000..b1159a11
--- /dev/null
+++ b/puppet/modules/apache/manifests/logrotate/centos/vhosts.pp
@@ -0,0 +1,11 @@
+# add vhost folders to logrotation
+class apache::logrotate::centos::vhosts inherits apache::logrotate::centos {
+ Augeas['logrotate_httpd']{
+ changes => [ 'rm /files/etc/logrotate.d/httpd/rule/file',
+ 'ins file before /files/etc/logrotate.d/httpd/rule/*[1]',
+ 'ins file before /files/etc/logrotate.d/httpd/rule/*[1]',
+ 'set /files/etc/logrotate.d/httpd/rule/file[1] /var/log/httpd/*log',
+ 'set /files/etc/logrotate.d/httpd/rule/file[2] /var/www/vhosts/*/logs/*log' ],
+ onlyif => 'get /files/etc/logrotate.d/httpd/rule/file[2] != "/var/www/vhosts/*/logs/*log"',
+ }
+}
diff --git a/puppet/modules/apache/manifests/mod_dav_svn.pp b/puppet/modules/apache/manifests/mod_dav_svn.pp
new file mode 100644
index 00000000..bdcc4abd
--- /dev/null
+++ b/puppet/modules/apache/manifests/mod_dav_svn.pp
@@ -0,0 +1,7 @@
+class apache::mod_dav_svn {
+ package{'mod_dav_svn':
+ ensure => installed,
+ require => Package['apache'],
+ notify => Service['apache'],
+ }
+}
diff --git a/puppet/modules/apache/manifests/mod_macro.pp b/puppet/modules/apache/manifests/mod_macro.pp
new file mode 100644
index 00000000..eed59e52
--- /dev/null
+++ b/puppet/modules/apache/manifests/mod_macro.pp
@@ -0,0 +1,7 @@
+class apache::mod_macro {
+ package{'mod_macro':
+ ensure => installed,
+ require => Package['apache'],
+ notify => Service['apache'],
+ }
+}
diff --git a/puppet/modules/apache/manifests/module.pp b/puppet/modules/apache/manifests/module.pp
new file mode 100644
index 00000000..cbcf2d04
--- /dev/null
+++ b/puppet/modules/apache/manifests/module.pp
@@ -0,0 +1,35 @@
+define apache::module (
+ $ensure = present, $source = '',
+ $destination = '', $module = '', $package_name = 'absent',
+ $conf_content = '', $conf_source = '',
+) {
+
+ $real_module = $module ? {
+ '' => $name,
+ default => $module,
+ }
+
+ case $operatingsystem {
+ 'centos': {
+ apache::centos::module { "$real_module":
+ ensure => $ensure, source => $source,
+ destination => $destination
+ }
+ }
+ 'gentoo': {
+ apache::gentoo::module { "$real_module":
+ ensure => $ensure, source => $source,
+ destination => $destination
+ }
+ }
+ 'debian','ubuntu': {
+ apache::debian::module { "$real_module":
+ ensure => $ensure, package_name => $package_name,
+ conf_content => $conf_content, conf_source => $conf_source
+ }
+ }
+ default: {
+ err('Your operating system does not have a module deployment mechanism defined')
+ }
+ }
+}
diff --git a/puppet/modules/apache/manifests/module/alias.pp b/puppet/modules/apache/manifests/module/alias.pp
new file mode 100644
index 00000000..33d26efe
--- /dev/null
+++ b/puppet/modules/apache/manifests/module/alias.pp
@@ -0,0 +1,14 @@
+# install mod_alias
+class apache::module::alias ( $ensure = present )
+{
+
+ apache::module { 'alias': ensure => $ensure }
+
+ # from 2.4, /etc/apache2/mods-enabled/alias.conf contains the "Require"
+ # directive which needs "authz_core" mod enabled
+
+ if ( guess_apache_version() == '2.4') {
+ class { 'authz_core': ensure => $ensure }
+ }
+
+}
diff --git a/puppet/modules/apache/manifests/module/auth_basic.pp b/puppet/modules/apache/manifests/module/auth_basic.pp
new file mode 100644
index 00000000..4335a09c
--- /dev/null
+++ b/puppet/modules/apache/manifests/module/auth_basic.pp
@@ -0,0 +1,6 @@
+# enable/disable auth_basic module
+class apache::module::auth_basic ( $ensure = present )
+{
+
+ apache::module { 'auth_basic': ensure => $ensure }
+}
diff --git a/puppet/modules/apache/manifests/module/authn_core.pp b/puppet/modules/apache/manifests/module/authn_core.pp
new file mode 100644
index 00000000..46baace0
--- /dev/null
+++ b/puppet/modules/apache/manifests/module/authn_core.pp
@@ -0,0 +1,6 @@
+# enable/disable authn_core module
+class apache::module::authn_core ( $ensure = present )
+{
+
+ apache::module { 'authn_core': ensure => $ensure }
+}
diff --git a/puppet/modules/apache/manifests/module/authn_file.pp b/puppet/modules/apache/manifests/module/authn_file.pp
new file mode 100644
index 00000000..7c346d9b
--- /dev/null
+++ b/puppet/modules/apache/manifests/module/authn_file.pp
@@ -0,0 +1,6 @@
+# enable/disable authn_file module
+class apache::module::authn_file ( $ensure = present )
+{
+
+ apache::module { 'authn_file': ensure => $ensure }
+}
diff --git a/puppet/modules/apache/manifests/module/authz_core.pp b/puppet/modules/apache/manifests/module/authz_core.pp
new file mode 100644
index 00000000..03b0617c
--- /dev/null
+++ b/puppet/modules/apache/manifests/module/authz_core.pp
@@ -0,0 +1,7 @@
+# install mod_authz_core (needed i.e. by the alias mod config)
+class apache::module::authz_core ( $ensure = present )
+{
+
+ apache::module { 'authz_core': ensure => $ensure }
+
+}
diff --git a/puppet/modules/apache/manifests/module/authz_host.pp b/puppet/modules/apache/manifests/module/authz_host.pp
new file mode 100644
index 00000000..46c3a812
--- /dev/null
+++ b/puppet/modules/apache/manifests/module/authz_host.pp
@@ -0,0 +1,6 @@
+# enable/disable authz_host module
+class apache::module::authz_host ( $ensure = present )
+{
+
+ apache::module { 'authz_host': ensure => $ensure }
+}
diff --git a/puppet/modules/apache/manifests/module/authz_user.pp b/puppet/modules/apache/manifests/module/authz_user.pp
new file mode 100644
index 00000000..84775727
--- /dev/null
+++ b/puppet/modules/apache/manifests/module/authz_user.pp
@@ -0,0 +1,6 @@
+# enable/disable authz_user module
+class apache::module::authz_user ( $ensure = present )
+{
+
+ apache::module { 'authz_user': ensure => $ensure }
+}
diff --git a/puppet/modules/apache/manifests/module/cgi.pp b/puppet/modules/apache/manifests/module/cgi.pp
new file mode 100644
index 00000000..ce212e97
--- /dev/null
+++ b/puppet/modules/apache/manifests/module/cgi.pp
@@ -0,0 +1,6 @@
+# enable/disable cgi module
+class apache::module::cgi ( $ensure = present )
+{
+
+ apache::module { 'cgi': ensure => $ensure }
+}
diff --git a/puppet/modules/apache/manifests/module/dir.pp b/puppet/modules/apache/manifests/module/dir.pp
new file mode 100644
index 00000000..da2dc1ee
--- /dev/null
+++ b/puppet/modules/apache/manifests/module/dir.pp
@@ -0,0 +1,6 @@
+# enable/disable dir module
+class apache::module::dir ( $ensure = present )
+{
+
+ apache::module { 'dir': ensure => $ensure }
+}
diff --git a/puppet/modules/apache/manifests/module/env.pp b/puppet/modules/apache/manifests/module/env.pp
new file mode 100644
index 00000000..f358e363
--- /dev/null
+++ b/puppet/modules/apache/manifests/module/env.pp
@@ -0,0 +1,7 @@
+# install mod_env, needed by api.conf
+class apache::module::env ( $ensure = present )
+{
+
+ apache::module { 'env': ensure => $ensure }
+
+}
diff --git a/puppet/modules/apache/manifests/module/expires.pp b/puppet/modules/apache/manifests/module/expires.pp
new file mode 100644
index 00000000..c56f416b
--- /dev/null
+++ b/puppet/modules/apache/manifests/module/expires.pp
@@ -0,0 +1,5 @@
+# enable/disable expires module
+class apache::module::expires ( $ensure = present )
+{
+ apache::module { 'expires': ensure => $ensure }
+}
diff --git a/puppet/modules/apache/manifests/module/headers.pp b/puppet/modules/apache/manifests/module/headers.pp
new file mode 100644
index 00000000..d1d587b0
--- /dev/null
+++ b/puppet/modules/apache/manifests/module/headers.pp
@@ -0,0 +1,6 @@
+# enable/disable headers module
+class apache::module::headers ( $ensure = present )
+{
+
+ apache::module { 'headers': ensure => $ensure }
+}
diff --git a/puppet/modules/apache/manifests/module/mime.pp b/puppet/modules/apache/manifests/module/mime.pp
new file mode 100644
index 00000000..5d691d30
--- /dev/null
+++ b/puppet/modules/apache/manifests/module/mime.pp
@@ -0,0 +1,6 @@
+# enable/disable mime module
+class apache::module::mime ( $ensure = present )
+{
+
+ apache::module { 'mime': ensure => $ensure }
+}
diff --git a/puppet/modules/apache/manifests/module/mpm_event.pp b/puppet/modules/apache/manifests/module/mpm_event.pp
new file mode 100644
index 00000000..a824cb37
--- /dev/null
+++ b/puppet/modules/apache/manifests/module/mpm_event.pp
@@ -0,0 +1,7 @@
+# install mod_mpm_event (needed for jessie hosts)
+class apache::module::mpm_event ( $ensure = present )
+{
+
+ apache::module { 'mpm_event': ensure => $ensure }
+
+}
diff --git a/puppet/modules/apache/manifests/module/mpm_prefork.pp b/puppet/modules/apache/manifests/module/mpm_prefork.pp
new file mode 100644
index 00000000..7c08da7f
--- /dev/null
+++ b/puppet/modules/apache/manifests/module/mpm_prefork.pp
@@ -0,0 +1,6 @@
+# enable/disable mpm_prefork module
+class apache::module::mpm_prefork ( $ensure = present )
+{
+
+ apache::module { 'mpm_prefork': ensure => $ensure }
+}
diff --git a/puppet/modules/apache/manifests/module/negotiation.pp b/puppet/modules/apache/manifests/module/negotiation.pp
new file mode 100644
index 00000000..15334fb9
--- /dev/null
+++ b/puppet/modules/apache/manifests/module/negotiation.pp
@@ -0,0 +1,6 @@
+# enable/disable negotiation module
+class apache::module::negotiation ( $ensure = present )
+{
+
+ apache::module { 'negotiation': ensure => $ensure }
+}
diff --git a/puppet/modules/apache/manifests/module/php5.pp b/puppet/modules/apache/manifests/module/php5.pp
new file mode 100644
index 00000000..ffb571fe
--- /dev/null
+++ b/puppet/modules/apache/manifests/module/php5.pp
@@ -0,0 +1,6 @@
+# enable/disable php5 module
+class apache::module::php5 ( $ensure = present )
+{
+
+ apache::module { 'php5': ensure => $ensure }
+}
diff --git a/puppet/modules/apache/manifests/module/removeip.pp b/puppet/modules/apache/manifests/module/removeip.pp
new file mode 100644
index 00000000..11088fc1
--- /dev/null
+++ b/puppet/modules/apache/manifests/module/removeip.pp
@@ -0,0 +1,6 @@
+# enable/disable removeip module
+class apache::module::removeip ( $ensure = present )
+{
+ package { 'libapache2-mod-removeip': ensure => $ensure }
+ apache::module { 'removeip': ensure => $ensure }
+}
diff --git a/puppet/modules/apache/manifests/module/rewrite.pp b/puppet/modules/apache/manifests/module/rewrite.pp
new file mode 100644
index 00000000..24ef899b
--- /dev/null
+++ b/puppet/modules/apache/manifests/module/rewrite.pp
@@ -0,0 +1,6 @@
+# enable/disable rewrite module
+class apache::module::rewrite ( $ensure = present )
+{
+
+ apache::module { 'rewrite': ensure => $ensure }
+}
diff --git a/puppet/modules/apache/manifests/module/socache_shmcb.pp b/puppet/modules/apache/manifests/module/socache_shmcb.pp
new file mode 100644
index 00000000..4c53adde
--- /dev/null
+++ b/puppet/modules/apache/manifests/module/socache_shmcb.pp
@@ -0,0 +1,6 @@
+# enable/disable socache_shmcb module
+class apache::module::socache_shmcb ( $ensure = present )
+{
+
+ apache::module { 'socache_shmcb': ensure => $ensure }
+}
diff --git a/puppet/modules/apache/manifests/module/status.pp b/puppet/modules/apache/manifests/module/status.pp
new file mode 100644
index 00000000..cfc437ca
--- /dev/null
+++ b/puppet/modules/apache/manifests/module/status.pp
@@ -0,0 +1,6 @@
+# enable/disable status module
+class apache::module::status ( $ensure = present )
+{
+
+ apache::module { 'status': ensure => $present }
+}
diff --git a/puppet/modules/apache/manifests/mozilla_autoconfig.pp b/puppet/modules/apache/manifests/mozilla_autoconfig.pp
new file mode 100644
index 00000000..f16e5ec7
--- /dev/null
+++ b/puppet/modules/apache/manifests/mozilla_autoconfig.pp
@@ -0,0 +1,37 @@
+# setup autoconfig infos
+#
+# this will create a global autoconfig file, that maps
+# any of your hosted domains on this host to a certain
+# provider configuration. Which means, that you get a zero
+# setup autoconfig for any domain that you host the website
+# and the emails for.
+# By default you only need to define the provider, which
+# is usually your main domain. Everything else should be
+# derived from that.
+# You can however still fine tune things from it.
+class apache::mozilla_autoconfig(
+ $provider,
+ $display_name = undef,
+ $shortname = undef,
+ $imap_server = undef,
+ $pop_server = undef,
+ $smtp_server = undef,
+ $documentation_url = undef,
+) {
+ apache::config::global { 'mozilla_autoconfig.conf': }
+
+ file{
+ '/var/www/autoconfig':
+ ensure => directory,
+ require => Package['apache'],
+ owner => root,
+ group => apache,
+ mode => '0640';
+ '/var/www/autoconfig/config.shtml':
+ content => template('apache/webfiles/autoconfig/config.shtml.erb'),
+ owner => root,
+ group => apache,
+ mode => '0640',
+ before => Service['apache'],
+ }
+}
diff --git a/puppet/modules/apache/manifests/munin.pp b/puppet/modules/apache/manifests/munin.pp
new file mode 100644
index 00000000..46af1723
--- /dev/null
+++ b/puppet/modules/apache/manifests/munin.pp
@@ -0,0 +1,12 @@
+# manage apache monitoring things
+class apache::munin {
+ if $::osfamily == 'Debian' {
+ include perl::extensions::libwww
+ }
+
+ munin::plugin{ [ 'apache_accesses', 'apache_processes', 'apache_volume' ]: }
+ munin::plugin::deploy { 'apache_activity':
+ source => 'apache/munin/apache_activity',
+ seltype => 'munin_services_plugin_exec_t',
+ }
+}
diff --git a/puppet/modules/apache/manifests/noiplog.pp b/puppet/modules/apache/manifests/noiplog.pp
new file mode 100644
index 00000000..355d7e6a
--- /dev/null
+++ b/puppet/modules/apache/manifests/noiplog.pp
@@ -0,0 +1,5 @@
+class apache::noiplog {
+ apache::config::global{ 'noip_log.conf':
+ content => 'LogFormat "127.0.0.1 - - %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %T %V" noip';
+ }
+}
diff --git a/puppet/modules/apache/manifests/openbsd.pp b/puppet/modules/apache/manifests/openbsd.pp
new file mode 100644
index 00000000..96a216ad
--- /dev/null
+++ b/puppet/modules/apache/manifests/openbsd.pp
@@ -0,0 +1,75 @@
+### openbsd
+class apache::openbsd inherits apache::base {
+ $config_dir = '/var/www'
+
+ File[vhosts_dir]{
+ path => "${config_dir}/vhosts.d",
+ }
+ File[modules_dir]{
+ path => "${config_dir}/conf/modules",
+ }
+ File[config_dir]{
+ path => "${config_dir}/conf.d",
+ }
+ File[include_dir]{
+ path => "${config_dir}/include.d",
+ }
+ File['htpasswd_dir']{
+ group => www,
+ }
+ File[web_dir]{
+ group => daemon,
+ }
+ file_line{'enable_apache_on_boot':
+ path => '/etc/rc.conf.local',
+ line => 'httpd flags=""',
+ }
+ file{'apache_main_config':
+ path => "${config_dir}/conf/httpd.conf",
+ source => ["puppet:///modules/site_apache/config/OpenBSD/${::fqdn}/httpd.conf",
+ "puppet:///modules/site_apache/config/OpenBSD/${apache::cluster_node}/httpd.conf",
+ 'puppet:///modules/site_apache/config/OpenBSD//httpd.conf',
+ 'puppet:///modules/apache/config/OpenBSD/httpd.conf' ],
+ notify => Service['apache'],
+ owner => root,
+ group => 0,
+ mode => '0644';
+ }
+ File[default_apache_index] {
+ path => '/var/www/htdocs/default/www/index.html',
+ }
+ file{'/opt/bin/restart_apache.sh':
+ source => 'puppet:///modules/apache/scripts/OpenBSD/bin/restart_apache.sh',
+ require => File['/opt/bin'],
+ owner => root,
+ group => 0,
+ mode => '0700';
+ }
+
+ ::apache::vhost::webdir{'default':
+ datadir => false,
+ }
+
+ Service['apache']{
+ restart => '/opt/bin/restart_apache.sh',
+ status => 'apachectl status',
+ start => 'apachectl start',
+ stop => 'apachectl stop',
+ }
+ file{'/opt/bin/apache_logrotate.sh':
+ source => 'puppet:///modules/apache/scripts/OpenBSD/bin/apache_logrotate.sh',
+ require => File['/opt/bin'],
+ owner => root,
+ group => 0,
+ mode => '0700';
+ }
+ cron { 'update_apache_logrotation':
+ command => '/bin/sh /opt/bin/apache_logrotate.sh > /etc/newsyslog_apache.conf',
+ minute => '1',
+ hour => '1',
+ }
+ cron { 'run_apache_logrotation':
+ command => '/usr/bin/newsyslog -f /etc/newsyslog_apache.conf > /dev/null',
+ minute => '10',
+ }
+}
diff --git a/puppet/modules/apache/manifests/package.pp b/puppet/modules/apache/manifests/package.pp
new file mode 100644
index 00000000..3308b371
--- /dev/null
+++ b/puppet/modules/apache/manifests/package.pp
@@ -0,0 +1,32 @@
+# deploy apache as package
+class apache::package inherits apache::base {
+ package { 'apache':
+ name => 'apache',
+ ensure => present,
+ }
+ File['vhosts_dir']{
+ require => Package[apache],
+ }
+ File['config_dir']{
+ require => Package[apache],
+ }
+ Service['apache']{
+ require => Package[apache],
+ }
+ File['default_apache_index']{
+ require => Package[apache],
+ }
+ File['modules_dir']{
+ require => Package[apache],
+ }
+ File['include_dir']{
+ require => Package[apache],
+ }
+ File['web_dir']{
+ require => Package[apache],
+ }
+ File['htpasswd_dir']{
+ require => Package[apache],
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/package/itk.pp b/puppet/modules/apache/manifests/package/itk.pp
new file mode 100644
index 00000000..4ca9960e
--- /dev/null
+++ b/puppet/modules/apache/manifests/package/itk.pp
@@ -0,0 +1,5 @@
+class apache::package::itk inherits apache::package {
+ Package['apache'] {
+ name => 'apache2-itk',
+ }
+}
diff --git a/puppet/modules/apache/manifests/sftponly.pp b/puppet/modules/apache/manifests/sftponly.pp
new file mode 100644
index 00000000..ece726b0
--- /dev/null
+++ b/puppet/modules/apache/manifests/sftponly.pp
@@ -0,0 +1,5 @@
+class apache::sftponly {
+ case $::operatingsystem {
+ centos: { include apache::sftponly::centos }
+ }
+}
diff --git a/puppet/modules/apache/manifests/sftponly/centos.pp b/puppet/modules/apache/manifests/sftponly/centos.pp
new file mode 100644
index 00000000..0f2a43d8
--- /dev/null
+++ b/puppet/modules/apache/manifests/sftponly/centos.pp
@@ -0,0 +1,10 @@
+# manage sftponly group and apache
+# user for access
+class apache::sftponly::centos {
+ require user::groups::sftponly
+ user::groups::manage_user{'apache':
+ group => 'sftponly',
+ require => Package['apache'],
+ notify => Service['apache'],
+ }
+}
diff --git a/puppet/modules/apache/manifests/ssl.pp b/puppet/modules/apache/manifests/ssl.pp
new file mode 100644
index 00000000..bfef7adc
--- /dev/null
+++ b/puppet/modules/apache/manifests/ssl.pp
@@ -0,0 +1,13 @@
+# manifests/ssl.pp
+
+class apache::ssl {
+ case $::operatingsystem {
+ centos: { include apache::ssl::centos }
+ openbsd: { include apache::ssl::openbsd }
+ debian: { include apache::ssl::debian }
+ defaults: { include apache::ssl::base }
+ }
+ if $apache::manage_shorewall {
+ include shorewall::rules::https
+ }
+}
diff --git a/puppet/modules/apache/manifests/ssl/base.pp b/puppet/modules/apache/manifests/ssl/base.pp
new file mode 100644
index 00000000..3f329136
--- /dev/null
+++ b/puppet/modules/apache/manifests/ssl/base.pp
@@ -0,0 +1,15 @@
+# basic defaults for ssl support
+class apache::ssl::base (
+) {
+ apache::config::include {
+ 'ssl_defaults.inc':
+ content => template('apache/include.d/ssl_defaults.inc.erb');
+ }
+
+ if !$apache::no_default_site {
+ apache::vhost::file{
+ '0-default_ssl':
+ content => template('apache/vhosts/0-default_ssl.conf.erb');
+ }
+ }
+}
diff --git a/puppet/modules/apache/manifests/ssl/centos.pp b/puppet/modules/apache/manifests/ssl/centos.pp
new file mode 100644
index 00000000..7bc8c895
--- /dev/null
+++ b/puppet/modules/apache/manifests/ssl/centos.pp
@@ -0,0 +1,12 @@
+class apache::ssl::centos inherits apache::ssl::base {
+ package { 'mod_ssl':
+ name => 'mod_ssl',
+ ensure => present,
+ require => Package[apache],
+ }
+ ::apache::config::global{ 'ssl.conf': }
+
+ apache::config::global{'00-listen-ssl.conf':
+ ensure => absent,
+ }
+}
diff --git a/puppet/modules/apache/manifests/ssl/debian.pp b/puppet/modules/apache/manifests/ssl/debian.pp
new file mode 100644
index 00000000..99dfe36e
--- /dev/null
+++ b/puppet/modules/apache/manifests/ssl/debian.pp
@@ -0,0 +1,4 @@
+class apache::ssl::debian inherits apache::ssl::base {
+ apache::debian::module { 'ssl': ensure => present }
+ apache::config::global { 'ssl.conf': }
+}
diff --git a/puppet/modules/apache/manifests/ssl/itk.pp b/puppet/modules/apache/manifests/ssl/itk.pp
new file mode 100644
index 00000000..5fd3aaf6
--- /dev/null
+++ b/puppet/modules/apache/manifests/ssl/itk.pp
@@ -0,0 +1,8 @@
+# manifests/ssl/itk.pp
+
+class apache::ssl::itk inherits apache::ssl {
+ case $::operatingsystem {
+ centos: { include apache::ssl::itk::centos }
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/ssl/itk/centos.pp b/puppet/modules/apache/manifests/ssl/itk/centos.pp
new file mode 100644
index 00000000..fb6a4a6b
--- /dev/null
+++ b/puppet/modules/apache/manifests/ssl/itk/centos.pp
@@ -0,0 +1,6 @@
+class apache::ssl::itk::centos inherits apache::ssl::centos {
+ Package['mod_ssl']{
+ name => 'mod_ssl-itk',
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/ssl/itk_plus.pp b/puppet/modules/apache/manifests/ssl/itk_plus.pp
new file mode 100644
index 00000000..0c8e6679
--- /dev/null
+++ b/puppet/modules/apache/manifests/ssl/itk_plus.pp
@@ -0,0 +1,6 @@
+class apache::ssl::itk_plus inherits apache::ssl::itk {
+ case $::operatingsystem {
+ centos: { include ::apache::ssl::itk_plus::centos }
+ default: { fail("itk plus mode is currently only implemented for CentOS") }
+ }
+}
diff --git a/puppet/modules/apache/manifests/ssl/itk_plus/centos.pp b/puppet/modules/apache/manifests/ssl/itk_plus/centos.pp
new file mode 100644
index 00000000..00fb4729
--- /dev/null
+++ b/puppet/modules/apache/manifests/ssl/itk_plus/centos.pp
@@ -0,0 +1,11 @@
+class apache::ssl::itk_plus::centos inherits apache::ssl::centos {
+ include apache::ssl::itk::centos
+ Apache::Config::Global['ssl.conf']{
+ source => "modules/apache/itk_plus/conf.d/${::operatingsystem}/ssl.conf",
+ }
+
+ Apache::Config::Global['00-listen-ssl.conf']{
+ ensure => 'present',
+ content => template("apache/itk_plus/${::operatingsystem}/00-listen-ssl.conf.erb"),
+ }
+}
diff --git a/puppet/modules/apache/manifests/ssl/openbsd.pp b/puppet/modules/apache/manifests/ssl/openbsd.pp
new file mode 100644
index 00000000..43bc6803
--- /dev/null
+++ b/puppet/modules/apache/manifests/ssl/openbsd.pp
@@ -0,0 +1,18 @@
+class apache::ssl::openbsd inherits apache::openbsd {
+ include apache::ssl::base
+
+ File_line['enable_apache_on_boot']{
+ ensure => 'absent',
+ }
+ file_line{'enable_apachessl_on_boot':
+ path => '/etc/rc.conf.local',
+ line => 'httpd flags="-DSSL"',
+ }
+
+ File['/opt/bin/restart_apache.sh']{
+ source => "puppet:///modules/apache/scripts/OpenBSD/bin/restart_apache_ssl.sh",
+ }
+ Service['apache']{
+ start => 'apachectl startssl',
+ }
+}
diff --git a/puppet/modules/apache/manifests/status.pp b/puppet/modules/apache/manifests/status.pp
new file mode 100644
index 00000000..c5070130
--- /dev/null
+++ b/puppet/modules/apache/manifests/status.pp
@@ -0,0 +1,13 @@
+# enable apache status page
+# manage munin plugins if requested
+class apache::status {
+ case $::operatingsystem {
+ centos: { include apache::status::centos }
+ debian: { include apache::status::debian }
+ defaults: { include apache::status::base }
+ }
+ if $apache::manage_munin {
+ include apache::munin
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/status/base.pp b/puppet/modules/apache/manifests/status/base.pp
new file mode 100644
index 00000000..df6c90b9
--- /dev/null
+++ b/puppet/modules/apache/manifests/status/base.pp
@@ -0,0 +1 @@
+class apache::status::base {}
diff --git a/puppet/modules/apache/manifests/status/centos.pp b/puppet/modules/apache/manifests/status/centos.pp
new file mode 100644
index 00000000..d893707d
--- /dev/null
+++ b/puppet/modules/apache/manifests/status/centos.pp
@@ -0,0 +1,5 @@
+### centos
+class apache::status::centos {
+ ::apache::config::global{ 'status.conf': }
+}
+
diff --git a/puppet/modules/apache/manifests/status/debian.pp b/puppet/modules/apache/manifests/status/debian.pp
new file mode 100644
index 00000000..222b85c7
--- /dev/null
+++ b/puppet/modules/apache/manifests/status/debian.pp
@@ -0,0 +1,4 @@
+# enable status module on debian
+class apache::status::debian {
+ ::apache::debian::module { 'status': }
+}
diff --git a/puppet/modules/apache/manifests/vhost.pp b/puppet/modules/apache/manifests/vhost.pp
new file mode 100644
index 00000000..da1ce901
--- /dev/null
+++ b/puppet/modules/apache/manifests/vhost.pp
@@ -0,0 +1,127 @@
+# this is a wrapper for apache::vhost::file and avhost::template below
+#
+# vhost_mode: which option is choosed to deploy the vhost
+# - template: generate it from a template (default)
+# - file: deploy a vhost file (apache::vhost::file will be called directly)
+#
+# logmode:
+# - default: Do normal logging to CustomLog and ErrorLog
+# - nologs: Send every logging to /dev/null
+# - anonym: Don't log ips for CustomLog, send ErrorLog to /dev/null
+# - semianonym: Don't log ips for CustomLog, log normal ErrorLog
+#
+# run_mode: controls in which mode the vhost should be run, there are different setups
+# possible:
+# - normal: (*default*) run vhost with the current active worker (default: prefork) don't
+# setup anything special
+# - itk: run vhost with the mpm_itk module (Incompatibility: cannot be used in combination
+# with 'proxy-itk' & 'static-itk' mode)
+# - proxy-itk: run vhost with a dual prefork/itk setup, where prefork just proxies all the
+# requests for the itk setup, that listens only on the loobpack device.
+# (Incompatibility: cannot be used in combination with the itk setup.)
+# - static-itk: run vhost with a dual prefork/itk setup, where prefork serves all the static
+# content and proxies the dynamic calls to the itk setup, that listens only on
+# the loobpack device (Incompatibility: cannot be used in combination with
+# 'itk' mode)
+#
+# mod_security: Whether we use mod_security or not (will include mod_security module)
+# - false: (*default*) don't activate mod_security
+# - true: activate mod_security
+#
+define apache::vhost(
+ $ensure = present,
+ $configuration = {},
+ $path = 'absent',
+ $path_is_webdir = false,
+ $logpath = 'absent',
+ $logmode = 'default',
+ $logprefix = '',
+ $vhost_mode = 'template',
+ $template_partial = 'apache/vhosts/static/partial.erb',
+ $vhost_source = 'absent',
+ $vhost_destination = 'absent',
+ $content = 'absent',
+ $domain = 'absent',
+ $domainalias = 'absent',
+ $server_admin = 'absent',
+ $allow_override = 'None',
+ $php_settings = {},
+ $php_options = {},
+ $cgi_binpath = 'absent',
+ $default_charset = 'absent',
+ $do_includes = false,
+ $options = 'absent',
+ $additional_options = 'absent',
+ $run_mode = 'normal',
+ $run_uid = 'absent',
+ $run_gid = 'absent',
+ $ssl_mode = false,
+ $htpasswd_file = 'absent',
+ $htpasswd_path = 'absent',
+ $mod_security = true,
+ $mod_security_relevantonly = true,
+ $mod_security_rules_to_disable = [],
+ $mod_security_additional_options = 'absent',
+ $use_mod_macro = false,
+ $ldap_auth = false,
+ $ldap_user = 'any',
+ $passing_extension = 'absent',
+ $gempath = 'absent'
+) {
+ # file or template mode?
+ case $vhost_mode {
+ 'file': {
+ apache::vhost::file{$name:
+ ensure => $ensure,
+ configuration => $configuration,
+ vhost_source => $vhost_source,
+ vhost_destination => $vhost_destination,
+ do_includes => $do_includes,
+ run_mode => $run_mode,
+ mod_security => $mod_security,
+ htpasswd_file => $htpasswd_file,
+ htpasswd_path => $htpasswd_path,
+ use_mod_macro => $use_mod_macro,
+ }
+ }
+ 'template': {
+ apache::vhost::template{$name:
+ ensure => $ensure,
+ configuration => $configuration,
+ path => $path,
+ path_is_webdir => $path_is_webdir,
+ logpath => $logpath,
+ logmode => $logmode,
+ logprefix => $logprefix,
+ domain => $domain,
+ domainalias => $domainalias,
+ server_admin => $server_admin,
+ cgi_binpath => $cgi_binpath,
+ allow_override => $allow_override,
+ do_includes => $do_includes,
+ options => $options,
+ additional_options => $additional_options,
+ default_charset => $default_charset,
+ php_settings => $php_settings,
+ php_options => $php_options,
+ run_mode => $run_mode,
+ run_uid => $run_uid,
+ run_gid => $run_gid,
+ template_partial => $template_partial,
+ ssl_mode => $ssl_mode,
+ htpasswd_file => $htpasswd_file,
+ htpasswd_path => $htpasswd_path,
+ ldap_auth => $ldap_auth,
+ ldap_user => $ldap_user,
+ mod_security => $mod_security,
+ mod_security_relevantonly => $mod_security_relevantonly,
+ mod_security_rules_to_disable => $mod_security_rules_to_disable,
+ mod_security_additional_options => $mod_security_additional_options,
+ use_mod_macro => $use_mod_macro,
+ passing_extension => $passing_extension,
+ gempath => $gempath,
+ }
+ }
+ default: { fail("No such vhost_mode: ${vhost_mode} defined for ${name}.") }
+ }
+}
diff --git a/puppet/modules/apache/manifests/vhost/davdbdir.pp b/puppet/modules/apache/manifests/vhost/davdbdir.pp
new file mode 100644
index 00000000..459167c9
--- /dev/null
+++ b/puppet/modules/apache/manifests/vhost/davdbdir.pp
@@ -0,0 +1,40 @@
+define apache::vhost::davdbdir(
+ $ensure = present,
+ $dav_db_dir = 'absent',
+ $documentroot_owner = apache,
+ $documentroot_group = 0,
+ $documentroot_mode = 0750,
+ $run_mode = 'normal',
+ $run_uid = 'absent'
+){
+ # php db dir
+ case $dav_db_dir {
+ 'absent': {
+ include apache::defaultdavdbdir
+ $real_dav_db_dir = "/var/www/dav_db_dir/${name}"
+ }
+ default: { $real_dav_db_dir = $dav_db_dir }
+ }
+
+ case $ensure {
+ absent: {
+ file{$real_dav_db_dir:
+ ensure => absent,
+ purge => true,
+ force => true,
+ recurse => true,
+ }
+ }
+ default: {
+ file{$real_dav_db_dir:
+ ensure => directory,
+ owner => $run_mode ? {
+ 'itk' => $run_uid,
+ default => $documentroot_owner
+ },
+ group => $documentroot_group, mode => $documentroot_mode;
+ }
+ }
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/vhost/file.pp b/puppet/modules/apache/manifests/vhost/file.pp
new file mode 100644
index 00000000..686cb1a1
--- /dev/null
+++ b/puppet/modules/apache/manifests/vhost/file.pp
@@ -0,0 +1,151 @@
+# htpasswd_file: wether to deploy a passwd for this vhost or not
+# - absent: ignore (default)
+# - nodeploy: htpasswd file isn't deployed by this mechanism
+# - else: try to deploy the file
+#
+# htpasswd_path: where to deploy the passwd file
+# - absent: standardpath (default)
+# - else: path to deploy
+#
+# ssl_mode: wether this vhost supports ssl or not
+# - false: don't enable ssl for this vhost (default)
+# - true: enable ssl for this vhost
+# - force: enable ssl and redirect non-ssl to ssl
+# - only: enable ssl only
+#
+# run_mode: controls in which mode the vhost should be run, there are different setups
+# possible:
+# - normal: (*default*) run vhost with the current active worker (default: prefork) don't
+# setup anything special
+# - itk: run vhost with the mpm_itk module (Incompatibility: cannot be used in combination
+# with 'proxy-itk' & 'static-itk' mode)
+# - proxy-itk: run vhost with a dual prefork/itk setup, where prefork just proxies all the
+# requests for the itk setup, that listens only on the loobpack device.
+# (Incompatibility: cannot be used in combination with the itk setup.)
+# - static-itk: run vhost with a dual prefork/itk setup, where prefork serves all the static
+# content and proxies the dynamic calls to the itk setup, that listens only on
+# the loobpack device (Incompatibility: cannot be used in combination with
+# 'itk' mode)
+# logmode:
+# - default: Do normal logging to CustomLog and ErrorLog
+# - nologs: Send every logging to /dev/null
+# - anonym: Don't log ips for CustomLog, send ErrorLog to /dev/null
+# - semianonym: Don't log ips for CustomLog, log normal ErrorLog
+#
+#
+# mod_security: Whether we use mod_security or not
+# (will include mod_security module)
+# - false: (*default*) don't activate mod_security
+# - true: activate mod_security
+#
+define apache::vhost::file(
+ $ensure = present,
+ $configuration = {},
+ $vhost_source = 'absent',
+ $vhost_destination = 'absent',
+ $content = 'absent',
+ $do_includes = false,
+ $run_mode = 'normal',
+ $logmode = 'default',
+ $ssl_mode = false,
+ $mod_security = false,
+ $htpasswd_file = 'absent',
+ $htpasswd_path = 'absent',
+ $use_mod_macro = false
+){
+ $vhosts_dir = $::operatingsystem ? {
+ centos => "${apache::centos::config_dir}/vhosts.d",
+ gentoo => "${apache::gentoo::config_dir}/vhosts.d",
+ debian => "${apache::debian::config_dir}/sites-enabled",
+ ubuntu => "${apache::ubuntu::config_dir}/sites-enabled",
+ openbsd => "${apache::openbsd::config_dir}/vhosts.d",
+ default => '/etc/apache2/vhosts.d',
+ }
+ $real_vhost_destination = $vhost_destination ? {
+ 'absent' => "${vhosts_dir}/${name}.conf",
+ default => $vhost_destination,
+ }
+ file{"${name}.conf":
+ ensure => $ensure,
+ path => $real_vhost_destination,
+ require => File[vhosts_dir],
+ notify => Service[apache],
+ owner => root,
+ group => 0,
+ mode => '0644';
+ }
+ if $ensure != 'absent' {
+ if $do_includes {
+ include ::apache::includes
+ }
+ if $use_mod_macro {
+ include ::apache::mod_macro
+ }
+ case $logmode {
+ 'semianonym','anonym': { include apache::noiplog }
+ }
+ case $run_mode {
+ 'itk': {
+ include ::apache::itk::lock
+ if $mod_security { include mod_security::itk }
+ }
+ 'proxy-itk','static-itk': {
+ include ::apache::itk_plus::lock
+ if $mod_security { include mod_security::itk_plus }
+ }
+ default: {
+ if $mod_security { include mod_security }
+ }
+ }
+
+ case $content {
+ 'absent': {
+ $real_vhost_source = $vhost_source ? {
+ 'absent' => [
+ "puppet:///modules/site_apache/vhosts.d/${::fqdn}/${name}.conf",
+ "puppet:///modules/site_apache/vhosts.d/${apache::cluster_node}/${name}.conf",
+ "puppet:///modules/site_apache/vhosts.d/${::operatingsystem}.${::operatingsystemmajrelease}/${name}.conf",
+ "puppet:///modules/site_apache/vhosts.d/${::operatingsystem}/${name}.conf",
+ "puppet:///modules/site_apache/vhosts.d/${name}.conf",
+ "puppet:///modules/apache/vhosts.d/${::operatingsystem}.${::operatingsystemmajrelease}/${name}.conf",
+ "puppet:///modules/apache/vhosts.d/${::operatingsystem}/${name}.conf",
+ "puppet:///modules/apache/vhosts.d/${name}.conf"
+ ],
+ default => "puppet:///${vhost_source}",
+ }
+ File["${name}.conf"]{
+ source => $real_vhost_source,
+ }
+ }
+ default: {
+ File["${name}.conf"]{
+ content => $content,
+ }
+ }
+ }
+ }
+ case $htpasswd_file {
+ 'absent','nodeploy': { info("don't deploy a htpasswd file for ${name}") }
+ default: {
+ if $htpasswd_path == 'absent' {
+ $real_htpasswd_path = "/var/www/htpasswds/${name}"
+ } else {
+ $real_htpasswd_path = $htpasswd_path
+ }
+ file{$real_htpasswd_path:
+ ensure => $ensure,
+ }
+ if ($ensure!='absent') {
+ File[$real_htpasswd_path]{
+ source => [ "puppet:///modules/site_apache/htpasswds/${::fqdn}/${name}",
+ "puppet:///modules/site_apache/htpasswds/${apache::cluster_node}/${name}",
+ "puppet:///modules/site_apache/htpasswds/${name}" ],
+ owner => root,
+ group => 0,
+ mode => '0644',
+ }
+ }
+ }
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/vhost/file/documentrootdir.pp b/puppet/modules/apache/manifests/vhost/file/documentrootdir.pp
new file mode 100644
index 00000000..425406ad
--- /dev/null
+++ b/puppet/modules/apache/manifests/vhost/file/documentrootdir.pp
@@ -0,0 +1,24 @@
+define apache::vhost::file::documentrootdir(
+ $ensure = directory,
+ $documentroot,
+ $filename,
+ $thedomain,
+ $owner = 'root',
+ $group = '0',
+ $mode = 440
+){
+ file{"$documentroot/$filename":
+ require => Apache::Vhost::Webdir["$thedomain"],
+ owner => $owner, group => $group, mode => $mode;
+ }
+ if $ensure != 'absent' {
+ File["$documentroot/$filename"]{
+ ensure => directory,
+ }
+ } else {
+ File["$documentroot/$filename"]{
+ ensure => $ensure,
+ }
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/vhost/file/documentrootfile.pp b/puppet/modules/apache/manifests/vhost/file/documentrootfile.pp
new file mode 100644
index 00000000..c5bc72a1
--- /dev/null
+++ b/puppet/modules/apache/manifests/vhost/file/documentrootfile.pp
@@ -0,0 +1,27 @@
+# place a file in the documentroot
+define apache::vhost::file::documentrootfile(
+ $documentroot,
+ $filename,
+ $thedomain,
+ $owner = 'root',
+ $group = '0',
+ $mode = '0440',
+){
+ file{"${documentroot}/${filename}":
+ source => [ "puppet:///modules/site_apache/vhost_varieties/${::fqdn}/${thedomain}/${filename}",
+ "puppet:///modules/site_apache/vhost_varieties/${apache::cluster_node}/${thedomain}/${filename}",
+ "puppet:///modules/site_apache/vhost_varieties/${::operatingsystem}.${::operatingsystemmajrelease}/${thedomain}/${filename}",
+ "puppet:///modules/site_apache/vhost_varieties/${::operatingsystem}/${thedomain}/${filename}",
+ "puppet:///modules/site_apache/vhost_varieties/${thedomain}/${filename}",
+ "puppet:///modules/apache/vhost_varieties/${thedomain}/${filename}",
+ "puppet:///modules/apache/vhost_varieties/${::operatingsystem}.${::operatingsystemmajrelease}/${thedomain}/${filename}",
+ "puppet:///modules/apache/vhost_varieties/${::operatingsystem}/${thedomain}/${filename}",
+ "puppet:///modules/apache/vhost_varieties/${thedomain}/${filename}",
+ ],
+ require => Apache::Vhost::Webdir[$thedomain],
+ owner => $owner,
+ group => $group,
+ mode => $mode;
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/vhost/gitweb.pp b/puppet/modules/apache/manifests/vhost/gitweb.pp
new file mode 100644
index 00000000..6dd86439
--- /dev/null
+++ b/puppet/modules/apache/manifests/vhost/gitweb.pp
@@ -0,0 +1,59 @@
+# logmode:
+# - default: Do normal logging to CustomLog and ErrorLog
+# - nologs: Send every logging to /dev/null
+# - anonym: Don't log ips for CustomLog, send ErrorLog to /dev/null
+# - semianonym: Don't log ips for CustomLog, log normal ErrorLog
+#
+define apache::vhost::gitweb(
+ $ensure = present,
+ $configuration = {},
+ $domain = 'absent',
+ $logmode = 'default',
+ $domainalias = 'absent',
+ $server_admin = 'absent',
+ $owner = root,
+ $group = apache,
+ $documentroot_owner = apache,
+ $documentroot_group = 0,
+ $documentroot_mode = 0640,
+ $allow_override = 'None',
+ $template_partial = 'apache/vhosts/gitweb/partial.erb',
+ $do_includes = false,
+ $options = 'absent',
+ $additional_options = 'absent',
+ $default_charset = 'absent',
+ $ssl_mode = false,
+ $htpasswd_file = 'absent',
+ $htpasswd_path = 'absent'
+){
+ # create vhost configuration file
+ ::apache::vhost{$name:
+ ensure => $ensure,
+ configuration => $configuration,
+ path => '/var/www/git',
+ path_is_webdir => true,
+ logpath => $::operatingsystem ? {
+ centos => '/var/log/httpd',
+ fedora => '/var/log/httpd',
+ redhat => '/var/log/httpd',
+ openbsd => '/var/www/logs',
+ default => '/var/log/apache2'
+ },
+ logmode => $logmode,
+ template_partial => $template_partial,
+ domain => $domain,
+ domainalias => $domainalias,
+ server_admin => $server_admin,
+ allow_override => $allow_override,
+ do_includes => $do_includes,
+ options => $options,
+ additional_options => $additional_options,
+ default_charset => $default_charset,
+ run_mode => 'normal',
+ ssl_mode => $ssl_mode,
+ htpasswd_file => $htpasswd_file,
+ htpasswd_path => $htpasswd_path,
+ mod_security => false,
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/vhost/modperl.pp b/puppet/modules/apache/manifests/vhost/modperl.pp
new file mode 100644
index 00000000..31e46b6f
--- /dev/null
+++ b/puppet/modules/apache/manifests/vhost/modperl.pp
@@ -0,0 +1,153 @@
+# run_mode: controls in which mode the vhost should be run, there are different setups
+# possible:
+# - normal: (*default*) run vhost with the current active worker (default: prefork) don't
+# setup anything special
+# - itk: run vhost with the mpm_itk module (Incompatibility: cannot be used in combination
+# with 'proxy-itk' & 'static-itk' mode)
+# - proxy-itk: run vhost with a dual prefork/itk setup, where prefork just proxies all the
+# requests for the itk setup, that listens only on the loobpack device.
+# (Incompatibility: cannot be used in combination with the itk setup.)
+# - static-itk: run vhost with a dual prefork/itk setup, where prefork serves all the static
+# content and proxies the dynamic calls to the itk setup, that listens only on
+# the loobpack device (Incompatibility: cannot be used in combination with
+# 'itk' mode)
+#
+# run_uid: the uid the vhost should run as with the itk module
+# run_gid: the gid the vhost should run as with the itk module
+#
+# mod_security: Whether we use mod_security or not (will include mod_security module)
+# - false: don't activate mod_security
+# - true: (*default*) activate mod_security
+#
+# logmode:
+# - default: Do normal logging to CustomLog and ErrorLog
+# - nologs: Send every logging to /dev/null
+# - anonym: Don't log ips for CustomLog, send ErrorLog to /dev/null
+# - semianonym: Don't log ips for CustomLog, log normal ErrorLog
+#
+define apache::vhost::modperl(
+ $ensure = present,
+ $configuration = configuration,
+ $domain = 'absent',
+ $domainalias = 'absent',
+ $server_admin = 'absent',
+ $logmode = 'default',
+ $path = 'absent',
+ $owner = root,
+ $group = apache,
+ $documentroot_owner = apache,
+ $documentroot_group = 0,
+ $documentroot_mode = 0640,
+ $run_mode = 'normal',
+ $run_uid = 'absent',
+ $run_gid = 'absent',
+ $allow_override = 'None',
+ $cgi_binpath = 'absent',
+ $do_includes = false,
+ $options = 'absent',
+ $additional_options = 'absent',
+ $default_charset = 'absent',
+ $mod_security = true,
+ $mod_security_relevantonly = true,
+ $mod_security_rules_to_disable = [],
+ $mod_security_additional_options = 'absent',
+ $ssl_mode = false,
+ $vhost_mode = 'template',
+ $template_partial = 'apache/vhosts/perl/partial.erb',
+ $vhost_source = 'absent',
+ $vhost_destination = 'absent',
+ $htpasswd_file = 'absent',
+ $htpasswd_path = 'absent'
+){
+ # cgi_bin path
+ case $cgi_binpath {
+ 'absent': {
+ $real_path = $path ? {
+ 'absent' => $::operatingsystem ? {
+ openbsd => "/var/www/htdocs/${name}",
+ default => "/var/www/vhosts/${name}"
+ },
+ default => $path
+ }
+ $real_cgi_binpath = "${real_path}/cgi-bin"
+ }
+ default: { $real_cgi_binpath = $cgi_binpath }
+ }
+
+ file{$real_cgi_binpath:
+ ensure => $ensure ? {
+ 'absent' => 'absent',
+ default => directory
+ },
+ owner => $documentroot_owner,
+ group => $documentroot_group,
+ mode => $documentroot_mode;
+ }
+
+ if $ensure != 'absent' {
+ case $run_mode {
+ 'proxy-itk','static-itk': {
+ include ::mod_perl::itk_plus
+ }
+ 'fcgid': {
+ include ::mod_fcgid
+ include apache::include::mod_fcgid
+
+ # we don't need mod_perl if we run it as fcgid
+ include ::mod_perl::disable
+ mod_fcgid::starter {$name:
+ cgi_type => 'perl',
+ owner => $run_uid,
+ group => $run_gid,
+ notify => Service['apache'],
+ }
+ }
+ default: { include ::mod_perl }
+ }
+ }
+
+ # create webdir
+ ::apache::vhost::webdir{$name:
+ ensure => $ensure,
+ path => $path,
+ owner => $owner,
+ group => $group,
+ run_mode => $run_mode,
+ documentroot_owner => $documentroot_owner,
+ documentroot_group => $documentroot_group,
+ documentroot_mode => $documentroot_mode,
+ }
+
+ # create vhost configuration file
+ ::apache::vhost{$name:
+ ensure => $ensure,
+ configuration => $configuration,
+ path => $path,
+ logmode => $logmode,
+ vhost_mode => $vhost_mode,
+ template_partial => $template_partial,
+ vhost_source => $vhost_source,
+ vhost_destination => $vhost_destination,
+ domain => $domain,
+ domainalias => $domainalias,
+ server_admin => $server_admin,
+ run_mode => $run_mode,
+ run_uid => $run_uid,
+ run_gid => $run_gid,
+ allow_override => $allow_override,
+ do_includes => $do_includes,
+ options => $options,
+ additional_options => $additional_options,
+ default_charset => $default_charset,
+ cgi_binpath => $real_cgi_binpath,
+ ssl_mode => $ssl_mode,
+ htpasswd_file => $htpasswd_file,
+ htpasswd_path => $htpasswd_path,
+ mod_security => $mod_security,
+ mod_security_relevantonly => $mod_security_relevantonly,
+ mod_security_rules_to_disable => $mod_security_rules_to_disable,
+ mod_security_additional_options => $mod_security_additional_options,
+ passing_extension => 'pl'
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/vhost/passenger.pp b/puppet/modules/apache/manifests/vhost/passenger.pp
new file mode 100644
index 00000000..46218908
--- /dev/null
+++ b/puppet/modules/apache/manifests/vhost/passenger.pp
@@ -0,0 +1,139 @@
+# run_uid: the uid the vhost should run as with the mod_passenger module
+# run_gid: the gid the vhost should run as with the mod_passenger module
+#
+# logmode:
+#
+# - default: Do normal logging to CustomLog and ErrorLog
+# - nologs: Send every logging to /dev/null
+# - anonym: Don't log ips for CustomLog, send ErrorLog to /dev/null
+# - semianonym: Don't log ips for CustomLog, log normal ErrorLog
+#
+# mod_security: Whether we use mod_security or not (will include mod_security module)
+# - false: don't activate mod_security
+# - true: (*defaul*) activate mod_security
+#
+define apache::vhost::passenger(
+ $ensure = present,
+ $configuration = {},
+ $domain = 'absent',
+ $domainalias = 'absent',
+ $server_admin = 'absent',
+ $logmode = 'default',
+ $path = 'absent',
+ $manage_webdir = true,
+ $manage_docroot = true,
+ $owner = root,
+ $group = apache,
+ $documentroot_owner = apache,
+ $documentroot_group = 0,
+ $documentroot_mode = 0640,
+ $run_uid = 'absent',
+ $run_gid = 'absent',
+ $allow_override = 'None',
+ $do_includes = false,
+ $options = 'absent',
+ $additional_options = 'absent',
+ $default_charset = 'absent',
+ $mod_security = true,
+ $mod_security_relevantonly = true,
+ $mod_security_rules_to_disable = [],
+ $mod_security_additional_options = 'absent',
+ $ssl_mode = false,
+ $vhost_mode = 'template',
+ $template_partial = 'apache/vhosts/passenger/partial.erb',
+ $vhost_source = 'absent',
+ $vhost_destination = 'absent',
+ $htpasswd_file = 'absent',
+ $htpasswd_path = 'absent',
+ $passenger_ree = false,
+ $passenger_app = 'rails'
+){
+
+ if $passenger_ree {
+ include ::passenger::ree::apache
+ } else {
+ include ::passenger::apache
+ }
+
+ if $manage_webdir {
+ # create webdir
+ ::apache::vhost::webdir{$name:
+ ensure => $ensure,
+ path => $path,
+ owner => $owner,
+ group => $group,
+ mode => 0644,
+ run_mode => 'normal',
+ manage_docroot => $manage_docroot,
+ documentroot_owner => $documentroot_owner,
+ documentroot_group => $run_gid,
+ documentroot_mode => $documentroot_mode,
+ }
+ }
+ $real_path = $path ? {
+ 'absent' => $::operatingsystem ? {
+ openbsd => "/var/www/htdocs/${name}",
+ default => "/var/www/vhosts/${name}"
+ },
+ default => $path
+ }
+ file{
+ ["${real_path}/www/tmp", "${real_path}/www/log"]:
+ ensure => directory,
+ owner => $documentroot_owner, group => $run_gid, mode => 0660;
+ ["${real_path}/www/public", "${real_path}/gems"]:
+ ensure => directory,
+ owner => $documentroot_owner, group => $run_gid, mode => 0640;
+ }
+ if $passenger_app == 'rails' {
+ file{
+ "${real_path}/www/config":
+ ensure => directory,
+ owner => $documentroot_owner, group => $run_gid, mode => 0640;
+ "${real_path}/www/config/environment.rb":
+ ensure => present,
+ owner => $run_uid, group => $run_gid, mode => 0640;
+ }
+ } else {
+ #rack based
+ file{
+ "${real_path}/www/config.ru":
+ ensure => present,
+ owner => $run_uid, group => $run_gid, mode => 0640;
+ }
+ }
+
+ # create vhost configuration file
+ ::apache::vhost{$name:
+ ensure => $ensure,
+ configuration => $configuration,
+ path => "${real_path}/www/public",
+ path_is_webdir => true,
+ template_partial => $template_partial,
+ logmode => $logmode,
+ logpath => "${real_path}/logs",
+ vhost_mode => $vhost_mode,
+ vhost_source => $vhost_source,
+ vhost_destination => $vhost_destination,
+ domain => $domain,
+ domainalias => $domainalias,
+ server_admin => $server_admin,
+ run_mode => 'normal',
+ run_uid => $run_uid,
+ run_gid => $run_gid,
+ allow_override => $allow_override,
+ do_includes => $do_includes,
+ options => $options,
+ additional_options => $additional_options,
+ default_charset => $default_charset,
+ ssl_mode => $ssl_mode,
+ htpasswd_file => $htpasswd_file,
+ htpasswd_path => $htpasswd_path,
+ mod_security => $mod_security,
+ mod_security_relevantonly => $mod_security_relevantonly,
+ mod_security_rules_to_disable => $mod_security_rules_to_disable,
+ mod_security_additional_options => $mod_security_additional_options,
+ gempath => "${real_path}/gems"
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/vhost/php/drupal.pp b/puppet/modules/apache/manifests/vhost/php/drupal.pp
new file mode 100644
index 00000000..5b15e6a0
--- /dev/null
+++ b/puppet/modules/apache/manifests/vhost/php/drupal.pp
@@ -0,0 +1,144 @@
+# run_mode: controls in which mode the vhost should be run, there are different setups
+# possible:
+# - normal: (*default*) run vhost with the current active worker (default: prefork) don't
+# setup anything special
+# - itk: run vhost with the mpm_itk module (Incompatibility: cannot be used in combination
+# with 'proxy-itk' & 'static-itk' mode)
+# - proxy-itk: run vhost with a dual prefork/itk setup, where prefork just proxies all the
+# requests for the itk setup, that listens only on the loobpack device.
+# (Incompatibility: cannot be used in combination with the itk setup.)
+# - static-itk: run vhost with a dual prefork/itk setup, where prefork serves all the static
+# content and proxies the dynamic calls to the itk setup, that listens only on
+# the loobpack device (Incompatibility: cannot be used in combination with
+# 'itk' mode)
+#
+# run_uid: the uid the vhost should run as with the itk module
+# run_gid: the gid the vhost should run as with the itk module
+#
+# mod_security: Whether we use mod_security or not (will include mod_security module)
+# - false: don't activate mod_security
+# - true: (*default*) activate mod_security
+#
+# php_safe_mode_exec_bins: An array of local binaries which should be linked in the
+# safe_mode_exec_bin for this hosting
+# *default*: None
+# php_default_charset: default charset header for php.
+# *default*: absent, which will set the same as default_charset
+# of apache
+# logmode:
+# - default: Do normal logging to CustomLog and ErrorLog
+# - nologs: Send every logging to /dev/null
+# - anonym: Don't log ips for CustomLog, send ErrorLog to /dev/null
+# - semianonym: Don't log ips for CustomLog, log normal ErrorLog
+#
+define apache::vhost::php::drupal(
+ $ensure = present,
+ $configuration = {},
+ $domain = 'absent',
+ $domainalias = 'absent',
+ $server_admin = 'absent',
+ $logmode = 'default',
+ $path = 'absent',
+ $owner = root,
+ $group = apache,
+ $documentroot_owner = apache,
+ $documentroot_group = 0,
+ $documentroot_mode = '0640',
+ $run_mode = 'normal',
+ $run_uid = 'absent',
+ $run_gid = 'absent',
+ $allow_override = 'None',
+ $php_settings = {},
+ $php_options = {},
+ $do_includes = false,
+ $options = 'absent',
+ $additional_options = 'absent',
+ $default_charset = 'absent',
+ $mod_security = true,
+ $mod_security_relevantonly = true,
+ $mod_security_rules_to_disable = [],
+ $mod_security_additional_options = 'absent',
+ $ssl_mode = false,
+ $vhost_mode = 'template',
+ $template_partial = 'apache/vhosts/php_drupal/partial.erb',
+ $vhost_source = 'absent',
+ $vhost_destination = 'absent',
+ $htpasswd_file = 'absent',
+ $htpasswd_path = 'absent',
+ $manage_directories = true,
+ $config_webwriteable = false,
+ $manage_config = true,
+ $manage_cron = true
+){
+ $documentroot = $path ? {
+ 'absent' => $::operatingsystem ? {
+ openbsd => "/var/www/htdocs/${name}/www",
+ default => "/var/www/vhosts/${name}/www"
+ },
+ default => "${path}/www"
+ }
+
+ if $manage_cron {
+ if $domain == 'absent' {
+ $real_domain = $name
+ } else {
+ $real_domain = $domain
+ }
+
+ file{"/etc/cron.d/drupal_cron_${name}":
+ content => "0 * * * * apache wget -O - -q -t 1 http://${real_domain}/cron.php\n",
+ owner => root,
+ group => 0,
+ mode => '0644';
+ }
+ }
+
+ $std_drupal_php_settings = {
+ magic_quotes_gpc => 0,
+ register_globals => 0,
+ 'session.auto_start' => 0,
+ 'mbstring.http_input' => 'pass',
+ 'mbstring.http_output' => 'pass',
+ 'mbstring.encoding_translation' => 0,
+ }
+
+ # create vhost configuration file
+ ::apache::vhost::php::webapp{$name:
+ ensure => $ensure,
+ configuration => $configuration,
+ domain => $domain,
+ domainalias => $domainalias,
+ server_admin => $server_admin,
+ logmode => $logmode,
+ path => $path,
+ owner => $owner,
+ group => $group,
+ documentroot_owner => $documentroot_owner,
+ documentroot_group => $documentroot_group,
+ documentroot_mode => $documentroot_mode,
+ run_mode => $run_mode,
+ run_uid => $run_uid,
+ run_gid => $run_gid,
+ allow_override => $allow_override,
+ php_settings => merge($std_drupal_php_settings, $php_settings),
+ php_options => $php_options,
+ do_includes => $do_includes,
+ options => $options,
+ additional_options => $additional_options,
+ default_charset => $default_charset,
+ mod_security => $mod_security,
+ mod_security_relevantonly => $mod_security_relevantonly,
+ mod_security_rules_to_disable => $mod_security_rules_to_disable,
+ mod_security_additional_options => $mod_security_additional_options,
+ ssl_mode => $ssl_mode,
+ vhost_mode => $vhost_mode,
+ template_partial => $template_partial,
+ vhost_source => $vhost_source,
+ vhost_destination => $vhost_destination,
+ htpasswd_file => $htpasswd_file,
+ htpasswd_path => $htpasswd_path,
+ manage_directories => false,
+ manage_config => false,
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/vhost/php/gallery2.pp b/puppet/modules/apache/manifests/vhost/php/gallery2.pp
new file mode 100644
index 00000000..3acb011d
--- /dev/null
+++ b/puppet/modules/apache/manifests/vhost/php/gallery2.pp
@@ -0,0 +1,141 @@
+# run_mode: controls in which mode the vhost should be run, there are different setups
+# possible:
+# - normal: (*default*) run vhost with the current active worker (default: prefork) don't
+# setup anything special
+# - itk: run vhost with the mpm_itk module (Incompatibility: cannot be used in combination
+# with 'proxy-itk' & 'static-itk' mode)
+# - proxy-itk: run vhost with a dual prefork/itk setup, where prefork just proxies all the
+# requests for the itk setup, that listens only on the loobpack device.
+# (Incompatibility: cannot be used in combination with the itk setup.)
+# - static-itk: run vhost with a dual prefork/itk setup, where prefork serves all the static
+# content and proxies the dynamic calls to the itk setup, that listens only on
+# the loobpack device (Incompatibility: cannot be used in combination with
+# 'itk' mode)
+#
+# run_uid: the uid the vhost should run as with the itk module
+# run_gid: the gid the vhost should run as with the itk module
+#
+# mod_security: Whether we use mod_security or not (will include mod_security module)
+# - false: (*defaul*) don't activate mod_security
+# - true: activate mod_security
+#
+# php_safe_mode_exec_bins: An array of local binaries which should be linked in the
+# safe_mode_exec_bin for this hosting
+# *default*: None
+# php_default_charset: default charset header for php.
+# *default*: absent, which will set the same as default_charset
+# of apache
+# logmode:
+# - default: Do normal logging to CustomLog and ErrorLog
+# - nologs: Send every logging to /dev/null
+# - anonym: Don't log ips for CustomLog, send ErrorLog to /dev/null
+# - semianonym: Don't log ips for CustomLog, log normal ErrorLog
+define apache::vhost::php::gallery2(
+ $ensure = present,
+ $configuration = {},
+ $domain = 'absent',
+ $domainalias = 'absent',
+ $server_admin = 'absent',
+ $logmode = 'default',
+ $path = 'absent',
+ $owner = root,
+ $group = apache,
+ $documentroot_owner = apache,
+ $documentroot_group = 0,
+ $documentroot_mode = 0640,
+ $run_mode = 'normal',
+ $run_uid = 'absent',
+ $run_gid = 'absent',
+ $allow_override = 'None',
+ $php_settings = {},
+ $php_options = {},
+ $do_includes = false,
+ $options = 'absent',
+ $additional_options = 'absent',
+ $default_charset = 'absent',
+ $mod_security = false,
+ $mod_security_relevantonly = true,
+ $mod_security_rules_to_disable = [],
+ $mod_security_additional_options = 'absent',
+ $ssl_mode = false,
+ $vhost_mode = 'template',
+ $template_partial = 'apache/vhosts/php_gallery2/partial.erb',
+ $vhost_source = 'absent',
+ $vhost_destination = 'absent',
+ $htpasswd_file = 'absent',
+ $htpasswd_path = 'absent',
+ $manage_config = true,
+ $config_webwriteable = false,
+ $manage_directories = true,
+){
+ $documentroot = $path ? {
+ 'absent' => $::operatingsystem ? {
+ openbsd => "/var/www/htdocs/${name}/www",
+ default => "/var/www/vhosts/${name}/www"
+ },
+ default => "${path}/www"
+ }
+ $upload_dir = "/var/www/vhosts/${name}/data/upload"
+ $gdata_dir = "/var/www/vhosts/${name}/data/gdata"
+ if $ensure != 'absent' {
+ file{
+ $gdata_dir:
+ ensure => 'directory',
+ owner => $documentroot_owner,
+ group => $documentroot_group,
+ mode => '0660';
+ $upload_dir:
+ ensure => 'directory',
+ owner => $documentroot_owner,
+ group => $documentroot_group,
+ mode => '0660';
+ }
+ }
+
+ $gallery_php_settings = {
+ safe_mode => 'Off',
+ output_buffering => 'Off',
+ }
+ $real_php_settings = merge($gallery_php_settings,$php_settings)
+
+ # create vhost configuration file
+ ::apache::vhost::php::webapp{$name:
+ ensure => $ensure,
+ configuration => $configuration,
+ domain => $domain,
+ domainalias => $domainalias,
+ server_admin => $server_admin,
+ logmode => $logmode,
+ path => $path,
+ owner => $owner,
+ group => $group,
+ documentroot_owner => $documentroot_owner,
+ documentroot_group => $documentroot_group,
+ documentroot_mode => $documentroot_mode,
+ run_mode => $run_mode,
+ run_uid => $run_uid,
+ run_gid => $run_gid,
+ allow_override => $allow_override,
+ php_settings => $real_php_settings,
+ php_options => $php_options,
+ do_includes => $do_includes,
+ options => $options,
+ additional_options => $additional_options,
+ default_charset => $default_charset,
+ mod_security => $mod_security,
+ mod_security_relevantonly => $mod_security_relevantonly,
+ mod_security_rules_to_disable => $mod_security_rules_to_disable,
+ mod_security_additional_options => $mod_security_additional_options,
+ ssl_mode => $ssl_mode,
+ vhost_mode => $vhost_mode,
+ template_partial => $template_partial,
+ vhost_source => $vhost_source,
+ vhost_destination => $vhost_destination,
+ htpasswd_file => $htpasswd_file,
+ htpasswd_path => $htpasswd_path,
+ manage_directories => $manage_directories,
+ manage_config => $manage_config,
+ config_file => 'config.php',
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/vhost/php/global_exec_bin_dir.pp b/puppet/modules/apache/manifests/vhost/php/global_exec_bin_dir.pp
new file mode 100644
index 00000000..efcdaf7f
--- /dev/null
+++ b/puppet/modules/apache/manifests/vhost/php/global_exec_bin_dir.pp
@@ -0,0 +1,9 @@
+# manage global exec_bin_dir
+class apache::vhost::php::global_exec_bin_dir {
+ file{'/var/www/php_safe_exec_bins':
+ ensure => directory,
+ owner => root,
+ group => apache,
+ mode => '0640';
+ }
+}
diff --git a/puppet/modules/apache/manifests/vhost/php/joomla.pp b/puppet/modules/apache/manifests/vhost/php/joomla.pp
new file mode 100644
index 00000000..ed0696f8
--- /dev/null
+++ b/puppet/modules/apache/manifests/vhost/php/joomla.pp
@@ -0,0 +1,174 @@
+# run_mode: controls in which mode the vhost should be run, there are different
+# setups possible:
+# - normal: (*default*) run vhost with the current active worker
+# (default: prefork) don't setup anything special
+# - itk: run vhost with the mpm_itk module (Incompatibility: cannot be used in
+# combination with 'proxy-itk' & 'static-itk' mode)
+# - proxy-itk: run vhost with a dual prefork/itk setup, where prefork just
+# proxies all the requests for the itk setup, that listens only
+# on the loobpack device.
+# (Incompatibility: cannot be used in combination with the itk
+# setup.)
+# - static-itk: run vhost with a dual prefork/itk setup, where prefork serves
+# all the static
+# content and proxies the dynamic calls to the itk setup, that
+# listens only on the loobpack device
+# (Incompatibility: cannot be used in combination with 'itk'
+# mode)
+#
+# run_uid: the uid the vhost should run as with the itk module
+# run_gid: the gid the vhost should run as with the itk module
+#
+# mod_security: Whether we use mod_security or not (will include mod_security
+# module)
+# - false: don't activate mod_security
+# - true: (*default*) activate mod_security
+#
+# logmode:
+# - default: Do normal logging to CustomLog and ErrorLog
+# - nologs: Send every logging to /dev/null
+# - anonym: Don't log ips for CustomLog, send ErrorLog to /dev/null
+# - semianonym: Don't log ips for CustomLog, log normal ErrorLog
+define apache::vhost::php::joomla(
+ $ensure = present,
+ $configuration = {},
+ $domain = 'absent',
+ $domainalias = 'absent',
+ $server_admin = 'absent',
+ $logmode = 'default',
+ $path = 'absent',
+ $owner = root,
+ $group = apache,
+ $documentroot_owner = apache,
+ $documentroot_group = 0,
+ $documentroot_mode = '0640',
+ $run_mode = 'normal',
+ $run_uid = 'absent',
+ $run_gid = 'absent',
+ $allow_override = 'None',
+ $php_settings = {},
+ $php_options = {},
+ $php_installation = 'system',
+ $do_includes = false,
+ $options = 'absent',
+ $additional_options = 'absent',
+ $default_charset = 'absent',
+ $mod_security = true,
+ $mod_security_relevantonly = true,
+ $mod_security_rules_to_disable = [],
+ $mod_security_additional_options = 'absent',
+ $ssl_mode = false,
+ $vhost_mode = 'template',
+ $template_partial = 'apache/vhosts/php_joomla/partial.erb',
+ $vhost_source = 'absent',
+ $vhost_destination = 'absent',
+ $htpasswd_file = 'absent',
+ $htpasswd_path = 'absent',
+ $manage_config = true,
+ $config_webwriteable = false,
+ $manage_directories = true
+){
+ include ::apache::include::joomla
+
+ $documentroot = $path ? {
+ 'absent' => $::operatingsystem ? {
+ openbsd => "/var/www/htdocs/${name}/www",
+ default => "/var/www/vhosts/${name}/www"
+ },
+ default => "${path}/www"
+ }
+
+ if $mod_security_additional_options == 'absent' {
+ $id_str = $::operatingsystem ? {
+ 'CentOS' => $::operatingsystemmajrelease ? {
+ 5 => '',
+ default => 'id:1199400,'
+ },
+ default => ''
+ }
+ $real_mod_security_additional_options = "
+ # http://optics.csufresno.edu/~kriehn/fedora/fedora_files/f9/howto/modsecurity.html
+ # Exceptions for Joomla Root Directory
+ <LocationMatch \"^/\">
+ SecRuleRemoveById 950013
+ </LocationMatch>
+
+ # Exceptions for Joomla Administration Panel
+ SecRule REQUEST_FILENAME \"/administrator/index2.php\" \"${id_str}allow,phase:1,nolog,ctl:ruleEngine=Off\"
+
+ # Exceptions for Joomla Component Expose
+ <LocationMatch \"^/components/com_expose/expose/manager/amfphp/gateway.php\">
+ SecRuleRemoveById 960010
+ </LocationMatch>
+"
+ } else {
+ $real_mod_security_additional_options = $mod_security_additional_options
+ }
+
+ $std_joomla_php_settings = {
+ 'allow_url_fopen' => 'on',
+ 'allow_url_include' => 'off',
+ }
+
+ # create vhost configuration file
+ ::apache::vhost::php::webapp{
+ $name:
+ ensure => $ensure,
+ configuration => $configuration,
+ domain => $domain,
+ domainalias => $domainalias,
+ server_admin => $server_admin,
+ logmode => $logmode,
+ path => $path,
+ owner => $owner,
+ group => $group,
+ documentroot_owner => $documentroot_owner,
+ documentroot_group => $documentroot_group,
+ documentroot_mode => $documentroot_mode,
+ run_mode => $run_mode,
+ run_uid => $run_uid,
+ run_gid => $run_gid,
+ allow_override => $allow_override,
+ php_settings => merge($std_joomla_php_settings,
+ $php_settings),
+ php_options => $php_options,
+ php_installation => $php_installation,
+ do_includes => $do_includes,
+ options => $options,
+ additional_options => $additional_options,
+ default_charset => $default_charset,
+ mod_security => $mod_security,
+ mod_security_relevantonly => $mod_security_relevantonly,
+ mod_security_rules_to_disable => $mod_security_rules_to_disable,
+ mod_security_additional_options => $real_mod_security_additional_options,
+ ssl_mode => $ssl_mode,
+ vhost_mode => $vhost_mode,
+ template_partial => $template_partial,
+ vhost_source => $vhost_source,
+ vhost_destination => $vhost_destination,
+ htpasswd_file => $htpasswd_file,
+ htpasswd_path => $htpasswd_path,
+ manage_directories => $manage_directories,
+ managed_directories => [ "${documentroot}/administrator/backups",
+ "${documentroot}/administrator/components",
+ "${documentroot}/administrator/language",
+ "${documentroot}/administrator/modules",
+ "${documentroot}/administrator/templates",
+ "${documentroot}/components",
+ "${documentroot}/dmdocuments",
+ "${documentroot}/images",
+ "${documentroot}/language",
+ "${documentroot}/media",
+ "${documentroot}/modules",
+ "${documentroot}/plugins",
+ "${documentroot}/templates",
+ "${documentroot}/cache",
+ "${documentroot}/tmp",
+ "${documentroot}/administrator/cache" ],
+ manage_config => $manage_config,
+ config_webwriteable => $config_webwriteable,
+ config_file => 'configuration.php',
+ }
+
+}
+
diff --git a/puppet/modules/apache/manifests/vhost/php/mediawiki.pp b/puppet/modules/apache/manifests/vhost/php/mediawiki.pp
new file mode 100644
index 00000000..25881ca1
--- /dev/null
+++ b/puppet/modules/apache/manifests/vhost/php/mediawiki.pp
@@ -0,0 +1,106 @@
+# run_mode: controls in which mode the vhost should be run, there are different setups
+# possible:
+# - normal: (*default*) run vhost with the current active worker (default: prefork) don't
+# setup anything special
+# - itk: run vhost with the mpm_itk module (Incompatibility: cannot be used in combination
+# with 'proxy-itk' & 'static-itk' mode)
+# - proxy-itk: run vhost with a dual prefork/itk setup, where prefork just proxies all the
+# requests for the itk setup, that listens only on the loobpack device.
+# (Incompatibility: cannot be used in combination with the itk setup.)
+# - static-itk: run vhost with a dual prefork/itk setup, where prefork serves all the static
+# content and proxies the dynamic calls to the itk setup, that listens only on
+# the loobpack device (Incompatibility: cannot be used in combination with
+# 'itk' mode)
+#
+# run_uid: the uid the vhost should run as with the itk module
+# run_gid: the gid the vhost should run as with the itk module
+#
+# mod_security: Whether we use mod_security or not (will include mod_security module)
+# - false: don't activate mod_security
+# - true: (*default*) activate mod_security
+#
+# logmode:
+# - default: Do normal logging to CustomLog and ErrorLog
+# - nologs: Send every logging to /dev/null
+# - anonym: Don't log ips for CustomLog, send ErrorLog to /dev/null
+# - semianonym: Don't log ips for CustomLog, log normal ErrorLog
+define apache::vhost::php::mediawiki(
+ $ensure = present,
+ $configuration = {},
+ $domain = 'absent',
+ $domainalias = 'absent',
+ $server_admin = 'absent',
+ $logmode = 'default',
+ $path = 'absent',
+ $manage_docroot = true,
+ $owner = root,
+ $group = apache,
+ $documentroot_owner = apache,
+ $documentroot_group = 0,
+ $documentroot_mode = 0640,
+ $run_mode = 'normal',
+ $run_uid = 'absent',
+ $run_gid = 'absent',
+ $allow_override = 'FileInfo Limit',
+ $php_settings = {},
+ $php_options = {},
+ $options = 'absent',
+ $additional_options = 'absent',
+ $default_charset = 'absent',
+ $mod_security = true,
+ $mod_security_relevantonly = true,
+ $mod_security_rules_to_disable = [],
+ $mod_security_additional_options = 'absent',
+ $ssl_mode = false,
+ $vhost_mode = 'template',
+ $template_partial = 'apache/vhosts/php_mediawiki/partial.erb',
+ $vhost_source = 'absent',
+ $vhost_destination = 'absent',
+ $htpasswd_file = 'absent',
+ $htpasswd_path = 'absent'
+){
+
+ $mediawiki_php_settings = {
+ safe_mode => false,
+ }
+
+ # create vhost configuration file
+ ::apache::vhost::php::webapp{$name:
+ ensure => $ensure,
+ configuration => $configuration,
+ domain => $domain,
+ domainalias => $domainalias,
+ server_admin => $server_admin,
+ logmode => $logmode,
+ path => $path,
+ manage_docroot => $manage_docroot,
+ owner => $owner,
+ group => $group,
+ documentroot_owner => $documentroot_owner,
+ documentroot_group => $documentroot_group,
+ documentroot_mode => $documentroot_mode,
+ run_mode => $run_mode,
+ run_uid => $run_uid,
+ run_gid => $run_gid,
+ allow_override => $allow_override,
+ php_settings => merge($mediawiki_php_settings,$php_settings),
+ php_options => $php_options,
+ options => $options,
+ additional_options => $additional_options,
+ default_charset => $default_charset,
+ mod_security => $mod_security,
+ mod_security_relevantonly => $mod_security_relevantonly,
+ mod_security_rules_to_disable => $mod_security_rules_to_disable,
+ mod_security_additional_options => $mod_security_additional_options,
+ ssl_mode => $ssl_mode,
+ vhost_mode => $vhost_mode,
+ template_partial => $template_partial,
+ vhost_source => $vhost_source,
+ vhost_destination => $vhost_destination,
+ htpasswd_file => $htpasswd_file,
+ htpasswd_path => $htpasswd_path,
+ manage_directories => false,
+ manage_config => false,
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/vhost/php/safe_mode_bin.pp b/puppet/modules/apache/manifests/vhost/php/safe_mode_bin.pp
new file mode 100644
index 00000000..1c82e199
--- /dev/null
+++ b/puppet/modules/apache/manifests/vhost/php/safe_mode_bin.pp
@@ -0,0 +1,17 @@
+# safe_mode binaries
+define apache::vhost::php::safe_mode_bin(
+ $ensure = 'present',
+ $path
+){
+ $substr=regsubst($name,'^.*\/','','G')
+ $real_path = "${path}/${substr}"
+ $target = $ensure ? {
+ 'present' => regsubst($name,'^.*@',''),
+ default => absent,
+ }
+ file{$real_path:
+ ensure => link,
+ target => $target,
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/vhost/php/silverstripe.pp b/puppet/modules/apache/manifests/vhost/php/silverstripe.pp
new file mode 100644
index 00000000..1f19eab4
--- /dev/null
+++ b/puppet/modules/apache/manifests/vhost/php/silverstripe.pp
@@ -0,0 +1,119 @@
+# run_mode: controls in which mode the vhost should be run, there are different setups
+# possible:
+# - normal: (*default*) run vhost with the current active worker (default: prefork) don't
+# setup anything special
+# - itk: run vhost with the mpm_itk module (Incompatibility: cannot be used in combination
+# with 'proxy-itk' & 'static-itk' mode)
+# - proxy-itk: run vhost with a dual prefork/itk setup, where prefork just proxies all the
+# requests for the itk setup, that listens only on the loobpack device.
+# (Incompatibility: cannot be used in combination with the itk setup.)
+# - static-itk: run vhost with a dual prefork/itk setup, where prefork serves all the static
+# content and proxies the dynamic calls to the itk setup, that listens only on
+# the loobpack device (Incompatibility: cannot be used in combination with
+# 'itk' mode)
+#
+# run_uid: the uid the vhost should run as with the itk module
+# run_gid: the gid the vhost should run as with the itk module
+#
+# mod_security: Whether we use mod_security or not (will include mod_security module)
+# - false: don't activate mod_security
+# - true: (*default*) activate mod_security
+#
+# logmode:
+# - default: Do normal logging to CustomLog and ErrorLog
+# - nologs: Send every logging to /dev/null
+# - anonym: Don't log ips for CustomLog, send ErrorLog to /dev/null
+# - semianonym: Don't log ips for CustomLog, log normal ErrorLog
+define apache::vhost::php::silverstripe(
+ $ensure = present,
+ $configuration = {},
+ $domain = 'absent',
+ $domainalias = 'absent',
+ $server_admin = 'absent',
+ $logmode = 'default',
+ $path = 'absent',
+ $owner = root,
+ $group = apache,
+ $documentroot_owner = apache,
+ $documentroot_group = 0,
+ $documentroot_mode = '0640',
+ $run_mode = 'normal',
+ $run_uid = 'absent',
+ $run_gid = 'absent',
+ $allow_override = 'None',
+ $php_settings = {},
+ $php_options = {},
+ $do_includes = false,
+ $options = 'absent',
+ $additional_options = 'absent',
+ $default_charset = 'absent',
+ $mod_security = true,
+ $mod_security_relevantonly = true,
+ $mod_security_rules_to_disable = [],
+ $mod_security_additional_options = 'absent',
+ $ssl_mode = false,
+ $vhost_mode = 'template',
+ $template_partial = 'apache/vhosts/php_silverstripe/partial.erb',
+ $vhost_source = 'absent',
+ $vhost_destination = 'absent',
+ $htpasswd_file = 'absent',
+ $htpasswd_path = 'absent',
+ $manage_config = true,
+ $config_webwriteable = false,
+ $manage_directories = true,
+){
+
+ include ::apache::include::silverstripe
+
+ $documentroot = $path ? {
+ 'absent' => $::operatingsystem ? {
+ openbsd => "/var/www/htdocs/${name}/www",
+ default => "/var/www/vhosts/${name}/www"
+ },
+ default => "${path}/www"
+ }
+ $modsec_rules = ['960010']
+ $real_mod_security_rules_to_disable = union($mod_security_rules_to_disable,$modsec_rules)
+
+ # create vhost configuration file
+ ::apache::vhost::php::webapp{$name:
+ ensure => $ensure,
+ configuration => $configuration,
+ domain => $domain,
+ domainalias => $domainalias,
+ server_admin => $server_admin,
+ logmode => $logmode,
+ path => $path,
+ owner => $owner,
+ group => $group,
+ documentroot_owner => $documentroot_owner,
+ documentroot_group => $documentroot_group,
+ documentroot_mode => $documentroot_mode,
+ run_mode => $run_mode,
+ run_uid => $run_uid,
+ run_gid => $run_gid,
+ allow_override => $allow_override,
+ php_settings => $php_settings,
+ php_options => $php_options,
+ do_includes => $do_includes,
+ options => $options,
+ additional_options => $additional_options,
+ default_charset => $default_charset,
+ mod_security => $mod_security,
+ mod_security_relevantonly => $mod_security_relevantonly,
+ mod_security_rules_to_disable => $mod_security_rules_to_disable,
+ mod_security_additional_options => $mod_security_additional_options,
+ ssl_mode => $ssl_mode,
+ vhost_mode => $vhost_mode,
+ template_partial => $template_partial,
+ vhost_source => $vhost_source,
+ vhost_destination => $vhost_destination,
+ htpasswd_file => $htpasswd_file,
+ htpasswd_path => $htpasswd_path,
+ manage_directories => $manage_directories,
+ managed_directories => [ "${documentroot}/assets" ],
+ manage_config => $manage_config,
+ }
+
+}
+
diff --git a/puppet/modules/apache/manifests/vhost/php/simplemachine.pp b/puppet/modules/apache/manifests/vhost/php/simplemachine.pp
new file mode 100644
index 00000000..3fa11a77
--- /dev/null
+++ b/puppet/modules/apache/manifests/vhost/php/simplemachine.pp
@@ -0,0 +1,125 @@
+# run_mode: controls in which mode the vhost should be run, there are different setups
+# possible:
+# - normal: (*default*) run vhost with the current active worker (default: prefork) don't
+# setup anything special
+# - itk: run vhost with the mpm_itk module (Incompatibility: cannot be used in combination
+# with 'proxy-itk' & 'static-itk' mode)
+# - proxy-itk: run vhost with a dual prefork/itk setup, where prefork just proxies all the
+# requests for the itk setup, that listens only on the loobpack device.
+# (Incompatibility: cannot be used in combination with the itk setup.)
+# - static-itk: run vhost with a dual prefork/itk setup, where prefork serves all the static
+# content and proxies the dynamic calls to the itk setup, that listens only on
+# the loobpack device (Incompatibility: cannot be used in combination with
+# 'itk' mode)
+#
+# run_uid: the uid the vhost should run as with the itk module
+# run_gid: the gid the vhost should run as with the itk module
+#
+# mod_security: Whether we use mod_security or not (will include mod_security module)
+# - false: don't activate mod_security
+# - true: (*default*) activate mod_security
+#
+# logmode:
+# - default: Do normal logging to CustomLog and ErrorLog
+# - nologs: Send every logging to /dev/null
+# - anonym: Don't log ips for CustomLog, send ErrorLog to /dev/null
+# - semianonym: Don't log ips for CustomLog, log normal ErrorLog
+define apache::vhost::php::simplemachine(
+ $ensure = present,
+ $configuration = {},
+ $domain = 'absent',
+ $domainalias = 'absent',
+ $server_admin = 'absent',
+ $logmode = 'default',
+ $path = 'absent',
+ $owner = root,
+ $group = apache,
+ $documentroot_owner = apache,
+ $documentroot_group = 0,
+ $documentroot_mode = '0640',
+ $run_mode = 'normal',
+ $run_uid = 'absent',
+ $run_gid = 'absent',
+ $allow_override = 'None',
+ $php_settings = {},
+ $php_options = {},
+ $do_includes = false,
+ $options = 'absent',
+ $additional_options = 'absent',
+ $default_charset = 'absent',
+ $mod_security = true,
+ $mod_security_relevantonly = true,
+ $mod_security_rules_to_disable = [],
+ $mod_security_additional_options = 'absent',
+ $ssl_mode = false,
+ $vhost_mode = 'template',
+ $template_partial = 'apache/vhosts/php/partial.erb',
+ $vhost_source = 'absent',
+ $vhost_destination = 'absent',
+ $htpasswd_file = 'absent',
+ $htpasswd_path = 'absent',
+ $manage_config = true,
+ $config_webwriteable = false,
+ $manage_directories = true,
+){
+ $documentroot = $path ? {
+ 'absent' => $::operatingsystem ? {
+ openbsd => "/var/www/htdocs/${name}/www",
+ default => "/var/www/vhosts/${name}/www"
+ },
+ default => "${path}/www"
+ }
+
+ # create vhost configuration file
+ ::apache::vhost::php::webapp{$name:
+ ensure => $ensure,
+ configuration => $configuration,
+ domain => $domain,
+ domainalias => $domainalias,
+ server_admin => $server_admin,
+ logmode => $logmode,
+ path => $path,
+ owner => $owner,
+ group => $group,
+ documentroot_owner => $documentroot_owner,
+ documentroot_group => $documentroot_group,
+ documentroot_mode => $documentroot_mode,
+ run_mode => $run_mode,
+ run_uid => $run_uid,
+ run_gid => $run_gid,
+ allow_override => $allow_override,
+ php_settings => $php_settings,
+ php_options => $php_options,
+ do_includes => $do_includes,
+ options => $options,
+ additional_options => $additional_options,
+ default_charset => $default_charset,
+ mod_security => $mod_security,
+ mod_security_relevantonly => $mod_security_relevantonly,
+ mod_security_rules_to_disable => $mod_security_rules_to_disable,
+ mod_security_additional_options => $mod_security_additional_options,
+ ssl_mode => $ssl_mode,
+ vhost_mode => $vhost_mode,
+ template_partial => $template_partial,
+ vhost_source => $vhost_source,
+ vhost_destination => $vhost_destination,
+ htpasswd_file => $htpasswd_file,
+ htpasswd_path => $htpasswd_path,
+ manage_directories => $manage_directories,
+ managed_directories => [
+ "${documentroot}/agreement.txt",
+ "${documentroot}/attachments",
+ "${documentroot}/avatars",
+ "${documentroot}/cache",
+ "${documentroot}/Packages",
+ "${documentroot}/Packages/installed.list",
+ "${documentroot}/Smileys",
+ "${documentroot}/Themes",
+ "${documentroot}/Themes/default/languages/Install.english.php"
+ ],
+ manage_config => $manage_config,
+ config_webwriteable => $config_webwriteable,
+ config_file => 'Settings.php',
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/vhost/php/spip.pp b/puppet/modules/apache/manifests/vhost/php/spip.pp
new file mode 100644
index 00000000..e33c1dfe
--- /dev/null
+++ b/puppet/modules/apache/manifests/vhost/php/spip.pp
@@ -0,0 +1,114 @@
+# run_mode: controls in which mode the vhost should be run, there are different setups
+# possible:
+# - normal: (*default*) run vhost with the current active worker (default: prefork) don't
+# setup anything special
+# - itk: run vhost with the mpm_itk module (Incompatibility: cannot be used in combination
+# with 'proxy-itk' & 'static-itk' mode)
+# - proxy-itk: run vhost with a dual prefork/itk setup, where prefork just proxies all the
+# requests for the itk setup, that listens only on the loobpack device.
+# (Incompatibility: cannot be used in combination with the itk setup.)
+# - static-itk: run vhost with a dual prefork/itk setup, where prefork serves all the static
+# content and proxies the dynamic calls to the itk setup, that listens only on
+# the loobpack device (Incompatibility: cannot be used in combination with
+# 'itk' mode)
+#
+# run_uid: the uid the vhost should run as with the itk module
+# run_gid: the gid the vhost should run as with the itk module
+#
+# mod_security: Whether we use mod_security or not (will include mod_security module)
+# - false: don't activate mod_security
+# - true: (*default*) activate mod_security
+#
+# logmode:
+# - default: Do normal logging to CustomLog and ErrorLog
+# - nologs: Send every logging to /dev/null
+# - anonym: Don't log ips for CustomLog, send ErrorLog to /dev/null
+# - semianonym: Don't log ips for CustomLog, log normal ErrorLog
+define apache::vhost::php::spip(
+ $ensure = present,
+ $configuration = {},
+ $domain = 'absent',
+ $domainalias = 'absent',
+ $server_admin = 'absent',
+ $logmode = 'default',
+ $path = 'absent',
+ $owner = root,
+ $group = apache,
+ $documentroot_owner = apache,
+ $documentroot_group = 0,
+ $documentroot_mode = '0640',
+ $run_mode = 'normal',
+ $run_uid = 'absent',
+ $run_gid = 'absent',
+ $allow_override = 'FileInfo',
+ $php_settings = {},
+ $php_options = {},
+ $template_partial = 'apache/vhosts/php/partial.erb',
+ $do_includes = false,
+ $options = 'absent',
+ $additional_options = 'absent',
+ $default_charset = 'absent',
+ $mod_security = true,
+ $mod_security_relevantonly = true,
+ $mod_security_rules_to_disable = [],
+ $mod_security_additional_options = 'absent',
+ $ssl_mode = false,
+ $vhost_mode = 'template',
+ $vhost_source = 'absent',
+ $vhost_destination = 'absent',
+ $htpasswd_file = 'absent',
+ $htpasswd_path = 'absent'
+){
+ $documentroot = $path ? {
+ 'absent' => $::operatingsystem ? {
+ openbsd => "/var/www/htdocs/${name}/www",
+ default => "/var/www/vhosts/${name}/www"
+ },
+ default => "${path}/www"
+ }
+
+ # create vhost configuration file
+ ::apache::vhost::php::webapp{$name:
+ ensure => $ensure,
+ configuration => $configuration,
+ domain => $domain,
+ domainalias => $domainalias,
+ server_admin => $server_admin,
+ logmode => $logmode,
+ path => $path,
+ owner => $owner,
+ group => $group,
+ documentroot_owner => $documentroot_owner,
+ documentroot_group => $documentroot_group,
+ documentroot_mode => $documentroot_mode,
+ run_mode => $run_mode,
+ run_uid => $run_uid,
+ run_gid => $run_gid,
+ allow_override => $allow_override,
+ php_settings => $php_settings,
+ php_options => $php_options,
+ do_includes => $do_includes,
+ options => $options,
+ additional_options => $additional_options,
+ default_charset => $default_charset,
+ mod_security => $mod_security,
+ mod_security_relevantonly => $mod_security_relevantonly,
+ mod_security_rules_to_disable => $mod_security_rules_to_disable,
+ mod_security_additional_options => $mod_security_additional_options,
+ ssl_mode => $ssl_mode,
+ vhost_mode => $vhost_mode,
+ template_partial => $template_partial,
+ vhost_source => $vhost_source,
+ vhost_destination => $vhost_destination,
+ htpasswd_file => $htpasswd_file,
+ htpasswd_path => $htpasswd_path,
+ managed_directories => [
+ "${documentroot}/IMG",
+ "${documentroot}/tmp",
+ "${documentroot}/local",
+ "${documentroot}/config"
+ ],
+ manage_config => false,
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/vhost/php/standard.pp b/puppet/modules/apache/manifests/vhost/php/standard.pp
new file mode 100644
index 00000000..3870707a
--- /dev/null
+++ b/puppet/modules/apache/manifests/vhost/php/standard.pp
@@ -0,0 +1,304 @@
+# run_mode: controls in which mode the vhost should be run, there are different setups
+# possible:
+# - normal: (*default*) run vhost with the current active worker (default: prefork) don't
+# setup anything special
+# - itk: run vhost with the mpm_itk module (Incompatibility: cannot be used in combination
+# with 'proxy-itk' & 'static-itk' mode)
+# - proxy-itk: run vhost with a dual prefork/itk setup, where prefork just proxies all the
+# requests for the itk setup, that listens only on the loobpack device.
+# (Incompatibility: cannot be used in combination with the itk setup.)
+# - static-itk: run vhost with a dual prefork/itk setup, where prefork serves all the static
+# content and proxies the dynamic calls to the itk setup, that listens only on
+# the loobpack device (Incompatibility: cannot be used in combination with
+# 'itk' mode)
+#
+# run_uid: the uid the vhost should run as with the itk module
+# run_gid: the gid the vhost should run as with the itk module
+#
+# mod_security: Whether we use mod_security or not (will include mod_security module)
+# - false: don't activate mod_security
+# - true: (*default*) activate mod_security
+#
+# logmode:
+# - default: Do normal logging to CustomLog and ErrorLog
+# - nologs: Send every logging to /dev/null
+# - anonym: Don't log ips for CustomLog, send ErrorLog to /dev/null
+# - semianonym: Don't log ips for CustomLog, log normal ErrorLog
+define apache::vhost::php::standard(
+ $ensure = present,
+ $configuration = {},
+ $domain = 'absent',
+ $domainalias = 'absent',
+ $server_admin = 'absent',
+ $logmode = 'default',
+ $logpath = 'absent',
+ $logprefix = '',
+ $path = 'absent',
+ $manage_webdir = true,
+ $path_is_webdir = false,
+ $manage_docroot = true,
+ $owner = root,
+ $group = apache,
+ $documentroot_owner = apache,
+ $documentroot_group = 0,
+ $documentroot_mode = 0640,
+ $run_mode = 'normal',
+ $run_uid = 'absent',
+ $run_gid = 'absent',
+ $allow_override = 'None',
+ $php_settings = {},
+ $php_options = {},
+ $php_installation = 'system',
+ $do_includes = false,
+ $options = 'absent',
+ $additional_options = 'absent',
+ $default_charset = 'absent',
+ $use_mod_macro = false,
+ $mod_security = true,
+ $mod_security_relevantonly = true,
+ $mod_security_rules_to_disable = [],
+ $mod_security_additional_options = 'absent',
+ $ssl_mode = false,
+ $vhost_mode = 'template',
+ $template_partial = 'apache/vhosts/php/partial.erb',
+ $vhost_source = 'absent',
+ $vhost_destination = 'absent',
+ $htpasswd_file = 'absent',
+ $htpasswd_path = 'absent',
+){
+
+ if $manage_webdir {
+ # create webdir
+ ::apache::vhost::webdir{$name:
+ ensure => $ensure,
+ path => $path,
+ owner => $owner,
+ group => $group,
+ run_mode => $run_mode,
+ manage_docroot => $manage_docroot,
+ documentroot_owner => $documentroot_owner,
+ documentroot_group => $documentroot_group,
+ documentroot_mode => $documentroot_mode,
+ }
+ }
+
+ $real_path = $path ? {
+ 'absent' => $::operatingsystem ? {
+ openbsd => "/var/www/htdocs/${name}",
+ default => "/var/www/vhosts/${name}"
+ },
+ default => $path
+ }
+
+ if $path_is_webdir {
+ $documentroot = $real_path
+ } else {
+ $documentroot = "${real_path}/www"
+ }
+ $logdir = $logpath ? {
+ 'absent' => "${real_path}/logs",
+ default => $logpath
+ }
+
+ $std_php_options = {
+ smarty => false,
+ pear => false,
+ }
+ $real_php_options = merge($std_php_options,$php_options)
+
+ if $real_php_options[smarty] {
+ include php::extensions::smarty
+ $smarty_path = '/usr/share/php/Smarty/:'
+ } else {
+ $smarty_path = ''
+ }
+
+ if $real_php_options[pear] {
+ $pear_path = '/usr/share/pear/:'
+ } else {
+ $pear_path = ''
+ }
+
+ if $logmode != 'nologs' {
+ $php_error_log = "${logdir}/php_error_log"
+ } else {
+ $php_error_log = undef
+ }
+
+ if ('safe_mode_exec_dir' in $php_settings) {
+ $php_safe_mode_exec_dir = $php_settings[safe_mode_exec_dir]
+ } else {
+ $php_safe_mode_exec_dir = $path ? {
+ 'absent' => $::operatingsystem ? {
+ openbsd => "/var/www/htdocs/${name}/bin",
+ default => "/var/www/vhosts/${name}/bin"
+ },
+ default => "${path}/bin"
+ }
+ }
+ file{$php_safe_mode_exec_dir:
+ recurse => true,
+ force => true,
+ purge => true,
+ }
+ if ('safe_mode_exec_bins' in $php_options) {
+ $std_php_settings_safe_mode_exec_dir = $php_safe_mode_exec_dir
+ $ensure_exec = $ensure ? {
+ 'present' => directory,
+ default => 'absent',
+ }
+ File[$php_safe_mode_exec_dir]{
+ ensure => $ensure_exec,
+ owner => $documentroot_owner,
+ group => $documentroot_group,
+ mode => '0750',
+ }
+ $php_safe_mode_exec_bins_subst = regsubst($php_options[safe_mode_exec_bins],'(.+)',"${name}@\\1")
+ apache::vhost::php::safe_mode_bin{
+ $php_safe_mode_exec_bins_subst:
+ ensure => $ensure,
+ path => $php_safe_mode_exec_dir;
+ }
+ } else {
+ $std_php_settings_safe_mode_exec_dir = undef
+ File[$php_safe_mode_exec_dir]{
+ ensure => absent,
+ }
+ }
+
+ if !('default_charset' in $php_settings) and ($default_charset != 'absent') {
+ $std_php_settings_default_charset = $default_charset ? {
+ 'On' => 'iso-8859-1',
+ default => $default_charset
+ }
+ } else {
+ $std_php_settings_default_charset = undef
+ }
+
+ if ('additional_open_basedir' in $php_options) {
+ $the_open_basedir = "${smarty_path}${pear_path}${documentroot}:${real_path}/data:/var/www/upload_tmp_dir/${name}:/var/www/session.save_path/${name}:${php_options[additional_open_basedir]}"
+ } else {
+ $the_open_basedir = "${smarty_path}${pear_path}${documentroot}:${real_path}/data:/var/www/upload_tmp_dir/${name}:/var/www/session.save_path/${name}"
+ }
+
+ if $run_mode == 'fcgid' {
+ $safe_mode_gid = $::operatingsystem ? {
+ debian => undef,
+ default => $php_installation ? {
+ 'system' => 'On',
+ default => undef,
+ }
+ }
+ } else {
+ $safe_mode_gid = undef
+ }
+
+ $safe_mode = $::operatingsystem ? {
+ debian => undef,
+ default => $php_installation ? {
+ 'system' => 'On',
+ default => undef,
+ }
+ }
+ $std_php_settings = {
+ engine => 'On',
+ upload_tmp_dir => "/var/www/upload_tmp_dir/${name}",
+ 'session.save_path' => "/var/www/session.save_path/${name}",
+ error_log => $php_error_log,
+ safe_mode => $safe_mode,
+ safe_mode_gid => $safe_mode_gid,
+ safe_mode_exec_dir => $std_php_settings_safe_mode_exec_dir,
+ default_charset => $std_php_settings_default_charset,
+ open_basedir => $the_open_basedir,
+ }
+
+ $real_php_settings = merge($std_php_settings,$php_settings)
+
+ if $ensure != 'absent' {
+ case $run_mode {
+ 'proxy-itk','static-itk': {
+ include ::php::itk_plus
+ }
+ 'itk': { include ::php::itk }
+ 'fcgid': {
+ include ::mod_fcgid
+ include ::php::mod_fcgid
+ include apache::include::mod_fcgid
+
+ mod_fcgid::starter {$name:
+ tmp_dir => $real_php_settings[php_tmp_dir],
+ cgi_type => 'php',
+ cgi_type_options => delete($real_php_settings, php_tmp_dir),
+ owner => $run_uid,
+ group => $run_gid,
+ notify => Service['apache'],
+ }
+ if $php_installation == 'scl54' {
+ require php::scl::php54
+ Mod_fcgid::Starter[$name]{
+ binary => '/opt/rh/php54/root/usr/bin/php-cgi',
+ additional_cmds => 'source /opt/rh/php54/enable',
+ rc => '/opt/rh/php54/root/etc',
+ }
+ } elsif $php_installation == 'scl55' {
+ require php::scl::php55
+ Mod_fcgid::Starter[$name]{
+ binary => '/opt/rh/php55/root/usr/bin/php-cgi',
+ additional_cmds => 'source /opt/rh/php55/enable',
+ rc => '/opt/rh/php55/root/etc',
+ }
+ }
+ }
+ default: { include ::php }
+ }
+ }
+
+ ::apache::vhost::phpdirs{$name:
+ ensure => $ensure,
+ php_upload_tmp_dir => $real_php_settings[upload_tmp_dir],
+ php_session_save_path => $real_php_settings['session.save_path'],
+ documentroot_owner => $documentroot_owner,
+ documentroot_group => $documentroot_group,
+ documentroot_mode => $documentroot_mode,
+ run_mode => $run_mode,
+ run_uid => $run_uid,
+ }
+
+ # create vhost configuration file
+ ::apache::vhost{$name:
+ ensure => $ensure,
+ configuration => $configuration,
+ path => $path,
+ path_is_webdir => $path_is_webdir,
+ vhost_mode => $vhost_mode,
+ template_partial => $template_partial,
+ vhost_source => $vhost_source,
+ vhost_destination => $vhost_destination,
+ domain => $domain,
+ domainalias => $domainalias,
+ server_admin => $server_admin,
+ logmode => $logmode,
+ logpath => $logpath,
+ logprefix => $logprefix,
+ run_mode => $run_mode,
+ run_uid => $run_uid,
+ run_gid => $run_gid,
+ allow_override => $allow_override,
+ do_includes => $do_includes,
+ options => $options,
+ additional_options => $additional_options,
+ default_charset => $default_charset,
+ php_settings => $real_php_settings,
+ php_options => $real_php_options,
+ ssl_mode => $ssl_mode,
+ htpasswd_file => $htpasswd_file,
+ htpasswd_path => $htpasswd_path,
+ mod_security => $mod_security,
+ mod_security_relevantonly => $mod_security_relevantonly,
+ mod_security_rules_to_disable => $mod_security_rules_to_disable,
+ mod_security_additional_options => $mod_security_additional_options,
+ use_mod_macro => $use_mod_macro,
+ passing_extension => 'php',
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/vhost/php/typo3.pp b/puppet/modules/apache/manifests/vhost/php/typo3.pp
new file mode 100644
index 00000000..d9e877a6
--- /dev/null
+++ b/puppet/modules/apache/manifests/vhost/php/typo3.pp
@@ -0,0 +1,150 @@
+# run_mode: controls in which mode the vhost should be run, there are different setups
+# possible:
+# - normal: (*default*) run vhost with the current active worker (default: prefork) don't
+# setup anything special
+# - itk: run vhost with the mpm_itk module (Incompatibility: cannot be used in combination
+# with 'proxy-itk' & 'static-itk' mode)
+# - proxy-itk: run vhost with a dual prefork/itk setup, where prefork just proxies all the
+# requests for the itk setup, that listens only on the loobpack device.
+# (Incompatibility: cannot be used in combination with the itk setup.)
+# - static-itk: run vhost with a dual prefork/itk setup, where prefork serves all the static
+# content and proxies the dynamic calls to the itk setup, that listens only on
+# the loobpack device (Incompatibility: cannot be used in combination with
+# 'itk' mode)
+#
+# run_uid: the uid the vhost should run as with the itk module
+# run_gid: the gid the vhost should run as with the itk module
+#
+# mod_security: Whether we use mod_security or not (will include mod_security module)
+# - false: don't activate mod_security
+# - true: (*default*) activate mod_security
+#
+# logmode:
+# - default: Do normal logging to CustomLog and ErrorLog
+# - nologs: Send every logging to /dev/null
+# - anonym: Don't log ips for CustomLog, send ErrorLog to /dev/null
+# - semianonym: Don't log ips for CustomLog, log normal ErrorLog
+define apache::vhost::php::typo3(
+ $ensure = present,
+ $configuration = {},
+ $domain = 'absent',
+ $domainalias = 'absent',
+ $server_admin = 'absent',
+ $logmode = 'default',
+ $path = 'absent',
+ $owner = root,
+ $group = apache,
+ $documentroot_owner = apache,
+ $documentroot_group = 0,
+ $documentroot_mode = '0640',
+ $run_mode = 'normal',
+ $run_uid = 'absent',
+ $run_gid = 'absent',
+ $allow_override = 'None',
+ $php_settings = {},
+ $php_options = {},
+ $do_includes = false,
+ $options = 'absent',
+ $additional_options = 'absent',
+ $default_charset = 'absent',
+ $mod_security = true,
+ $mod_security_relevantonly = true,
+ $mod_security_rules_to_disable = [],
+ $mod_security_additional_options = 'absent',
+ $ssl_mode = false,
+ $vhost_mode = 'template',
+ $template_partial = 'apache/vhosts/php_typo3/partial.erb',
+ $vhost_source = 'absent',
+ $vhost_destination = 'absent',
+ $htpasswd_file = 'absent',
+ $htpasswd_path = 'absent',
+ $manage_config = true,
+ $config_webwriteable = false,
+ $manage_directories = true,
+){
+ $documentroot = $path ? {
+ 'absent' => $::operatingsystem ? {
+ openbsd => "/var/www/htdocs/${name}/www",
+ default => "/var/www/vhosts/${name}/www"
+ },
+ default => "${path}/www"
+ }
+
+ $modsec_rules = ['960010']
+ $real_mod_security_rules_to_disable = union($mod_security_rules_to_disable,$modsec_rules)
+ if $mod_security_additional_options == 'absent' {
+ $real_mod_security_additional_options = '
+ <Location "/typo3">
+ SecRuleEngine Off
+ SecAuditEngine Off
+ </Location>
+'
+ } else {
+ $real_mod_security_additional_options = $mod_security_additional_options
+ }
+
+ $typo3_php_settings = {
+ # turn allow_url_fopen on for the extension manager fetch
+ allow_url_fopen => 'On'
+ }
+ $real_php_settings = merge($typo3_php_settings,$php_settings)
+
+ # create vhost configuration file
+ ::apache::vhost::php::webapp{$name:
+ ensure => $ensure,
+ configuration => $configuration,
+ domain => $domain,
+ domainalias => $domainalias,
+ server_admin => $server_admin,
+ logmode => $logmode,
+ path => $path,
+ owner => $owner,
+ group => $group,
+ documentroot_owner => $documentroot_owner,
+ documentroot_group => $documentroot_group,
+ documentroot_mode => $documentroot_mode,
+ run_mode => $run_mode,
+ run_uid => $run_uid,
+ run_gid => $run_gid,
+ allow_override => $allow_override,
+ php_settings => $real_php_settings,
+ php_options => $php_options,
+ do_includes => $do_includes,
+ options => $options,
+ additional_options => $additional_options,
+ default_charset => $default_charset,
+ mod_security => $mod_security,
+ mod_security_relevantonly => $mod_security_relevantonly,
+ mod_security_rules_to_disable => $real_mod_security_rules_to_disable,
+ mod_security_additional_options => $real_mod_security_additional_options,
+ ssl_mode => $ssl_mode,
+ vhost_mode => $vhost_mode,
+ template_partial => $template_partial,
+ vhost_source => $vhost_source,
+ vhost_destination => $vhost_destination,
+ htpasswd_file => $htpasswd_file,
+ htpasswd_path => $htpasswd_path,
+ manage_directories => $manage_directories,
+ managed_directories => [ "${documentroot}/typo3temp",
+ "${documentroot}/typo3temp/pics",
+ "${documentroot}/typo3temp/temp",
+ "${documentroot}/typo3temp/llxml",
+ "${documentroot}/typo3temp/cs",
+ "${documentroot}/typo3temp/GB",
+ "${documentroot}/typo3temp/locks",
+ "${documentroot}/typo3conf",
+ "${documentroot}/typo3conf/ext",
+ "${documentroot}/typo3conf/l10n",
+ # "${documentroot}/typo3/ext/", # only needed for ext manager installing global extensions
+ "${documentroot}/uploads",
+ "${documentroot}/uploads/pics",
+ "${documentroot}/uploads/media",
+ "${documentroot}/uploads/tf",
+ "${documentroot}/fileadmin",
+ "${documentroot}/fileadmin/_temp_"
+ ],
+ manage_config => $manage_config,
+ }
+
+}
+
diff --git a/puppet/modules/apache/manifests/vhost/php/webapp.pp b/puppet/modules/apache/manifests/vhost/php/webapp.pp
new file mode 100644
index 00000000..695120d0
--- /dev/null
+++ b/puppet/modules/apache/manifests/vhost/php/webapp.pp
@@ -0,0 +1,148 @@
+# run_mode: controls in which mode the vhost should be run, there are different setups
+# possible:
+# - normal: (*default*) run vhost with the current active worker (default: prefork) don't
+# setup anything special
+# - itk: run vhost with the mpm_itk module (Incompatibility: cannot be used in combination
+# with 'proxy-itk' & 'static-itk' mode)
+# - proxy-itk: run vhost with a dual prefork/itk setup, where prefork just proxies all the
+# requests for the itk setup, that listens only on the loobpack device.
+# (Incompatibility: cannot be used in combination with the itk setup.)
+# - static-itk: run vhost with a dual prefork/itk setup, where prefork serves all the static
+# content and proxies the dynamic calls to the itk setup, that listens only on
+# the loobpack device (Incompatibility: cannot be used in combination with
+# 'itk' mode)
+#
+# run_uid: the uid the vhost should run as with the itk module
+# run_gid: the gid the vhost should run as with the itk module
+#
+# mod_security: Whether we use mod_security or not (will include mod_security module)
+# - false: don't activate mod_security
+# - true: (*default*) activate mod_security
+#
+# logmode:
+# - default: Do normal logging to CustomLog and ErrorLog
+# - nologs: Send every logging to /dev/null
+# - anonym: Don't log ips for CustomLog, send ErrorLog to /dev/null
+# - semianonym: Don't log ips for CustomLog, log normal ErrorLog
+define apache::vhost::php::webapp(
+ $ensure = present,
+ $configuration = {},
+ $domain = 'absent',
+ $domainalias = 'absent',
+ $server_admin = 'absent',
+ $logmode = 'default',
+ $path = 'absent',
+ $manage_webdir = true,
+ $manage_docroot = true,
+ $owner = root,
+ $group = apache,
+ $documentroot_owner = apache,
+ $documentroot_group = 0,
+ $documentroot_mode = '0640',
+ $run_mode = 'normal',
+ $run_uid = 'absent',
+ $run_gid = 'absent',
+ $allow_override = 'None',
+ $php_settings = {},
+ $php_options = {},
+ $php_installation = 'system',
+ $do_includes = false,
+ $options = 'absent',
+ $additional_options = 'absent',
+ $default_charset = 'absent',
+ $mod_security = true,
+ $mod_security_relevantonly = true,
+ $mod_security_rules_to_disable = [],
+ $mod_security_additional_options = 'absent',
+ $ssl_mode = false,
+ $vhost_mode = 'template',
+ $template_partial,
+ $vhost_source = 'absent',
+ $vhost_destination = 'absent',
+ $htpasswd_file = 'absent',
+ $htpasswd_path = 'absent',
+ $manage_config = true,
+ $config_file = 'absent',
+ $config_webwriteable = false,
+ $manage_directories = true,
+ $managed_directories = 'absent',
+){
+ if ($ensure != 'absent') {
+ if $manage_directories and ($managed_directories != 'absent') {
+ ::apache::file::rw{ $managed_directories :
+ owner => $documentroot_owner,
+ group => $documentroot_group,
+ }
+ }
+
+ if $manage_config {
+ if $config_file == 'absent' { fail("No config file defined for ${name} on ${::fqdn}, if you'd like to manage the config, you have to add one!") }
+
+ $real_path = $path ? {
+ 'absent' => $::operatingsystem ? {
+ openbsd => "/var/www/htdocs/${name}",
+ default => "/var/www/vhosts/${name}"
+ },
+ default => $path
+ }
+ $documentroot = "${real_path}/www"
+ ::apache::vhost::file::documentrootfile{"configurationfile_${name}":
+ documentroot => $documentroot,
+ filename => $config_file,
+ thedomain => $name,
+ owner => $documentroot_owner,
+ group => $documentroot_group,
+ }
+ if $config_webwriteable {
+ Apache::Vhost::File::Documentrootfile["configurationfile_${name}"]{
+ mode => '0660',
+ }
+ } else {
+ Apache::Vhost::File::Documentrootfile["configurationfile_${name}"]{
+ mode => '0440',
+ }
+ }
+ }
+ }
+
+ # create vhost configuration file
+ ::apache::vhost::php::standard{$name:
+ ensure => $ensure,
+ configuration => $configuration,
+ domain => $domain,
+ domainalias => $domainalias,
+ server_admin => $server_admin,
+ logmode => $logmode,
+ path => $path,
+ manage_webdir => $manage_webdir,
+ manage_docroot => $manage_docroot,
+ owner => $owner,
+ group => $group,
+ documentroot_owner => $documentroot_owner,
+ documentroot_group => $documentroot_group,
+ documentroot_mode => $documentroot_mode,
+ run_mode => $run_mode,
+ run_uid => $run_uid,
+ run_gid => $run_gid,
+ allow_override => $allow_override,
+ php_settings => $php_settings,
+ php_options => $php_options,
+ php_installation => $php_installation,
+ do_includes => $do_includes,
+ options => $options,
+ additional_options => $additional_options,
+ default_charset => $default_charset,
+ mod_security => $mod_security,
+ mod_security_relevantonly => $mod_security_relevantonly,
+ mod_security_rules_to_disable => $mod_security_rules_to_disable,
+ mod_security_additional_options => $mod_security_additional_options,
+ ssl_mode => $ssl_mode,
+ vhost_mode => $vhost_mode,
+ template_partial => $template_partial,
+ vhost_source => $vhost_source,
+ vhost_destination => $vhost_destination,
+ htpasswd_file => $htpasswd_file,
+ htpasswd_path => $htpasswd_path,
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/vhost/php/wordpress.pp b/puppet/modules/apache/manifests/vhost/php/wordpress.pp
new file mode 100644
index 00000000..a6bbe434
--- /dev/null
+++ b/puppet/modules/apache/manifests/vhost/php/wordpress.pp
@@ -0,0 +1,123 @@
+# run_mode: controls in which mode the vhost should be run, there are different
+# setups # possible:
+# - normal: (*default*) run vhost with the current active worker
+# (default: prefork) don't setup anything special
+# - itk: run vhost with the mpm_itk module (Incompatibility: cannot be used in
+# combination with 'proxy-itk' & 'static-itk' mode)
+# - proxy-itk: run vhost with a dual prefork/itk setup, where prefork just
+# proxies all the requests for the itk setup, that listens only
+# on the loobpack device.
+# (Incompatibility: cannot be used in combination with the itk
+# setup.)
+# - static-itk: run vhost with a dual prefork/itk setup, where prefork serves
+# all the static content and proxies the dynamic calls to the
+# itk setup, that listens only on the loobpack device
+# (Incompatibility: cannot be used in combination with
+# 'itk' mode)
+#
+# run_uid: the uid the vhost should run as with the itk module
+# run_gid: the gid the vhost should run as with the itk module
+#
+# mod_security: Whether we use mod_security or not (will include mod_security
+# module)
+# - false: don't activate mod_security
+# - true: (*default*) activate mod_security
+#
+# logmode:
+# - default: Do normal logging to CustomLog and ErrorLog
+# - nologs: Send every logging to /dev/null
+# - anonym: Don't log ips for CustomLog, send ErrorLog to /dev/null
+# - semianonym: Don't log ips for CustomLog, log normal ErrorLog
+define apache::vhost::php::wordpress(
+ $ensure = present,
+ $configuration = {},
+ $domain = 'absent',
+ $domainalias = 'absent',
+ $server_admin = 'absent',
+ $logmode = 'default',
+ $path = 'absent',
+ $owner = root,
+ $group = apache,
+ $documentroot_owner = apache,
+ $documentroot_group = 0,
+ $documentroot_mode = '0640',
+ $run_mode = 'normal',
+ $run_uid = 'absent',
+ $run_gid = 'absent',
+ $allow_override = 'FileInfo Indexes',
+ $php_settings = {},
+ $php_options = {},
+ $do_includes = false,
+ $options = 'absent',
+ $additional_options = 'absent',
+ $default_charset = 'absent',
+ $mod_security = true,
+ $mod_security_relevantonly = true,
+ $mod_security_rules_to_disable = [],
+ $mod_security_additional_options = 'absent',
+ $ssl_mode = false,
+ $vhost_mode = 'template',
+ $template_partial = 'apache/vhosts/php_wordpress/partial.erb',
+ $vhost_source = 'absent',
+ $vhost_destination = 'absent',
+ $htpasswd_file = 'absent',
+ $htpasswd_path = 'absent',
+ $manage_config = true,
+ $config_webwriteable = false,
+ $manage_directories = true
+){
+
+ $documentroot = $path ? {
+ 'absent' => $::operatingsystem ? {
+ 'openbsd' => "/var/www/htdocs/${name}/www",
+ default => "/var/www/vhosts/${name}/www"
+ },
+ default => "${path}/www"
+ }
+ $modsec_rules = ['960010', '950018']
+ $real_mod_security_rules_to_disable = union($mod_security_rules_to_disable,
+ $modsec_rules)
+
+ # create vhost configuration file
+ apache::vhost::php::webapp{$name:
+ ensure => $ensure,
+ configuration => $configuration,
+ domain => $domain,
+ domainalias => $domainalias,
+ server_admin => $server_admin,
+ logmode => $logmode,
+ path => $path,
+ owner => $owner,
+ group => $group,
+ documentroot_owner => $documentroot_owner,
+ documentroot_group => $documentroot_group,
+ documentroot_mode => $documentroot_mode,
+ run_mode => $run_mode,
+ run_uid => $run_uid,
+ run_gid => $run_gid,
+ allow_override => $allow_override,
+ php_settings => $php_settings,
+ php_options => $php_options,
+ do_includes => $do_includes,
+ options => $options,
+ additional_options => $additional_options,
+ default_charset => $default_charset,
+ mod_security => $mod_security,
+ mod_security_relevantonly => $mod_security_relevantonly,
+ mod_security_rules_to_disable => $real_mod_security_rules_to_disable,
+ mod_security_additional_options => $mod_security_additional_options,
+ ssl_mode => $ssl_mode,
+ vhost_mode => $vhost_mode,
+ template_partial => $template_partial,
+ vhost_source => $vhost_source,
+ vhost_destination => $vhost_destination,
+ htpasswd_file => $htpasswd_file,
+ htpasswd_path => $htpasswd_path,
+ manage_directories => $manage_directories,
+ managed_directories => [ "${documentroot}/wp-content/uploads",],
+ manage_config => $manage_config,
+ config_webwriteable => $config_webwriteable,
+ config_file => 'wp-config.php',
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/vhost/phpdirs.pp b/puppet/modules/apache/manifests/vhost/phpdirs.pp
new file mode 100644
index 00000000..5936da61
--- /dev/null
+++ b/puppet/modules/apache/manifests/vhost/phpdirs.pp
@@ -0,0 +1,39 @@
+define apache::vhost::phpdirs(
+ $ensure = present,
+ $php_upload_tmp_dir,
+ $php_session_save_path,
+ $documentroot_owner = apache,
+ $documentroot_group = 0,
+ $documentroot_mode = 0750,
+ $run_mode = 'normal',
+ $run_uid = 'absent'
+){
+ case $ensure {
+ absent : {
+ file {
+ [$php_upload_tmp_dir, $php_session_save_path] :
+ ensure => absent,
+ purge => true,
+ force => true,
+ recurse => true,
+ }
+ }
+ default : {
+ include apache::defaultphpdirs
+ file {
+ [$php_upload_tmp_dir, $php_session_save_path] :
+ ensure => directory,
+ owner => $run_mode ? {
+ 'itk' => $run_uid,
+ 'static-itk' => $run_uid,
+ 'proxy-itk' => $run_uid,
+ 'fcgid' => $run_uid,
+ default => $documentroot_owner
+ },
+ group => $documentroot_group,
+ mode => $documentroot_mode ;
+ }
+ }
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/vhost/proxy.pp b/puppet/modules/apache/manifests/vhost/proxy.pp
new file mode 100644
index 00000000..95ae2059
--- /dev/null
+++ b/puppet/modules/apache/manifests/vhost/proxy.pp
@@ -0,0 +1,67 @@
+# Proxy VHost
+# Parameters:
+#
+# - ensure: wether this vhost is `present` or `absent`
+# - domain: the domain to redirect (*name*)
+# - domainalias: A list of whitespace seperated domains to redirect
+# - target_url: the url to be proxied. Note: We don't want http://example.com/foobar only example.com/foobar
+# - server_admin: the email that is shown as responsible
+# - ssl_mode: wether this vhost supports ssl or not
+# - false: don't enable ssl for this vhost (default)
+# - true: enable ssl for this vhost
+# - force: enable ssl and redirect non-ssl to ssl
+# - only: enable ssl only
+#
+# logmode:
+#
+# - default: Do normal logging to CustomLog and ErrorLog
+# - nologs: Send every logging to /dev/null
+# - anonym: Don't log ips for CustomLog, send ErrorLog to /dev/null
+# - semianonym: Don't log ips for CustomLog, log normal ErrorLog
+#
+define apache::vhost::proxy(
+ $ensure = present,
+ $configuration = {},
+ $domain = 'absent',
+ $domainalias = 'absent',
+ $htpasswd_file = 'absent',
+ $target_url,
+ $server_admin = 'absent',
+ $logmode = 'default',
+ $mod_security = false,
+ $ssl_mode = false,
+ $mod_security_relevantonly = true,
+ $mod_security_rules_to_disable = [],
+ $mod_security_additional_options = 'absent',
+ $additional_options = 'absent'
+){
+ # create vhost configuration file
+ # we use the options field as the target_url
+ ::apache::vhost::template{$name:
+ ensure => $ensure,
+ configuration => $configuration,
+ template_partial => 'apache/vhosts/proxy/partial.erb',
+ domain => $domain,
+ path => 'really_absent',
+ path_is_webdir => true,
+ htpasswd_file => $htpasswd_file,
+ domainalias => $domainalias,
+ server_admin => $server_admin,
+ logpath => $::operatingsystem ? {
+ openbsd => '/var/www/logs',
+ centos => '/var/log/httpd',
+ default => '/var/log/apache2'
+ },
+ logmode => $logmode,
+ allow_override => $allow_override,
+ run_mode => 'normal',
+ mod_security => $mod_security,
+ mod_security_relevantonly => $mod_security_relevantonly,
+ mod_security_rules_to_disable => $mod_security_rules_to_disable,
+ mod_security_additional_options => $mod_security_additional_options,
+ options => $target_url,
+ ssl_mode => $ssl_mode,
+ additional_options => $additional_options,
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/vhost/redirect.pp b/puppet/modules/apache/manifests/vhost/redirect.pp
new file mode 100644
index 00000000..0ac40cc3
--- /dev/null
+++ b/puppet/modules/apache/manifests/vhost/redirect.pp
@@ -0,0 +1,56 @@
+# Redirect VHost to redirect hosts
+# Parameters:
+#
+# - ensure: wether this vhost is `present` or `absent`
+# - domain: the domain to redirect (*name*)
+# - domainalias: A list of whitespace seperated domains to redirect
+# - target_url: the url to redirect to. Note: We don't want http://example.com/foobar only example.com/foobar
+# - server_admin: the email that is shown as responsible
+# - ssl_mode: wether this vhost supports ssl or not
+# - false: don't enable ssl for this vhost (default)
+# - true: enable ssl for this vhost
+# - force: enable ssl and redirect non-ssl to ssl
+# - only: enable ssl only
+#
+# logmode:
+#
+# - default: Do normal logging to CustomLog and ErrorLog
+# - nologs: Send every logging to /dev/null
+# - anonym: Don't log ips for CustomLog, send ErrorLog to /dev/null
+# - semianonym: Don't log ips for CustomLog, log normal ErrorLog
+#
+define apache::vhost::redirect(
+ $ensure = present,
+ $configuration = {},
+ $domain = 'absent',
+ $domainalias = 'absent',
+ $target_url,
+ $server_admin = 'absent',
+ $logmode = 'default',
+ $ssl_mode = false
+){
+ # create vhost configuration file
+ # we use the options field as the target_url
+ ::apache::vhost::template{$name:
+ ensure => $ensure,
+ configuration => $configuration,
+ template_partial => 'apache/vhosts/redirect/partial.erb',
+ domain => $domain,
+ path => 'really_absent',
+ path_is_webdir => true,
+ domainalias => $domainalias,
+ server_admin => $server_admin,
+ logpath => $::operatingsystem ? {
+ openbsd => '/var/www/logs',
+ centos => '/var/log/httpd',
+ default => '/var/log/apache2'
+ },
+ logmode => $logmode,
+ allow_override => $allow_override,
+ run_mode => 'normal',
+ mod_security => false,
+ options => $target_url,
+ ssl_mode => $ssl_mode,
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/vhost/static.pp b/puppet/modules/apache/manifests/vhost/static.pp
new file mode 100644
index 00000000..f9197662
--- /dev/null
+++ b/puppet/modules/apache/manifests/vhost/static.pp
@@ -0,0 +1,86 @@
+# vhost_mode: which option is chosen to deploy the vhost
+# - template: generate it from a template (default)
+# - file: deploy a vhost file (apache::vhost::file will be called directly)
+#
+# logmode:
+# - default: Do normal logging to CustomLog and ErrorLog
+# - nologs: Send every logging to /dev/null
+# - anonym: Don't log ips for CustomLog, send ErrorLog to /dev/null
+# - semianonym: Don't log ips for CustomLog, log normal ErrorLog
+#
+# mod_security: Whether we use mod_security or not (will include mod_security module)
+# - false: (*default*) don't activate mod_security
+# - true: activate mod_security
+#
+define apache::vhost::static(
+ $ensure = present,
+ $configuration = {},
+ $domain = 'absent',
+ $domainalias = 'absent',
+ $server_admin = 'absent',
+ $logmode = 'default',
+ $path = 'absent',
+ $owner = root,
+ $group = apache,
+ $documentroot_owner = apache,
+ $documentroot_group = 0,
+ $documentroot_mode = 0640,
+ $allow_override = 'None',
+ $do_includes = false,
+ $options = 'absent',
+ $additional_options = 'absent',
+ $default_charset = 'absent',
+ $ssl_mode = false,
+ $run_mode = 'normal',
+ $vhost_mode = 'template',
+ $template_partial = 'apache/vhosts/static/partial.erb',
+ $vhost_source = 'absent',
+ $vhost_destination = 'absent',
+ $htpasswd_file = 'absent',
+ $htpasswd_path = 'absent',
+ $mod_security = false,
+ $mod_security_relevantonly = true,
+ $mod_security_rules_to_disable = [],
+ $mod_security_additional_options = 'absent'
+){
+ # create webdir
+ ::apache::vhost::webdir{$name:
+ ensure => $ensure,
+ path => $path,
+ owner => $owner,
+ group => $group,
+ run_mode => $run_mode,
+ datadir => false,
+ documentroot_owner => $documentroot_owner,
+ documentroot_group => $documentroot_group,
+ documentroot_mode => $documentroot_mode,
+ }
+
+ # create vhost configuration file
+ ::apache::vhost{$name:
+ ensure => $ensure,
+ configuration => $configuration,
+ path => $path,
+ template_partial => $template_partial,
+ vhost_mode => $vhost_mode,
+ vhost_source => $vhost_source,
+ vhost_destination => $vhost_destination,
+ domain => $domain,
+ domainalias => $domainalias,
+ server_admin => $server_admin,
+ logmode => $logmode,
+ allow_override => $allow_override,
+ do_includes => $do_includes,
+ options => $options,
+ additional_options => $additional_options,
+ default_charset => $default_charset,
+ ssl_mode => $ssl_mode,
+ htpasswd_file => $htpasswd_file,
+ htpasswd_path => $htpasswd_path,
+ mod_security => $mod_security,
+ mod_security_relevantonly => $mod_security_relevantonly,
+ mod_security_rules_to_disable => $mod_security_rules_to_disable,
+ mod_security_additional_options => $mod_security_additional_options,
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/vhost/template.pp b/puppet/modules/apache/manifests/vhost/template.pp
new file mode 100644
index 00000000..8e9b798c
--- /dev/null
+++ b/puppet/modules/apache/manifests/vhost/template.pp
@@ -0,0 +1,158 @@
+# template_partial:
+# which template should be used to generate the type specific part
+# of the vhost entry.
+#
+# domainalias:
+# - absent: no domainalias is set (*default*)
+# - www: domainalias is set to www.$domain
+# - else: domainalias is set to that
+#
+# ssl_mode: wether this vhost supports ssl or not
+# - false: don't enable ssl for this vhost (default)
+# - true: enable ssl for this vhost
+# - force: enable ssl and redirect non-ssl to ssl
+# - only: enable ssl only
+#
+# logmode:
+# - default: Do normal logging to CustomLog and ErrorLog
+# - nologs: Send every logging to /dev/null
+# - anonym: Don't log ips for CustomLog, send ErrorLog to /dev/null
+# - semianonym: Don't log ips for CustomLog, log normal ErrorLog
+#
+# run_mode: controls in which mode the vhost should be run, there are different setups
+# possible:
+# - normal: (*default*) run vhost with the current active worker (default: prefork) don't
+# setup anything special
+# - itk: run vhost with the mpm_itk module (Incompatibility: cannot be used in combination
+# with 'proxy-itk' & 'static-itk' mode)
+# - proxy-itk: run vhost with a dual prefork/itk setup, where prefork just proxies all the
+# requests for the itk setup, that listens only on the loobpack device.
+# (Incompatibility: cannot be used in combination with the itk setup.)
+# - static-itk: run vhost with a dual prefork/itk setup, where prefork serves all the static
+# content and proxies the dynamic calls to the itk setup, that listens only on
+# the loobpack device (Incompatibility: cannot be used in combination with
+# 'itk' mode)
+#
+# run_uid: the uid the vhost should run as with the itk module
+# run_gid: the gid the vhost should run as with the itk module
+#
+# mod_security: Whether we use mod_security or not (will include mod_security module)
+# - false: don't activate mod_security
+# - true: (*default*) activate mod_security
+#
+define apache::vhost::template(
+ $ensure = present,
+ $configuration = {},
+ $path = 'absent',
+ $path_is_webdir = false,
+ $logpath = 'absent',
+ $logmode = 'default',
+ $logprefix = '',
+ $domain = 'absent',
+ $domainalias = 'absent',
+ $server_admin = 'absent',
+ $allow_override = 'None',
+ $dav_db_dir = 'absent',
+ $cgi_binpath = 'absent',
+ $do_includes = false,
+ $options = 'absent',
+ $additional_options = 'absent',
+ $default_charset = 'absent',
+ $php_options = {},
+ $php_settings = {},
+ $run_mode = 'normal',
+ $run_uid = 'absent',
+ $run_gid = 'absent',
+ $template_partial = 'apache/vhosts/static/partial.erb',
+ $template_vars = {},
+ $ssl_mode = false,
+ $mod_security = true,
+ $mod_security_relevantonly = true,
+ $mod_security_rules_to_disable = [],
+ $mod_security_additional_options = 'absent',
+ $use_mod_macro = false,
+ $htpasswd_file = 'absent',
+ $htpasswd_path = 'absent',
+ $ldap_auth = false,
+ $ldap_user = 'any',
+ $passing_extension = 'absent',
+ $gempath = 'absent'
+){
+ $real_path = $path ? {
+ 'absent' => $::operatingsystem ? {
+ openbsd => "/var/www/htdocs/${name}",
+ default => "/var/www/vhosts/${name}"
+ },
+ default => $path
+ }
+
+ if $path_is_webdir {
+ $documentroot = $real_path
+ } else {
+ $documentroot = "${real_path}/www"
+ }
+ $logdir = $logpath ? {
+ 'absent' => "${real_path}/logs",
+ default => $logpath
+ }
+
+ $servername = $domain ? {
+ 'absent' => $name,
+ default => $domain
+ }
+ $serveralias = $domainalias ? {
+ 'absent' => '',
+ 'www' => "www.${servername}",
+ default => $domainalias
+ }
+ if $htpasswd_path == 'absent' {
+ $real_htpasswd_path = "/var/www/htpasswds/${name}"
+ } else {
+ $real_htpasswd_path = $htpasswd_path
+ }
+ case $run_mode {
+ 'proxy-itk': { $logfileprefix = 'proxy' }
+ 'static-itk': { $logfileprefix = 'static' }
+ }
+ case $run_mode {
+ 'fcgid','itk','proxy-itk','static-itk': {
+ case $run_uid {
+ 'absent': { fail("you have to define run_uid for ${name} on ${::fqdn}") }
+ }
+ case $run_gid {
+ 'absent': { fail("you have to define run_gid for ${name} on ${::fqdn}") }
+ }
+ }
+ }
+
+ # dav db dir
+ case $dav_db_dir {
+ 'absent': {
+ $real_dav_db_dir = "/var/www/dav_db_dir/${name}"
+ }
+ default: { $real_dav_db_dir = $dav_db_dir }
+ }
+
+ apache::vhost::file{$name:
+ configuration => $configuration,
+ ensure => $ensure,
+ do_includes => $do_includes,
+ run_mode => $run_mode,
+ ssl_mode => $ssl_mode,
+ logmode => $logmode,
+ mod_security => $mod_security,
+ htpasswd_file => $htpasswd_file,
+ htpasswd_path => $htpasswd_path,
+ use_mod_macro => $use_mod_macro,
+ }
+ if $ensure != 'absent' {
+ Apache::Vhost::File[$name]{
+ content => $run_mode ? {
+ 'proxy-itk' => template('apache/vhosts/itk_plus.erb'),
+ 'static-itk' => template('apache/vhosts/itk_plus.erb'),
+ default => template('apache/vhosts/default.erb'),
+ }
+ }
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/vhost/webdav.pp b/puppet/modules/apache/manifests/vhost/webdav.pp
new file mode 100644
index 00000000..ff9e8abc
--- /dev/null
+++ b/puppet/modules/apache/manifests/vhost/webdav.pp
@@ -0,0 +1,126 @@
+# Webdav vhost: to manage webdav accessible targets
+# run_mode: controls in which mode the vhost should be run, there are different setups
+# possible:
+# - normal: (*default*) run vhost with the current active worker (default: prefork) don't
+# setup anything special
+# - itk: run vhost with the mpm_itk module (Incompatibility: cannot be used in combination
+# with 'proxy-itk' & 'static-itk' mode)
+# - proxy-itk: run vhost with a dual prefork/itk setup, where prefork just proxies all the
+# requests for the itk setup, that listens only on the loobpack device.
+# (Incompatibility: cannot be used in combination with the itk setup.)
+# - static-itk: this mode is not possible and will be rewritten to proxy-itk
+#
+# run_uid: the uid the vhost should run as with the itk module
+# run_gid: the gid the vhost should run as with the itk module
+#
+# mod_security: Whether we use mod_security or not (will include mod_security module)
+# - false: (*default*) don't activate mod_security
+# - true: activate mod_security
+#
+# logmode:
+# - default: Do normal logging to CustomLog and ErrorLog
+# - nologs: Send every logging to /dev/null
+# - anonym: Don't log ips for CustomLog, send ErrorLog to /dev/null
+# - semianonym: Don't log ips for CustomLog, log normal ErrorLog
+#
+define apache::vhost::webdav(
+ $ensure = present,
+ $configuration = {},
+ $domain = 'absent',
+ $domainalias = 'absent',
+ $server_admin = 'absent',
+ $path = 'absent',
+ $owner = root,
+ $group = apache,
+ $manage_webdir = true,
+ $path_is_webdir = false,
+ $logmode = 'default',
+ $logpath = 'absent',
+ $documentroot_owner = apache,
+ $documentroot_group = 0,
+ $documentroot_mode = 0640,
+ $run_mode = 'normal',
+ $run_uid = 'absent',
+ $run_gid = 'absent',
+ $options = 'absent',
+ $additional_options = 'absent',
+ $default_charset = 'absent',
+ $mod_security = false,
+ $mod_security_relevantonly = true,
+ $mod_security_rules_to_disable = [],
+ $mod_security_additional_options = 'absent',
+ $ssl_mode = false,
+ $vhost_mode = 'template',
+ $vhost_source = 'absent',
+ $vhost_destination = 'absent',
+ $htpasswd_file = 'absent',
+ $htpasswd_path = 'absent',
+ $ldap_auth = false,
+ $ldap_user = 'any',
+ $dav_db_dir = 'absent'
+){
+ ::apache::vhost::davdbdir{$name:
+ ensure => $ensure,
+ dav_db_dir => $dav_db_dir,
+ documentroot_owner => $documentroot_owner,
+ documentroot_group => $documentroot_group,
+ documentroot_mode => $documentroot_mode,
+ run_mode => $run_mode,
+ run_uid => $run_uid,
+ }
+
+ if $manage_webdir {
+ # create webdir
+ ::apache::vhost::webdir{$name:
+ ensure => $ensure,
+ path => $path,
+ owner => $owner,
+ group => $group,
+ run_mode => $run_mode,
+ datadir => false,
+ documentroot_owner => $documentroot_owner,
+ documentroot_group => $documentroot_group,
+ documentroot_mode => $documentroot_mode,
+ }
+ }
+
+ if $run_mode == 'static-itk' {
+ notice('static-itk mode is not possible for webdav vhosts, rewriting it to proxy-itk')
+ $real_run_mode = 'proxy-itk'
+ } else {
+ $real_run_mode = $run_mode
+ }
+
+ # create vhost configuration file
+ ::apache::vhost{$name:
+ ensure => $ensure,
+ configuration => $configuration,
+ path => $path,
+ path_is_webdir => $path_is_webdir,
+ logpath => $logpath,
+ logmode => $logmode,
+ template_partial => 'apache/vhosts/webdav/partial.erb',
+ vhost_mode => $vhost_mode,
+ vhost_source => $vhost_source,
+ vhost_destination => $vhost_destination,
+ domain => $domain,
+ domainalias => $domainalias,
+ server_admin => $server_admin,
+ run_mode => $real_run_mode,
+ run_uid => $run_uid,
+ run_gid => $run_gid,
+ options => $options,
+ additional_options => $additional_options,
+ default_charset => $default_charset,
+ ssl_mode => $ssl_mode,
+ htpasswd_file => $htpasswd_file,
+ htpasswd_path => $htpasswd_path,
+ ldap_auth => $ldap_auth,
+ ldap_user => $ldap_user,
+ mod_security => $mod_security,
+ mod_security_relevantonly => $mod_security_relevantonly,
+ mod_security_rules_to_disable => $mod_security_rules_to_disable,
+ mod_security_additional_options => $mod_security_additional_options,
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/vhost/webdir.pp b/puppet/modules/apache/manifests/vhost/webdir.pp
new file mode 100644
index 00000000..e0e25464
--- /dev/null
+++ b/puppet/modules/apache/manifests/vhost/webdir.pp
@@ -0,0 +1,130 @@
+# create webdir
+define apache::vhost::webdir(
+ $ensure = present,
+ $path = 'absent',
+ $owner = root,
+ $group = apache,
+ $mode = 0640,
+ $run_mode = 'normal',
+ $manage_docroot = true,
+ $datadir = true,
+ $documentroot_owner = root,
+ $documentroot_group = apache,
+ $documentroot_mode = 0640,
+ $documentroot_recurse = false
+){
+ $real_path = $path ? {
+ 'absent' => $::operatingsystem ? {
+ openbsd => "/var/www/htdocs/${name}",
+ default => "/var/www/vhosts/${name}"
+ },
+ default => $path
+ }
+
+ if (($run_mode =~ /^(static\-|proxy\-)?itk$/) or $run_mode == 'fcgid') and ($mode == '0640'){
+ $real_mode = 0644
+ } else {
+ $real_mode = $mode
+ }
+
+ $documentroot = "${real_path}/www"
+ $logdir = "${real_path}/logs"
+
+ if $owner == 'apache' {
+ $real_owner = $::operatingsystem ? {
+ openbsd => 'www',
+ debian => 'www-data',
+ default => $owner
+ }
+ } else {
+ $real_owner = $owner
+ }
+ if $group == 'apache' {
+ $real_group = $::operatingsystem ? {
+ openbsd => 'www',
+ debian => 'www-data',
+ default => $group
+ }
+ } else {
+ $real_group = $group
+ }
+
+ if $documentroot_owner == 'apache' {
+ $real_documentroot_owner = $::operatingsystem ? {
+ openbsd => 'www',
+ debian => 'www-data',
+ default => $documentroot_owner
+ }
+ } else {
+ $real_documentroot_owner = $documentroot_owner
+ }
+ if $documentroot_group == 'apache' {
+ $real_documentroot_group = $::operatingsystem ? {
+ openbsd => 'www',
+ debian => 'www-data',
+ default => $documentroot_group
+ }
+ } else {
+ $real_documentroot_group = $documentroot_group
+ }
+ case $ensure {
+ absent: {
+ exec{"cleanup_webdir_${real_path}":
+ command => "rm -rf ${real_path}",
+ onlyif => "test -d ${real_path}",
+ before => File[$real_path],
+ }
+ file{$real_path:
+ ensure => absent,
+ purge => true,
+ recurse => true,
+ force => true;
+ }
+ }
+ default: {
+ file{
+ $real_path:
+ ensure => directory,
+ require => Anchor['apache::basic_dirs::ready'],
+ owner => $real_owner,
+ group => $real_group,
+ mode => $real_mode;
+ $logdir:
+ ensure => directory,
+ before => Service['apache'],
+ owner => $real_documentroot_owner,
+ group => $real_documentroot_group,
+ mode => '0660';
+ "${real_path}/private":
+ ensure => directory,
+ owner => $real_documentroot_owner,
+ group => $real_documentroot_group,
+ mode => '0600';
+ }
+ if $manage_docroot {
+ file{$documentroot:
+ ensure => directory,
+ before => Service['apache'],
+ recurse => $documentroot_recurse,
+ owner => $real_documentroot_owner,
+ group => $real_documentroot_group,
+ mode => $documentroot_mode;
+ }
+ }
+ if $datadir {
+ file{"${real_path}/data":
+ ensure => directory,
+ owner => $real_documentroot_owner,
+ group => $real_documentroot_group,
+ mode => '0640';
+ }
+ }
+ case $::operatingsystem {
+ centos: { include apache::logrotate::centos::vhosts }
+ default: { #nothing
+ }
+ }
+ }
+ }
+}
+
diff --git a/puppet/modules/apache/manifests/webdav.pp b/puppet/modules/apache/manifests/webdav.pp
new file mode 100644
index 00000000..75219c90
--- /dev/null
+++ b/puppet/modules/apache/manifests/webdav.pp
@@ -0,0 +1,8 @@
+# manifests/webdav.pp
+
+class apache::webdav {
+ file{'/var/www/webdavlock':
+ ensure => directory,
+ owner => apache, group => 0, mode => 0700;
+ }
+}
diff --git a/puppet/modules/apache/manifests/worker.pp b/puppet/modules/apache/manifests/worker.pp
new file mode 100644
index 00000000..9a7b3be4
--- /dev/null
+++ b/puppet/modules/apache/manifests/worker.pp
@@ -0,0 +1,5 @@
+class apache::worker inherits apache {
+ case $::operatingsystem {
+ centos: { include ::apache::centos::worker }
+ }
+}
diff --git a/puppet/modules/apache/spec/classes/init_spec.rb b/puppet/modules/apache/spec/classes/init_spec.rb
new file mode 100644
index 00000000..baf26470
--- /dev/null
+++ b/puppet/modules/apache/spec/classes/init_spec.rb
@@ -0,0 +1,43 @@
+require File.expand_path(File.join(File.dirname(__FILE__),'../spec_helper'))
+
+describe 'apache', :type => 'class' do
+ describe 'with standard' do
+ #puppet-rspec bug
+ #it { should compile.with_all_deps }
+
+ it { should contain_class('apache::base') }
+ it { should_not contain_class('apache::status') }
+ it { should_not contain_class('shorewall::rules::http') }
+ it { should_not contain_class('apache::ssl') }
+ context 'on centos' do
+ let(:facts) {
+ {
+ :operatingsystem => 'CentOS',
+ }
+ }
+ it { should contain_class('apache::centos') }
+ end
+ end
+ describe 'with params' do
+ let(:facts) {
+ {
+ :concat_basedir => '/var/lib/puppet/concat'
+ }
+ }
+ let(:params){
+ {
+ :manage_shorewall => true,
+ # there is puppet-librarian bug in using that module
+ #:manage_munin => true,
+ :ssl => true,
+ }
+ }
+ #puppet-rspec bug
+ #it { should compile.with_all_deps }
+
+ it { should contain_class('apache::base') }
+ it { should_not contain_class('apache::status') }
+ it { should contain_class('shorewall::rules::http') }
+ it { should contain_class('apache::ssl') }
+ end
+end
diff --git a/puppet/modules/apache/spec/defines/vhost_file_spec.rb b/puppet/modules/apache/spec/defines/vhost_file_spec.rb
new file mode 100644
index 00000000..ed9ac5e2
--- /dev/null
+++ b/puppet/modules/apache/spec/defines/vhost_file_spec.rb
@@ -0,0 +1,131 @@
+require File.expand_path(File.join(File.dirname(__FILE__),'../spec_helper'))
+
+describe 'apache::vhost::file', :type => 'define' do
+ let(:title){ 'example.com' }
+ let(:facts){
+ {
+ :fqdn => 'apache.example.com',
+ }
+ }
+ let(:pre_condition) {
+ 'include apache'
+ }
+ describe 'with standard' do
+ it { should contain_file('example.com.conf').with(
+ :ensure => 'present',
+ :source => [ "puppet:///modules/site_apache/vhosts.d/apache.example.com/example.com.conf",
+ "puppet:///modules/site_apache/vhosts.d//example.com.conf",
+ "puppet:///modules/site_apache/vhosts.d/./example.com.conf",
+ "puppet:///modules/site_apache/vhosts.d//example.com.conf",
+ "puppet:///modules/site_apache/vhosts.d/example.com.conf",
+ "puppet:///modules/apache/vhosts.d/./example.com.conf",
+ "puppet:///modules/apache/vhosts.d//example.com.conf",
+ "puppet:///modules/apache/vhosts.d/example.com.conf" ],
+ :path => '/etc/apache2/vhosts.d/example.com.conf',
+ :require => 'File[vhosts_dir]',
+ :notify => 'Service[apache]',
+ :owner => 'root',
+ :group => 0,
+ :mode => '0644',
+ )}
+ it { should_not contain_file('/var/www/htpasswds/example.com') }
+ it { should_not contain_class('apache::includes') }
+ it { should_not contain_class('apache::mod_macro') }
+ it { should_not contain_class('apache::noiplog') }
+ it { should_not contain_class('apache::itk::lock') }
+ it { should_not contain_class('mod_security::itk_plus') }
+ it { should_not contain_class('mod_security') }
+ end
+ context 'on centos' do
+ let(:facts){
+ {
+ :fqdn => 'apache.example.com',
+ :operatingsystem => 'CentOS',
+ :operatingsystemmajrelease => '7',
+ }
+ }
+ it { should contain_file('example.com.conf').with(
+ :ensure => 'present',
+ :source => [ "puppet:///modules/site_apache/vhosts.d/apache.example.com/example.com.conf",
+ "puppet:///modules/site_apache/vhosts.d//example.com.conf",
+ "puppet:///modules/site_apache/vhosts.d/CentOS.7/example.com.conf",
+ "puppet:///modules/site_apache/vhosts.d/CentOS/example.com.conf",
+ "puppet:///modules/site_apache/vhosts.d/example.com.conf",
+ "puppet:///modules/apache/vhosts.d/CentOS.7/example.com.conf",
+ "puppet:///modules/apache/vhosts.d/CentOS/example.com.conf",
+ "puppet:///modules/apache/vhosts.d/example.com.conf" ],
+ :path => '/etc/httpd/vhosts.d/example.com.conf',
+ :require => 'File[vhosts_dir]',
+ :notify => 'Service[apache]',
+ :owner => 'root',
+ :group => 0,
+ :mode => '0644',
+ )}
+ it { should_not contain_file('/var/www/htpasswds/example.com') }
+ it { should_not contain_class('apache::includes') }
+ it { should_not contain_class('apache::mod_macro') }
+ it { should_not contain_class('apache::noiplog') }
+ it { should_not contain_class('apache::itk::lock') }
+ it { should_not contain_class('mod_security::itk_plus') }
+ it { should_not contain_class('mod_security') }
+ context 'with params' do
+ let(:params) {
+ {
+ :vhost_destination => '/tmp/a/example.com.conf',
+ :vhost_source => 'modules/my_module/example.com.conf',
+ :htpasswd_file => true,
+ :do_includes => true,
+ :mod_security => true,
+ :use_mod_macro => true,
+ :logmode => 'anonym',
+ }
+ }
+ it { should contain_file('example.com.conf').with(
+ :ensure => 'present',
+ :source => 'puppet:///modules/my_module/example.com.conf',
+ :path => '/tmp/a/example.com.conf',
+ :require => 'File[vhosts_dir]',
+ :notify => 'Service[apache]',
+ :owner => 'root',
+ :group => 0,
+ :mode => '0644',
+ )}
+ it { should contain_file('/var/www/htpasswds/example.com').with(
+ :source => [ "puppet:///modules/site_apache/htpasswds/apache.example.com/example.com",
+ "puppet:///modules/site_apache/htpasswds//example.com",
+ "puppet:///modules/site_apache/htpasswds/example.com" ],
+ :owner => 'root',
+ :group => 0,
+ :mode => '0644',
+ )}
+ it { should contain_class('apache::includes') }
+ it { should contain_class('apache::mod_macro') }
+ it { should contain_class('apache::noiplog') }
+ it { should_not contain_class('apache::itk::lock') }
+ it { should_not contain_class('mod_security::itk_plus') }
+ it { should contain_class('mod_security') }
+ end
+ context 'with content' do
+ let(:params) {
+ {
+ :content => "<VirtualHost *:80>\n Servername example.com\n</VirtualHost>"
+ }
+ }
+ it { should contain_file('example.com.conf').with(
+ :ensure => 'present',
+ :path => '/etc/httpd/vhosts.d/example.com.conf',
+ :require => 'File[vhosts_dir]',
+ :notify => 'Service[apache]',
+ :owner => 'root',
+ :group => 0,
+ :mode => '0644',
+ )}
+ it { should contain_file('example.com.conf').with_content(
+"<VirtualHost *:80>
+ Servername example.com
+</VirtualHost>"
+ )}
+ it { should_not contain_file('/var/www/htpasswds/example.com') }
+ end
+ end
+end
diff --git a/puppet/modules/apache/spec/defines/vhost_php_drupal_spec.rb b/puppet/modules/apache/spec/defines/vhost_php_drupal_spec.rb
new file mode 100644
index 00000000..5256746d
--- /dev/null
+++ b/puppet/modules/apache/spec/defines/vhost_php_drupal_spec.rb
@@ -0,0 +1,187 @@
+require File.expand_path(File.join(File.dirname(__FILE__),'../spec_helper'))
+
+describe 'apache::vhost::php::drupal', :type => 'define' do
+ let(:title){ 'example.com' }
+ let(:facts){
+ {
+ :fqdn => 'apache.example.com',
+ :operatingsystem => 'CentOS',
+ :operatingsystemmajrelease => '7',
+ }
+ }
+ describe 'with standard' do
+ it { should contain_file('/etc/cron.d/drupal_cron_example.com').with(
+ :content => "0 * * * * apache wget -O - -q -t 1 http://example.com/cron.php\n",
+ :owner => 'root',
+ :group => 0,
+ :mode => '0644',
+ )}
+ # only test the differences from the default
+ it { should contain_apache__vhost__php__webapp('example.com').with(
+ :manage_directories => false,
+ :template_partial => 'apache/vhosts/php_drupal/partial.erb',
+ :manage_config => false,
+ :php_settings => {
+ 'magic_quotes_gpc' => 0,
+ 'register_globals' => 0,
+ 'session.auto_start' => 0,
+ 'mbstring.http_input' => 'pass',
+ 'mbstring.http_output' => 'pass',
+ 'mbstring.encoding_translation' => 0,
+ }
+ )}
+ # go deeper in the catalog and test the produced template
+ it { should contain_apache__vhost__file('example.com').with_content(
+"<VirtualHost *:80 >
+
+ Include include.d/defaults.inc
+ ServerName example.com
+ DocumentRoot /var/www/vhosts/example.com/www/
+ DirectoryIndex index.htm index.html index.php
+
+
+ ErrorLog /var/www/vhosts/example.com/logs/error_log
+ CustomLog /var/www/vhosts/example.com/logs/access_log combined
+
+
+
+ <Directory \"/var/www/vhosts/example.com/www/\">
+ AllowOverride None
+
+
+ php_admin_flag engine on
+ php_admin_value error_log /var/www/vhosts/example.com/logs/php_error_log
+ php_admin_value magic_quotes_gpc 0
+ php_admin_value mbstring.encoding_translation 0
+ php_admin_value mbstring.http_input pass
+ php_admin_value mbstring.http_output pass
+ php_admin_value open_basedir /var/www/vhosts/example.com/www:/var/www/vhosts/example.com/data:/var/www/upload_tmp_dir/example.com:/var/www/session.save_path/example.com
+ php_admin_value register_globals 0
+ php_admin_flag safe_mode on
+ php_admin_value session.auto_start 0
+ php_admin_value session.save_path /var/www/session.save_path/example.com
+ php_admin_value upload_tmp_dir /var/www/upload_tmp_dir/example.com
+
+ # Protect files and directories from prying eyes.
+ <FilesMatch \"\\.(engine|inc|info|install|module|profile|po|sh|.*sql|theme|tpl(\\.php)?|xtmpl)$|^(code-style\\.pl|Entries.*|Repository|Root|Tag|Template)$\">
+ Order allow,deny
+ </FilesMatch>
+
+ # Customized error messages.
+ ErrorDocument 404 /index.php
+
+ RewriteEngine on
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]
+ </Directory>
+ <Directory \"/var/www/vhosts/example.com/www/files/\">
+ SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006
+ Options None
+ Options +FollowSymLinks
+ </Directory>
+
+ <IfModule mod_security2.c>
+ SecRuleEngine On
+ SecAuditEngine RelevantOnly
+ SecAuditLogType Concurrent
+ SecAuditLogStorageDir /var/www/vhosts/example.com/logs/
+ SecAuditLog /var/www/vhosts/example.com/logs/mod_security_audit.log
+ SecDebugLog /var/www/vhosts/example.com/logs/mod_security_debug.log
+ </IfModule>
+
+</VirtualHost>
+"
+)}
+ end
+ describe 'with mod_fcgid' do
+ let(:params){
+ {
+ :run_mode => 'fcgid',
+ :run_uid => 'foo',
+ :run_gid => 'bar',
+ }
+ }
+ it { should contain_file('/etc/cron.d/drupal_cron_example.com').with(
+ :content => "0 * * * * apache wget -O - -q -t 1 http://example.com/cron.php\n",
+ :owner => 'root',
+ :group => 0,
+ :mode => '0644',
+ )}
+ # only test variables that are tuned
+ it { should contain_apache__vhost__php__webapp('example.com').with(
+ :run_mode => 'fcgid',
+ :run_uid => 'foo',
+ :run_gid => 'bar',
+ :manage_directories => false,
+ :template_partial => 'apache/vhosts/php_drupal/partial.erb',
+ :manage_config => false,
+ :php_settings => {
+ 'magic_quotes_gpc' => 0,
+ 'register_globals' => 0,
+ 'session.auto_start' => 0,
+ 'mbstring.http_input' => 'pass',
+ 'mbstring.http_output' => 'pass',
+ 'mbstring.encoding_translation' => 0,
+ },
+ )}
+ # go deeper in the catalog and test the produced template
+ it { should contain_apache__vhost__file('example.com').with_content(
+"<VirtualHost *:80 >
+
+ Include include.d/defaults.inc
+ ServerName example.com
+ DocumentRoot /var/www/vhosts/example.com/www/
+ DirectoryIndex index.htm index.html index.php
+
+
+ ErrorLog /var/www/vhosts/example.com/logs/error_log
+ CustomLog /var/www/vhosts/example.com/logs/access_log combined
+
+
+
+ <IfModule mod_fcgid.c>
+ SuexecUserGroup foo bar
+ FcgidMaxRequestsPerProcess 5000
+ FCGIWrapper /var/www/mod_fcgid-starters/example.com/example.com-starter .php
+ AddHandler fcgid-script .php
+ </IfModule>
+
+ <Directory \"/var/www/vhosts/example.com/www/\">
+ AllowOverride None
+ Options +ExecCGI
+
+
+ # Protect files and directories from prying eyes.
+ <FilesMatch \"\\.(engine|inc|info|install|module|profile|po|sh|.*sql|theme|tpl(\\.php)?|xtmpl)$|^(code-style\\.pl|Entries.*|Repository|Root|Tag|Template)$\">
+ Order allow,deny
+ </FilesMatch>
+
+ # Customized error messages.
+ ErrorDocument 404 /index.php
+
+ RewriteEngine on
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]
+ </Directory>
+ <Directory \"/var/www/vhosts/example.com/www/files/\">
+ SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006
+ Options None
+ Options +FollowSymLinks
+ </Directory>
+
+ <IfModule mod_security2.c>
+ SecRuleEngine On
+ SecAuditEngine RelevantOnly
+ SecAuditLogType Concurrent
+ SecAuditLogStorageDir /var/www/vhosts/example.com/logs/
+ SecAuditLog /var/www/vhosts/example.com/logs/mod_security_audit.log
+ SecDebugLog /var/www/vhosts/example.com/logs/mod_security_debug.log
+ </IfModule>
+
+</VirtualHost>
+"
+)}
+ end
+end
diff --git a/puppet/modules/apache/spec/defines/vhost_php_gallery2_spec.rb b/puppet/modules/apache/spec/defines/vhost_php_gallery2_spec.rb
new file mode 100644
index 00000000..9f2325e9
--- /dev/null
+++ b/puppet/modules/apache/spec/defines/vhost_php_gallery2_spec.rb
@@ -0,0 +1,162 @@
+require File.expand_path(File.join(File.dirname(__FILE__),'../spec_helper'))
+
+describe 'apache::vhost::php::gallery2', :type => 'define' do
+ let(:title){ 'example.com' }
+ let(:facts){
+ {
+ :fqdn => 'apache.example.com',
+ :operatingsystem => 'CentOS',
+ :operatingsystemmajrelease => '7',
+ }
+ }
+ describe 'with standard' do
+ # only test the differences from the default
+ it { should contain_apache__vhost__php__webapp('example.com').with(
+ :manage_directories => true,
+ :template_partial => 'apache/vhosts/php_gallery2/partial.erb',
+ :php_settings => {
+ 'safe_mode' => 'Off',
+ 'output_buffering' => 'Off',
+ },
+ :manage_config => true,
+ :config_webwriteable => false,
+ :config_file => 'config.php',
+ )}
+ it { should contain_file('/var/www/vhosts/example.com/data/upload').with(
+ :ensure => 'directory',
+ :owner => 'apache',
+ :group => 0,
+ :mode => '0660',
+ )}
+ it { should contain_file('/var/www/vhosts/example.com/data/gdata').with(
+ :ensure => 'directory',
+ :owner => 'apache',
+ :group => 0,
+ :mode => '0660',
+ )}
+ # go deeper in the catalog and test the produced template
+ it { should contain_apache__vhost__file('example.com').with_content(
+"<VirtualHost *:80 >
+
+ Include include.d/defaults.inc
+ ServerName example.com
+ DocumentRoot /var/www/vhosts/example.com/www/
+ DirectoryIndex index.htm index.html index.php
+
+
+ ErrorLog /var/www/vhosts/example.com/logs/error_log
+ CustomLog /var/www/vhosts/example.com/logs/access_log combined
+
+
+
+ <Directory \"/var/www/vhosts/example.com/www/\">
+ AllowOverride None
+
+ php_admin_flag engine on
+ php_admin_value error_log /var/www/vhosts/example.com/logs/php_error_log
+ php_admin_value open_basedir /var/www/vhosts/example.com/www:/var/www/vhosts/example.com/data:/var/www/upload_tmp_dir/example.com:/var/www/session.save_path/example.com
+ php_admin_flag output_buffering off
+ php_admin_flag safe_mode off
+ php_admin_value session.save_path /var/www/session.save_path/example.com
+ php_admin_value upload_tmp_dir /var/www/upload_tmp_dir/example.com
+
+
+
+ # Always rewrite login's
+ # Source: http://gallery.menalto.com/node/30558
+ RewriteEngine On
+ RewriteCond %{HTTPS} !=on
+ RewriteCond %{HTTP:X-Forwarded-Proto} !=https
+ RewriteCond %{HTTP_COOKIE} ^GALLERYSID= [OR]
+ RewriteCond %{QUERY_STRING} subView=core\\.UserLogin
+ RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [NE,R,L]
+ </Directory>
+
+ <IfModule mod_security2.c>
+ SecRuleEngine Off
+ SecAuditEngine Off
+ SecAuditLogType Concurrent
+ SecAuditLogStorageDir /var/www/vhosts/example.com/logs/
+ SecAuditLog /var/www/vhosts/example.com/logs/mod_security_audit.log
+ SecDebugLog /var/www/vhosts/example.com/logs/mod_security_debug.log
+ </IfModule>
+
+</VirtualHost>
+"
+)}
+ end
+ describe 'with mod_fcgid' do
+ let(:params){
+ {
+ :run_mode => 'fcgid',
+ :run_uid => 'foo',
+ :run_gid => 'bar',
+ }
+ }
+ # only test variables that are tuned
+ it { should contain_apache__vhost__php__webapp('example.com').with(
+ :run_mode => 'fcgid',
+ :run_uid => 'foo',
+ :run_gid => 'bar',
+ :template_partial => 'apache/vhosts/php_gallery2/partial.erb',
+ :php_settings => {
+ 'safe_mode' => 'Off',
+ 'output_buffering' => 'Off',
+ },
+ :manage_directories => true,
+ :manage_config => true,
+ :config_webwriteable => false,
+ :config_file => 'config.php',
+ )}
+ # go deeper in the catalog and test the produced template
+ it { should contain_apache__vhost__file('example.com').with_content(
+"<VirtualHost *:80 >
+
+ Include include.d/defaults.inc
+ ServerName example.com
+ DocumentRoot /var/www/vhosts/example.com/www/
+ DirectoryIndex index.htm index.html index.php
+
+
+ ErrorLog /var/www/vhosts/example.com/logs/error_log
+ CustomLog /var/www/vhosts/example.com/logs/access_log combined
+
+
+
+ <IfModule mod_fcgid.c>
+ SuexecUserGroup foo bar
+ FcgidMaxRequestsPerProcess 5000
+ FCGIWrapper /var/www/mod_fcgid-starters/example.com/example.com-starter .php
+ AddHandler fcgid-script .php
+ </IfModule>
+
+ <Directory \"/var/www/vhosts/example.com/www/\">
+ AllowOverride None
+ Options +ExecCGI
+
+
+
+ # Always rewrite login's
+ # Source: http://gallery.menalto.com/node/30558
+ RewriteEngine On
+ RewriteCond %{HTTPS} !=on
+ RewriteCond %{HTTP:X-Forwarded-Proto} !=https
+ RewriteCond %{HTTP_COOKIE} ^GALLERYSID= [OR]
+ RewriteCond %{QUERY_STRING} subView=core\\.UserLogin
+ RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [NE,R,L]
+ </Directory>
+
+ <IfModule mod_security2.c>
+ SecRuleEngine Off
+ SecAuditEngine Off
+ SecAuditLogType Concurrent
+ SecAuditLogStorageDir /var/www/vhosts/example.com/logs/
+ SecAuditLog /var/www/vhosts/example.com/logs/mod_security_audit.log
+ SecDebugLog /var/www/vhosts/example.com/logs/mod_security_debug.log
+ </IfModule>
+
+</VirtualHost>
+"
+)}
+ end
+end
diff --git a/puppet/modules/apache/spec/defines/vhost_php_joomla_spec.rb b/puppet/modules/apache/spec/defines/vhost_php_joomla_spec.rb
new file mode 100644
index 00000000..000154de
--- /dev/null
+++ b/puppet/modules/apache/spec/defines/vhost_php_joomla_spec.rb
@@ -0,0 +1,279 @@
+require File.expand_path(File.join(File.dirname(__FILE__),'../spec_helper'))
+
+describe 'apache::vhost::php::joomla', :type => 'define' do
+ let(:title){ 'example.com' }
+ let(:facts){
+ {
+ :fqdn => 'apache.example.com',
+ :operatingsystem => 'CentOS',
+ :operatingsystemmajrelease => '7',
+ }
+ }
+ describe 'with standard' do
+ it { should contain_class('apache::include::joomla') }
+ # only test the differences from the default
+ it { should contain_apache__vhost__php__webapp('example.com').with(
+ :template_partial => 'apache/vhosts/php_joomla/partial.erb',
+ :php_settings => {
+ 'allow_url_fopen' => 'on',
+ 'allow_url_include' => 'off',
+ },
+ :manage_config => true,
+ :config_webwriteable => false,
+ :config_file => 'configuration.php',
+ :manage_directories => true,
+ :managed_directories => [ "/var/www/vhosts/example.com/www/administrator/backups",
+ "/var/www/vhosts/example.com/www/administrator/components",
+ "/var/www/vhosts/example.com/www/administrator/language",
+ "/var/www/vhosts/example.com/www/administrator/modules",
+ "/var/www/vhosts/example.com/www/administrator/templates",
+ "/var/www/vhosts/example.com/www/components",
+ "/var/www/vhosts/example.com/www/dmdocuments",
+ "/var/www/vhosts/example.com/www/images",
+ "/var/www/vhosts/example.com/www/language",
+ "/var/www/vhosts/example.com/www/media",
+ "/var/www/vhosts/example.com/www/modules",
+ "/var/www/vhosts/example.com/www/plugins",
+ "/var/www/vhosts/example.com/www/templates",
+ "/var/www/vhosts/example.com/www/cache",
+ "/var/www/vhosts/example.com/www/tmp",
+ "/var/www/vhosts/example.com/www/administrator/cache" ],
+ :mod_security_additional_options => "
+ # http://optics.csufresno.edu/~kriehn/fedora/fedora_files/f9/howto/modsecurity.html
+ # Exceptions for Joomla Root Directory
+ <LocationMatch \"^/\">
+ SecRuleRemoveById 950013
+ </LocationMatch>
+
+ # Exceptions for Joomla Administration Panel
+ SecRule REQUEST_FILENAME \"/administrator/index2.php\" \"id:1199400,allow,phase:1,nolog,ctl:ruleEngine=Off\"
+
+ # Exceptions for Joomla Component Expose
+ <LocationMatch \"^/components/com_expose/expose/manager/amfphp/gateway.php\">
+ SecRuleRemoveById 960010
+ </LocationMatch>
+"
+ )}
+ # go deeper in the catalog and test the produced template
+ it { should contain_apache__vhost__file('example.com').with_content(
+"<VirtualHost *:80 >
+
+ Include include.d/defaults.inc
+ ServerName example.com
+ DocumentRoot /var/www/vhosts/example.com/www/
+ DirectoryIndex index.htm index.html index.php
+
+
+ ErrorLog /var/www/vhosts/example.com/logs/error_log
+ CustomLog /var/www/vhosts/example.com/logs/access_log combined
+
+
+
+ <Directory \"/var/www/vhosts/example.com/www/\">
+ AllowOverride None
+
+ php_admin_flag allow_url_fopen on
+ php_admin_flag allow_url_include off
+ php_admin_flag engine on
+ php_admin_value error_log /var/www/vhosts/example.com/logs/php_error_log
+ php_admin_value open_basedir /var/www/vhosts/example.com/www:/var/www/vhosts/example.com/data:/var/www/upload_tmp_dir/example.com:/var/www/session.save_path/example.com
+ php_admin_flag safe_mode on
+ php_admin_value session.save_path /var/www/session.save_path/example.com
+ php_admin_value upload_tmp_dir /var/www/upload_tmp_dir/example.com
+
+
+
+ Include include.d/joomla.inc
+ </Directory>
+
+ <Directory \"/var/www/vhosts/example.com/www/administrator/\">
+ RewriteEngine on
+
+ # Rewrite URLs to https that go for the admin area
+ RewriteCond %{REMOTE_ADDR} !^127\\.[0-9]+\\.[0-9]+\\.[0-9]+$
+ RewriteCond %{HTTPS} !=on
+ RewriteCond %{REQUEST_URI} (.*/administrator/.*)
+ RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R]
+ </Directory>
+
+ # Deny various directories that
+ # shouldn't be webaccessible
+ <Directory \"/var/www/vhosts/example.com/www/tmp/\">
+ Deny From All
+ </Directory>
+ <Directory \"/var/www/vhosts/example.com/www/logs/\">
+ Deny From All
+ </Directory>
+ <Directory \"/var/www/vhosts/example.com/www/cli/\">
+ Deny From All
+ </Directory>
+
+
+ <IfModule mod_security2.c>
+ SecRuleEngine On
+ SecAuditEngine RelevantOnly
+ SecAuditLogType Concurrent
+ SecAuditLogStorageDir /var/www/vhosts/example.com/logs/
+ SecAuditLog /var/www/vhosts/example.com/logs/mod_security_audit.log
+ SecDebugLog /var/www/vhosts/example.com/logs/mod_security_debug.log
+
+
+ # http://optics.csufresno.edu/~kriehn/fedora/fedora_files/f9/howto/modsecurity.html
+ # Exceptions for Joomla Root Directory
+ <LocationMatch \"^/\">
+ SecRuleRemoveById 950013
+ </LocationMatch>
+
+ # Exceptions for Joomla Administration Panel
+ SecRule REQUEST_FILENAME \"/administrator/index2.php\" \"id:1199400,allow,phase:1,nolog,ctl:ruleEngine=Off\"
+
+ # Exceptions for Joomla Component Expose
+ <LocationMatch \"^/components/com_expose/expose/manager/amfphp/gateway.php\">
+ SecRuleRemoveById 960010
+ </LocationMatch>
+
+ </IfModule>
+
+</VirtualHost>
+"
+)}
+ end
+ describe 'with mod_fcgid' do
+ let(:params){
+ {
+ :run_mode => 'fcgid',
+ :run_uid => 'foo',
+ :run_gid => 'bar',
+ }
+ }
+ it { should contain_class('apache::include::joomla') }
+ # only test the differences from the default
+ it { should contain_apache__vhost__php__webapp('example.com').with(
+ :run_mode => 'fcgid',
+ :run_uid => 'foo',
+ :run_gid => 'bar',
+ :template_partial => 'apache/vhosts/php_joomla/partial.erb',
+ :php_settings => {
+ 'allow_url_fopen' => 'on',
+ 'allow_url_include' => 'off',
+ },
+ :manage_config => true,
+ :config_webwriteable => false,
+ :config_file => 'configuration.php',
+ :manage_directories => true,
+ :managed_directories => [ "/var/www/vhosts/example.com/www/administrator/backups",
+ "/var/www/vhosts/example.com/www/administrator/components",
+ "/var/www/vhosts/example.com/www/administrator/language",
+ "/var/www/vhosts/example.com/www/administrator/modules",
+ "/var/www/vhosts/example.com/www/administrator/templates",
+ "/var/www/vhosts/example.com/www/components",
+ "/var/www/vhosts/example.com/www/dmdocuments",
+ "/var/www/vhosts/example.com/www/images",
+ "/var/www/vhosts/example.com/www/language",
+ "/var/www/vhosts/example.com/www/media",
+ "/var/www/vhosts/example.com/www/modules",
+ "/var/www/vhosts/example.com/www/plugins",
+ "/var/www/vhosts/example.com/www/templates",
+ "/var/www/vhosts/example.com/www/cache",
+ "/var/www/vhosts/example.com/www/tmp",
+ "/var/www/vhosts/example.com/www/administrator/cache" ],
+ :mod_security_additional_options => "
+ # http://optics.csufresno.edu/~kriehn/fedora/fedora_files/f9/howto/modsecurity.html
+ # Exceptions for Joomla Root Directory
+ <LocationMatch \"^/\">
+ SecRuleRemoveById 950013
+ </LocationMatch>
+
+ # Exceptions for Joomla Administration Panel
+ SecRule REQUEST_FILENAME \"/administrator/index2.php\" \"id:1199400,allow,phase:1,nolog,ctl:ruleEngine=Off\"
+
+ # Exceptions for Joomla Component Expose
+ <LocationMatch \"^/components/com_expose/expose/manager/amfphp/gateway.php\">
+ SecRuleRemoveById 960010
+ </LocationMatch>
+"
+ )}
+ # go deeper in the catalog and test the produced template
+ it { should contain_apache__vhost__file('example.com').with_content(
+"<VirtualHost *:80 >
+
+ Include include.d/defaults.inc
+ ServerName example.com
+ DocumentRoot /var/www/vhosts/example.com/www/
+ DirectoryIndex index.htm index.html index.php
+
+
+ ErrorLog /var/www/vhosts/example.com/logs/error_log
+ CustomLog /var/www/vhosts/example.com/logs/access_log combined
+
+
+
+ <IfModule mod_fcgid.c>
+ SuexecUserGroup foo bar
+ FcgidMaxRequestsPerProcess 5000
+ FCGIWrapper /var/www/mod_fcgid-starters/example.com/example.com-starter .php
+ AddHandler fcgid-script .php
+ </IfModule>
+
+ <Directory \"/var/www/vhosts/example.com/www/\">
+ AllowOverride None
+ Options +ExecCGI
+
+
+
+ Include include.d/joomla.inc
+ </Directory>
+
+ <Directory \"/var/www/vhosts/example.com/www/administrator/\">
+ RewriteEngine on
+
+ # Rewrite URLs to https that go for the admin area
+ RewriteCond %{REMOTE_ADDR} !^127\\.[0-9]+\\.[0-9]+\\.[0-9]+$
+ RewriteCond %{HTTPS} !=on
+ RewriteCond %{REQUEST_URI} (.*/administrator/.*)
+ RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R]
+ </Directory>
+
+ # Deny various directories that
+ # shouldn't be webaccessible
+ <Directory \"/var/www/vhosts/example.com/www/tmp/\">
+ Deny From All
+ </Directory>
+ <Directory \"/var/www/vhosts/example.com/www/logs/\">
+ Deny From All
+ </Directory>
+ <Directory \"/var/www/vhosts/example.com/www/cli/\">
+ Deny From All
+ </Directory>
+
+
+ <IfModule mod_security2.c>
+ SecRuleEngine On
+ SecAuditEngine RelevantOnly
+ SecAuditLogType Concurrent
+ SecAuditLogStorageDir /var/www/vhosts/example.com/logs/
+ SecAuditLog /var/www/vhosts/example.com/logs/mod_security_audit.log
+ SecDebugLog /var/www/vhosts/example.com/logs/mod_security_debug.log
+
+
+ # http://optics.csufresno.edu/~kriehn/fedora/fedora_files/f9/howto/modsecurity.html
+ # Exceptions for Joomla Root Directory
+ <LocationMatch \"^/\">
+ SecRuleRemoveById 950013
+ </LocationMatch>
+
+ # Exceptions for Joomla Administration Panel
+ SecRule REQUEST_FILENAME \"/administrator/index2.php\" \"id:1199400,allow,phase:1,nolog,ctl:ruleEngine=Off\"
+
+ # Exceptions for Joomla Component Expose
+ <LocationMatch \"^/components/com_expose/expose/manager/amfphp/gateway.php\">
+ SecRuleRemoveById 960010
+ </LocationMatch>
+
+ </IfModule>
+
+</VirtualHost>
+"
+)}
+ end
+end
diff --git a/puppet/modules/apache/spec/defines/vhost_php_standard_spec.rb b/puppet/modules/apache/spec/defines/vhost_php_standard_spec.rb
new file mode 100644
index 00000000..159d4b81
--- /dev/null
+++ b/puppet/modules/apache/spec/defines/vhost_php_standard_spec.rb
@@ -0,0 +1,534 @@
+require File.expand_path(File.join(File.dirname(__FILE__),'../spec_helper'))
+
+describe 'apache::vhost::php::standard', :type => 'define' do
+ let(:title){ 'example.com' }
+ let(:facts){
+ {
+ :fqdn => 'apache.example.com',
+ :operatingsystem => 'CentOS',
+ :operatingsystemmajrelease => '7',
+ }
+ }
+ describe 'with standard' do
+ # only test variables that are tuned
+ it { should contain_apache__vhost__webdir('example.com') }
+ it { should_not contain_class('mod_fcgid') }
+ it { should_not contain_class('php::mod_fcgid') }
+ it { should_not contain_class('apache::include::mod_fcgid') }
+ it { should_not contain_class('php::scl::php54') }
+ it { should_not contain_class('php::scl::php55') }
+ it { should_not contain_class('php::extensions::smarty') }
+ it { should contain_class('php') }
+ it { should_not contain_mod_fcgid__starter('example.com') }
+
+ # only test variables that are tuned
+ it { should contain_apache__vhost__phpdirs('example.com').with(
+ :php_upload_tmp_dir => '/var/www/upload_tmp_dir/example.com',
+ :php_session_save_path => '/var/www/session.save_path/example.com',
+ )}
+ # only test variables that are tuned
+ it { should contain_apache__vhost('example.com').with(
+ :template_partial => 'apache/vhosts/php/partial.erb',
+ :passing_extension => 'php'
+ )}
+
+ it { should have_apache__vhost__php__safe_mode_bin_resource_count(0) }
+ it { should contain_file('/var/www/vhosts/example.com/bin').with(
+ :ensure => 'absent',
+ :recurse => true,
+ :force => true,
+ :purge => true,
+ )}
+ # go deeper in the catalog and test the produced template
+ it { should contain_apache__vhost__file('example.com').with_content(
+"<VirtualHost *:80 >
+
+ Include include.d/defaults.inc
+ ServerName example.com
+ DocumentRoot /var/www/vhosts/example.com/www/
+ DirectoryIndex index.htm index.html index.php
+
+
+ ErrorLog /var/www/vhosts/example.com/logs/error_log
+ CustomLog /var/www/vhosts/example.com/logs/access_log combined
+
+
+
+ <Directory \"/var/www/vhosts/example.com/www/\">
+ AllowOverride None
+
+ php_admin_flag engine on
+ php_admin_value error_log /var/www/vhosts/example.com/logs/php_error_log
+ php_admin_value open_basedir /var/www/vhosts/example.com/www:/var/www/vhosts/example.com/data:/var/www/upload_tmp_dir/example.com:/var/www/session.save_path/example.com
+ php_admin_flag safe_mode on
+ php_admin_value session.save_path /var/www/session.save_path/example.com
+ php_admin_value upload_tmp_dir /var/www/upload_tmp_dir/example.com
+
+
+ </Directory>
+
+ <IfModule mod_security2.c>
+ SecRuleEngine On
+ SecAuditEngine RelevantOnly
+ SecAuditLogType Concurrent
+ SecAuditLogStorageDir /var/www/vhosts/example.com/logs/
+ SecAuditLog /var/www/vhosts/example.com/logs/mod_security_audit.log
+ SecDebugLog /var/www/vhosts/example.com/logs/mod_security_debug.log
+ </IfModule>
+
+</VirtualHost>
+"
+)}
+ end
+ describe 'with standard and params' do
+ let(:params) {
+ {
+ :php_settings => {
+ 'safe_mode' => 'Off',
+ }
+ }
+ }
+ # go deeper in the catalog and test the produced template
+ it { should contain_apache__vhost__file('example.com').with_content(
+"<VirtualHost *:80 >
+
+ Include include.d/defaults.inc
+ ServerName example.com
+ DocumentRoot /var/www/vhosts/example.com/www/
+ DirectoryIndex index.htm index.html index.php
+
+
+ ErrorLog /var/www/vhosts/example.com/logs/error_log
+ CustomLog /var/www/vhosts/example.com/logs/access_log combined
+
+
+
+ <Directory \"/var/www/vhosts/example.com/www/\">
+ AllowOverride None
+
+ php_admin_flag engine on
+ php_admin_value error_log /var/www/vhosts/example.com/logs/php_error_log
+ php_admin_value open_basedir /var/www/vhosts/example.com/www:/var/www/vhosts/example.com/data:/var/www/upload_tmp_dir/example.com:/var/www/session.save_path/example.com
+ php_admin_flag safe_mode off
+ php_admin_value session.save_path /var/www/session.save_path/example.com
+ php_admin_value upload_tmp_dir /var/www/upload_tmp_dir/example.com
+
+
+ </Directory>
+
+ <IfModule mod_security2.c>
+ SecRuleEngine On
+ SecAuditEngine RelevantOnly
+ SecAuditLogType Concurrent
+ SecAuditLogStorageDir /var/www/vhosts/example.com/logs/
+ SecAuditLog /var/www/vhosts/example.com/logs/mod_security_audit.log
+ SecDebugLog /var/www/vhosts/example.com/logs/mod_security_debug.log
+ </IfModule>
+
+</VirtualHost>
+"
+)}
+ end
+ describe 'with mod_fcgid' do
+ let(:params){
+ {
+ :run_mode => 'fcgid',
+ :run_uid => 'foo',
+ :run_gid => 'bar',
+ }
+ }
+ # only test variables that are tuned
+ it { should contain_apache__vhost__webdir('example.com') }
+ it { should contain_class('mod_fcgid') }
+ it { should contain_class('php::mod_fcgid') }
+ it { should contain_class('apache::include::mod_fcgid') }
+ it { should_not contain_class('php::scl::php54') }
+ it { should_not contain_class('php::scl::php55') }
+ it { should_not contain_class('php::extensions::smarty') }
+ it { should contain_mod_fcgid__starter('example.com').with(
+ :tmp_dir => false,
+ :cgi_type => 'php',
+ :cgi_type_options => {
+ "engine" =>"On",
+ "upload_tmp_dir" =>"/var/www/upload_tmp_dir/example.com",
+ "session.save_path" =>"/var/www/session.save_path/example.com",
+ "error_log" =>"/var/www/vhosts/example.com/logs/php_error_log",
+ "safe_mode" =>"On",
+ "safe_mode_gid" =>"On",
+ "safe_mode_exec_dir"=>:undef,
+ "default_charset" =>:undef,
+ "open_basedir" =>"/var/www/vhosts/example.com/www:/var/www/vhosts/example.com/data:/var/www/upload_tmp_dir/example.com:/var/www/session.save_path/example.com"
+ },
+ :owner => 'foo',
+ :group => 'bar',
+ :notify => 'Service[apache]',
+ ) }
+
+ # only test variables that are tuned
+ it { should contain_apache__vhost__phpdirs('example.com').with(
+ :php_upload_tmp_dir => '/var/www/upload_tmp_dir/example.com',
+ :php_session_save_path => '/var/www/session.save_path/example.com',
+ )}
+ # only test variables that are tuned
+ it { should contain_apache__vhost('example.com').with(
+ :template_partial => 'apache/vhosts/php/partial.erb',
+ :passing_extension => 'php'
+ )}
+
+ it { should have_apache__vhost__php__safe_mode_bin_resource_count(0) }
+ it { should contain_file('/var/www/vhosts/example.com/bin').with(
+ :ensure => 'absent',
+ :recurse => true,
+ :force => true,
+ :purge => true,
+ )}
+ # go deeper in the catalog and test the produced template
+ it { should contain_apache__vhost__file('example.com').with_content(
+"<VirtualHost *:80 >
+
+ Include include.d/defaults.inc
+ ServerName example.com
+ DocumentRoot /var/www/vhosts/example.com/www/
+ DirectoryIndex index.htm index.html index.php
+
+
+ ErrorLog /var/www/vhosts/example.com/logs/error_log
+ CustomLog /var/www/vhosts/example.com/logs/access_log combined
+
+
+
+ <IfModule mod_fcgid.c>
+ SuexecUserGroup foo bar
+ FcgidMaxRequestsPerProcess 5000
+ FCGIWrapper /var/www/mod_fcgid-starters/example.com/example.com-starter .php
+ AddHandler fcgid-script .php
+ </IfModule>
+
+ <Directory \"/var/www/vhosts/example.com/www/\">
+ AllowOverride None
+ Options +ExecCGI
+
+
+ </Directory>
+
+ <IfModule mod_security2.c>
+ SecRuleEngine On
+ SecAuditEngine RelevantOnly
+ SecAuditLogType Concurrent
+ SecAuditLogStorageDir /var/www/vhosts/example.com/logs/
+ SecAuditLog /var/www/vhosts/example.com/logs/mod_security_audit.log
+ SecDebugLog /var/www/vhosts/example.com/logs/mod_security_debug.log
+ </IfModule>
+
+</VirtualHost>
+"
+)}
+ end
+ describe 'with mod_fcgid scl 5.4' do
+ let(:pre_condition){ 'include yum::prerequisites' }
+ let(:params){
+ {
+ :run_mode => 'fcgid',
+ :run_uid => 'foo',
+ :run_gid => 'bar',
+ :php_installation => 'scl54',
+ }
+ }
+ # only test variables that are tuned
+ it { should contain_apache__vhost__webdir('example.com') }
+ it { should contain_class('mod_fcgid') }
+ it { should contain_class('php::mod_fcgid') }
+ it { should contain_class('apache::include::mod_fcgid') }
+ it { should contain_class('php::scl::php54') }
+ it { should_not contain_class('php::scl::php55') }
+ it { should_not contain_class('php::extensions::smarty') }
+ it { should contain_mod_fcgid__starter('example.com').with(
+ :tmp_dir => false,
+ :cgi_type => 'php',
+ :cgi_type_options => {
+ "engine" =>"On",
+ "upload_tmp_dir" =>"/var/www/upload_tmp_dir/example.com",
+ "session.save_path" =>"/var/www/session.save_path/example.com",
+ "error_log" =>"/var/www/vhosts/example.com/logs/php_error_log",
+ "safe_mode" =>:undef,
+ "safe_mode_gid" =>:undef,
+ "safe_mode_exec_dir"=>:undef,
+ "default_charset" =>:undef,
+ "open_basedir" =>"/var/www/vhosts/example.com/www:/var/www/vhosts/example.com/data:/var/www/upload_tmp_dir/example.com:/var/www/session.save_path/example.com"
+ },
+ :binary => '/opt/rh/php54/root/usr/bin/php-cgi',
+ :additional_cmds => 'source /opt/rh/php54/enable',
+ :rc => '/opt/rh/php54/root/etc',
+ :owner => 'foo',
+ :group => 'bar',
+ :notify => 'Service[apache]',
+ ) }
+
+ # only test variables that are tuned
+ it { should contain_apache__vhost__phpdirs('example.com').with(
+ :php_upload_tmp_dir => '/var/www/upload_tmp_dir/example.com',
+ :php_session_save_path => '/var/www/session.save_path/example.com',
+ )}
+ # only test variables that are tuned
+ it { should contain_apache__vhost('example.com').with(
+ :template_partial => 'apache/vhosts/php/partial.erb',
+ :passing_extension => 'php'
+ )}
+
+ it { should have_apache__vhost__php__safe_mode_bin_resource_count(0) }
+ it { should contain_file('/var/www/vhosts/example.com/bin').with(
+ :ensure => 'absent',
+ :recurse => true,
+ :force => true,
+ :purge => true,
+ )}
+ # go deeper in the catalog and test the produced template
+ it { should contain_apache__vhost__file('example.com').with_content(
+"<VirtualHost *:80 >
+
+ Include include.d/defaults.inc
+ ServerName example.com
+ DocumentRoot /var/www/vhosts/example.com/www/
+ DirectoryIndex index.htm index.html index.php
+
+
+ ErrorLog /var/www/vhosts/example.com/logs/error_log
+ CustomLog /var/www/vhosts/example.com/logs/access_log combined
+
+
+
+ <IfModule mod_fcgid.c>
+ SuexecUserGroup foo bar
+ FcgidMaxRequestsPerProcess 5000
+ FCGIWrapper /var/www/mod_fcgid-starters/example.com/example.com-starter .php
+ AddHandler fcgid-script .php
+ </IfModule>
+
+ <Directory \"/var/www/vhosts/example.com/www/\">
+ AllowOverride None
+ Options +ExecCGI
+
+
+ </Directory>
+
+ <IfModule mod_security2.c>
+ SecRuleEngine On
+ SecAuditEngine RelevantOnly
+ SecAuditLogType Concurrent
+ SecAuditLogStorageDir /var/www/vhosts/example.com/logs/
+ SecAuditLog /var/www/vhosts/example.com/logs/mod_security_audit.log
+ SecDebugLog /var/www/vhosts/example.com/logs/mod_security_debug.log
+ </IfModule>
+
+</VirtualHost>
+"
+)}
+ end
+ describe 'with mod_fcgid with scl55' do
+ let(:pre_condition){ 'include yum::prerequisites' }
+ let(:params){
+ {
+ :run_mode => 'fcgid',
+ :run_uid => 'foo',
+ :run_gid => 'bar',
+ :php_installation => 'scl55',
+ }
+ }
+ # only test variables that are tuned
+ it { should contain_apache__vhost__webdir('example.com') }
+ it { should contain_class('mod_fcgid') }
+ it { should contain_class('php::mod_fcgid') }
+ it { should contain_class('apache::include::mod_fcgid') }
+ it { should_not contain_class('php::scl::php54') }
+ it { should contain_class('php::scl::php55') }
+ it { should_not contain_class('php::extensions::smarty') }
+ it { should contain_mod_fcgid__starter('example.com').with(
+ :tmp_dir => false,
+ :cgi_type => 'php',
+ :cgi_type_options => {
+ "engine" =>"On",
+ "upload_tmp_dir" =>"/var/www/upload_tmp_dir/example.com",
+ "session.save_path" =>"/var/www/session.save_path/example.com",
+ "error_log" =>"/var/www/vhosts/example.com/logs/php_error_log",
+ "safe_mode" =>:undef,
+ "safe_mode_gid" =>:undef,
+ "safe_mode_exec_dir"=>:undef,
+ "default_charset" =>:undef,
+ "open_basedir" =>"/var/www/vhosts/example.com/www:/var/www/vhosts/example.com/data:/var/www/upload_tmp_dir/example.com:/var/www/session.save_path/example.com"
+ },
+ :binary => '/opt/rh/php55/root/usr/bin/php-cgi',
+ :additional_cmds => 'source /opt/rh/php55/enable',
+ :rc => '/opt/rh/php55/root/etc',
+ :owner => 'foo',
+ :group => 'bar',
+ :notify => 'Service[apache]',
+ ) }
+
+ # only test variables that are tuned
+ it { should contain_apache__vhost__phpdirs('example.com').with(
+ :php_upload_tmp_dir => '/var/www/upload_tmp_dir/example.com',
+ :php_session_save_path => '/var/www/session.save_path/example.com',
+ )}
+ # only test variables that are tuned
+ it { should contain_apache__vhost('example.com').with(
+ :template_partial => 'apache/vhosts/php/partial.erb',
+ :passing_extension => 'php'
+ )}
+
+ it { should have_apache__vhost__php__safe_mode_bin_resource_count(0) }
+ it { should contain_file('/var/www/vhosts/example.com/bin').with(
+ :ensure => 'absent',
+ :recurse => true,
+ :force => true,
+ :purge => true,
+ )}
+ # go deeper in the catalog and test the produced template
+ it { should contain_apache__vhost__file('example.com').with_content(
+"<VirtualHost *:80 >
+
+ Include include.d/defaults.inc
+ ServerName example.com
+ DocumentRoot /var/www/vhosts/example.com/www/
+ DirectoryIndex index.htm index.html index.php
+
+
+ ErrorLog /var/www/vhosts/example.com/logs/error_log
+ CustomLog /var/www/vhosts/example.com/logs/access_log combined
+
+
+
+ <IfModule mod_fcgid.c>
+ SuexecUserGroup foo bar
+ FcgidMaxRequestsPerProcess 5000
+ FCGIWrapper /var/www/mod_fcgid-starters/example.com/example.com-starter .php
+ AddHandler fcgid-script .php
+ </IfModule>
+
+ <Directory \"/var/www/vhosts/example.com/www/\">
+ AllowOverride None
+ Options +ExecCGI
+
+
+ </Directory>
+
+ <IfModule mod_security2.c>
+ SecRuleEngine On
+ SecAuditEngine RelevantOnly
+ SecAuditLogType Concurrent
+ SecAuditLogStorageDir /var/www/vhosts/example.com/logs/
+ SecAuditLog /var/www/vhosts/example.com/logs/mod_security_audit.log
+ SecDebugLog /var/www/vhosts/example.com/logs/mod_security_debug.log
+ </IfModule>
+
+</VirtualHost>
+"
+)}
+ end
+ describe 'with mod_fcgid and params' do
+ let(:params){
+ {
+ :run_mode => 'fcgid',
+ :run_uid => 'foo',
+ :run_gid => 'bar',
+ :logmode => 'nologs',
+ :php_options => {
+ 'smarty' => true,
+ 'pear' => true,
+ 'safe_mode_exec_bins' => ['/usr/bin/cat'],
+ }
+ }
+ }
+ # only test variables that are tuned
+ it { should contain_apache__vhost__webdir('example.com') }
+ it { should contain_class('mod_fcgid') }
+ it { should contain_class('php::mod_fcgid') }
+ it { should contain_class('apache::include::mod_fcgid') }
+ it { should_not contain_class('php::scl::php54') }
+ it { should_not contain_class('php::scl::php55') }
+ it { should contain_class('php::extensions::smarty') }
+ it { should contain_mod_fcgid__starter('example.com').with(
+ :tmp_dir => false,
+ :cgi_type => 'php',
+ :cgi_type_options => {
+ "engine" =>"On",
+ "upload_tmp_dir" =>"/var/www/upload_tmp_dir/example.com",
+ "session.save_path" =>"/var/www/session.save_path/example.com",
+ "error_log" =>:undef,
+ "safe_mode" =>"On",
+ "safe_mode_gid" =>"On",
+ "safe_mode_exec_dir"=>"/var/www/vhosts/example.com/bin",
+ "default_charset" =>:undef,
+ "open_basedir" =>"/usr/share/php/Smarty/:/usr/share/pear/:/var/www/vhosts/example.com/www:/var/www/vhosts/example.com/data:/var/www/upload_tmp_dir/example.com:/var/www/session.save_path/example.com"
+ },
+ :owner => 'foo',
+ :group => 'bar',
+ :notify => 'Service[apache]',
+ ) }
+
+ # only test variables that are tuned
+ it { should contain_apache__vhost__phpdirs('example.com').with(
+ :php_upload_tmp_dir => '/var/www/upload_tmp_dir/example.com',
+ :php_session_save_path => '/var/www/session.save_path/example.com',
+ )}
+ # only test variables that are tuned
+ it { should contain_apache__vhost('example.com').with(
+ :template_partial => 'apache/vhosts/php/partial.erb',
+ :passing_extension => 'php'
+ )}
+
+ it { should have_apache__vhost__php__safe_mode_bin_resource_count(1) }
+ it { should contain_apache__vhost__php__safe_mode_bin('example.com@/usr/bin/cat').with(
+ :ensure => 'present',
+ :path => '/var/www/vhosts/example.com/bin',
+ )}
+ it { should contain_file('/var/www/vhosts/example.com/bin').with(
+ :ensure => 'directory',
+ :owner => 'apache',
+ :group => '0',
+ :recurse => true,
+ :force => true,
+ :purge => true,
+ )}
+ # go deeper in the catalog and test the produced template
+ it { should contain_apache__vhost__file('example.com').with_content(
+"<VirtualHost *:80 >
+
+ Include include.d/defaults.inc
+ ServerName example.com
+ DocumentRoot /var/www/vhosts/example.com/www/
+ DirectoryIndex index.htm index.html index.php
+
+
+ ErrorLog /dev/null
+ CustomLog /dev/null
+
+
+
+ <IfModule mod_fcgid.c>
+ SuexecUserGroup foo bar
+ FcgidMaxRequestsPerProcess 5000
+ FCGIWrapper /var/www/mod_fcgid-starters/example.com/example.com-starter .php
+ AddHandler fcgid-script .php
+ </IfModule>
+
+ <Directory \"/var/www/vhosts/example.com/www/\">
+ AllowOverride None
+ Options +ExecCGI
+
+
+ </Directory>
+
+ <IfModule mod_security2.c>
+ SecRuleEngine On
+ SecAuditEngine RelevantOnly
+ SecAuditLogType Concurrent
+ SecAuditLogStorageDir /var/www/vhosts/example.com/logs/
+ SecAuditLog /var/www/vhosts/example.com/logs/mod_security_audit.log
+ SecDebugLog /var/www/vhosts/example.com/logs/mod_security_debug.log
+ </IfModule>
+
+</VirtualHost>
+"
+)}
+ end
+end
diff --git a/puppet/modules/apache/spec/defines/vhost_php_webapp_spec.rb b/puppet/modules/apache/spec/defines/vhost_php_webapp_spec.rb
new file mode 100644
index 00000000..bdebb14c
--- /dev/null
+++ b/puppet/modules/apache/spec/defines/vhost_php_webapp_spec.rb
@@ -0,0 +1,261 @@
+require File.expand_path(File.join(File.dirname(__FILE__),'../spec_helper'))
+
+describe 'apache::vhost::php::webapp', :type => 'define' do
+ let(:title){ 'example.com' }
+ let(:facts){
+ {
+ :fqdn => 'apache.example.com',
+ :operatingsystem => 'CentOS',
+ :operatingsystemmajrelease => '7',
+ }
+ }
+ describe 'with standard' do
+ let(:params){
+ {
+ :manage_config => false,
+ :template_partial => 'apache/vhosts/php/partial.erb',
+ }
+ }
+ # only test variables that are tuned
+ it { should have_apache__file__rw_resource_count(0) }
+ it { should_not contain_apache__vhost__file__documentrootfile('configurationfile_example.com') }
+ it { should contain_apache__vhost__php__standard('example.com') }
+ # go deeper in the catalog and test the produced template
+ it { should contain_apache__vhost__file('example.com').with_content(
+"<VirtualHost *:80 >
+
+ Include include.d/defaults.inc
+ ServerName example.com
+ DocumentRoot /var/www/vhosts/example.com/www/
+ DirectoryIndex index.htm index.html index.php
+
+
+ ErrorLog /var/www/vhosts/example.com/logs/error_log
+ CustomLog /var/www/vhosts/example.com/logs/access_log combined
+
+
+
+ <Directory \"/var/www/vhosts/example.com/www/\">
+ AllowOverride None
+
+ php_admin_flag engine on
+ php_admin_value error_log /var/www/vhosts/example.com/logs/php_error_log
+ php_admin_value open_basedir /var/www/vhosts/example.com/www:/var/www/vhosts/example.com/data:/var/www/upload_tmp_dir/example.com:/var/www/session.save_path/example.com
+ php_admin_flag safe_mode on
+ php_admin_value session.save_path /var/www/session.save_path/example.com
+ php_admin_value upload_tmp_dir /var/www/upload_tmp_dir/example.com
+
+
+ </Directory>
+
+ <IfModule mod_security2.c>
+ SecRuleEngine On
+ SecAuditEngine RelevantOnly
+ SecAuditLogType Concurrent
+ SecAuditLogStorageDir /var/www/vhosts/example.com/logs/
+ SecAuditLog /var/www/vhosts/example.com/logs/mod_security_audit.log
+ SecDebugLog /var/www/vhosts/example.com/logs/mod_security_debug.log
+ </IfModule>
+
+</VirtualHost>
+"
+)}
+ end
+ describe 'with mod_fcgid' do
+ let(:params){
+ {
+ :manage_config => false,
+ :template_partial => 'apache/vhosts/php/partial.erb',
+ :run_mode => 'fcgid',
+ :run_uid => 'foo',
+ :run_gid => 'bar',
+ }
+ }
+ # only test variables that are tuned
+ it { should have_apache__file__rw_resource_count(0) }
+ it { should_not contain_apache__vhost__file__documentrootfile('configurationfile_example.com') }
+ it { should contain_apache__vhost__php__standard('example.com') }
+ # go deeper in the catalog and test the produced template
+ it { should contain_apache__vhost__file('example.com').with_content(
+"<VirtualHost *:80 >
+
+ Include include.d/defaults.inc
+ ServerName example.com
+ DocumentRoot /var/www/vhosts/example.com/www/
+ DirectoryIndex index.htm index.html index.php
+
+
+ ErrorLog /var/www/vhosts/example.com/logs/error_log
+ CustomLog /var/www/vhosts/example.com/logs/access_log combined
+
+
+
+ <IfModule mod_fcgid.c>
+ SuexecUserGroup foo bar
+ FcgidMaxRequestsPerProcess 5000
+ FCGIWrapper /var/www/mod_fcgid-starters/example.com/example.com-starter .php
+ AddHandler fcgid-script .php
+ </IfModule>
+
+ <Directory \"/var/www/vhosts/example.com/www/\">
+ AllowOverride None
+ Options +ExecCGI
+
+
+ </Directory>
+
+ <IfModule mod_security2.c>
+ SecRuleEngine On
+ SecAuditEngine RelevantOnly
+ SecAuditLogType Concurrent
+ SecAuditLogStorageDir /var/www/vhosts/example.com/logs/
+ SecAuditLog /var/www/vhosts/example.com/logs/mod_security_audit.log
+ SecDebugLog /var/www/vhosts/example.com/logs/mod_security_debug.log
+ </IfModule>
+
+</VirtualHost>
+"
+)}
+ end
+ context 'with config file and directories' do
+ describe 'with standard' do
+ let(:params){
+ {
+ :manage_config => true,
+ :managed_directories => [ '/tmp/a', '/tmp/b' ],
+ :config_file => 'config.php',
+ :template_partial => 'apache/vhosts/php/partial.erb',
+ }
+ }
+ # only test variables that are tuned
+ it { should have_apache__file__rw_resource_count(2) }
+ it { should contain_apache__file__rw('/tmp/a').with(
+ :owner => 'apache',
+ :group => 0,
+ )}
+ it { should contain_apache__file__rw('/tmp/b').with(
+ :owner => 'apache',
+ :group => 0,
+ )}
+ it { should contain_apache__vhost__file__documentrootfile('configurationfile_example.com').with(
+ :documentroot => '/var/www/vhosts/example.com/www',
+ :filename => 'config.php',
+ :thedomain => 'example.com',
+ :owner => 'apache',
+ :group => 0,
+ :mode => '0440',
+ ) }
+ it { should contain_apache__vhost__php__standard('example.com') }
+ # go deeper in the catalog and test the produced template
+ it { should contain_apache__vhost__file('example.com').with_content(
+"<VirtualHost *:80 >
+
+ Include include.d/defaults.inc
+ ServerName example.com
+ DocumentRoot /var/www/vhosts/example.com/www/
+ DirectoryIndex index.htm index.html index.php
+
+
+ ErrorLog /var/www/vhosts/example.com/logs/error_log
+ CustomLog /var/www/vhosts/example.com/logs/access_log combined
+
+
+
+ <Directory \"/var/www/vhosts/example.com/www/\">
+ AllowOverride None
+
+ php_admin_flag engine on
+ php_admin_value error_log /var/www/vhosts/example.com/logs/php_error_log
+ php_admin_value open_basedir /var/www/vhosts/example.com/www:/var/www/vhosts/example.com/data:/var/www/upload_tmp_dir/example.com:/var/www/session.save_path/example.com
+ php_admin_flag safe_mode on
+ php_admin_value session.save_path /var/www/session.save_path/example.com
+ php_admin_value upload_tmp_dir /var/www/upload_tmp_dir/example.com
+
+
+ </Directory>
+
+ <IfModule mod_security2.c>
+ SecRuleEngine On
+ SecAuditEngine RelevantOnly
+ SecAuditLogType Concurrent
+ SecAuditLogStorageDir /var/www/vhosts/example.com/logs/
+ SecAuditLog /var/www/vhosts/example.com/logs/mod_security_audit.log
+ SecDebugLog /var/www/vhosts/example.com/logs/mod_security_debug.log
+ </IfModule>
+
+</VirtualHost>
+"
+)}
+ end
+ describe 'with standard but writable' do
+ let(:params){
+ {
+ :manage_config => true,
+ :config_webwriteable => true,
+ :managed_directories => [ '/tmp/a', '/tmp/b' ],
+ :config_file => 'config.php',
+ :template_partial => 'apache/vhosts/php/partial.erb',
+ }
+ }
+ # only test variables that are tuned
+ it { should have_apache__file__rw_resource_count(2) }
+ it { should contain_apache__file__rw('/tmp/a').with(
+ :owner => 'apache',
+ :group => 0,
+ )}
+ it { should contain_apache__file__rw('/tmp/b').with(
+ :owner => 'apache',
+ :group => 0,
+ )}
+ it { should contain_apache__vhost__file__documentrootfile('configurationfile_example.com').with(
+ :documentroot => '/var/www/vhosts/example.com/www',
+ :filename => 'config.php',
+ :thedomain => 'example.com',
+ :owner => 'apache',
+ :group => 0,
+ :mode => '0660',
+ ) }
+ it { should contain_apache__vhost__php__standard('example.com') }
+ # go deeper in the catalog and test the produced template
+ it { should contain_apache__vhost__file('example.com').with_content(
+"<VirtualHost *:80 >
+
+ Include include.d/defaults.inc
+ ServerName example.com
+ DocumentRoot /var/www/vhosts/example.com/www/
+ DirectoryIndex index.htm index.html index.php
+
+
+ ErrorLog /var/www/vhosts/example.com/logs/error_log
+ CustomLog /var/www/vhosts/example.com/logs/access_log combined
+
+
+
+ <Directory \"/var/www/vhosts/example.com/www/\">
+ AllowOverride None
+
+ php_admin_flag engine on
+ php_admin_value error_log /var/www/vhosts/example.com/logs/php_error_log
+ php_admin_value open_basedir /var/www/vhosts/example.com/www:/var/www/vhosts/example.com/data:/var/www/upload_tmp_dir/example.com:/var/www/session.save_path/example.com
+ php_admin_flag safe_mode on
+ php_admin_value session.save_path /var/www/session.save_path/example.com
+ php_admin_value upload_tmp_dir /var/www/upload_tmp_dir/example.com
+
+
+ </Directory>
+
+ <IfModule mod_security2.c>
+ SecRuleEngine On
+ SecAuditEngine RelevantOnly
+ SecAuditLogType Concurrent
+ SecAuditLogStorageDir /var/www/vhosts/example.com/logs/
+ SecAuditLog /var/www/vhosts/example.com/logs/mod_security_audit.log
+ SecDebugLog /var/www/vhosts/example.com/logs/mod_security_debug.log
+ </IfModule>
+
+</VirtualHost>
+"
+)}
+ end
+ end
+end
diff --git a/puppet/modules/apache/spec/defines/vhost_php_wordpress_spec.rb b/puppet/modules/apache/spec/defines/vhost_php_wordpress_spec.rb
new file mode 100644
index 00000000..203f9690
--- /dev/null
+++ b/puppet/modules/apache/spec/defines/vhost_php_wordpress_spec.rb
@@ -0,0 +1,171 @@
+require File.expand_path(File.join(File.dirname(__FILE__),'../spec_helper'))
+
+describe 'apache::vhost::php::wordpress', :type => 'define' do
+ let(:title){ 'example.com' }
+ let(:facts){
+ {
+ :fqdn => 'apache.example.com',
+ :operatingsystem => 'CentOS',
+ :operatingsystemmajrelease => '7',
+ }
+ }
+ describe 'with standard' do
+ # only test the differences from the default
+ it { should contain_apache__vhost__php__webapp('example.com').with(
+ :mod_security_rules_to_disable => ["960010", "950018"],
+ :manage_directories => true,
+ :managed_directories => '/var/www/vhosts/example.com/www/wp-content',
+ :template_partial => 'apache/vhosts/php_wordpress/partial.erb',
+ :manage_config => true,
+ :config_webwriteable => false,
+ :config_file => 'wp-config.php',
+ )}
+ # go deeper in the catalog and test the produced template
+ it { should contain_apache__vhost__file('example.com').with_content(
+"<VirtualHost *:80 >
+
+ Include include.d/defaults.inc
+ ServerName example.com
+ DocumentRoot /var/www/vhosts/example.com/www/
+ DirectoryIndex index.htm index.html index.php
+
+
+ ErrorLog /var/www/vhosts/example.com/logs/error_log
+ CustomLog /var/www/vhosts/example.com/logs/access_log combined
+
+
+
+ <Directory \"/var/www/vhosts/example.com/www/\">
+ AllowOverride FileInfo
+
+ php_admin_flag engine on
+ php_admin_value error_log /var/www/vhosts/example.com/logs/php_error_log
+ php_admin_value open_basedir /var/www/vhosts/example.com/www:/var/www/vhosts/example.com/data:/var/www/upload_tmp_dir/example.com:/var/www/session.save_path/example.com
+ php_admin_flag safe_mode on
+ php_admin_value session.save_path /var/www/session.save_path/example.com
+ php_admin_value upload_tmp_dir /var/www/upload_tmp_dir/example.com
+
+
+ </Directory>
+
+
+ # fixes: http://git.zx2c4.com/w3-total-fail/tree/w3-total-fail.sh
+ <Directory \"/var/www/vhosts/example.com/www/wp-content/w3tc/dbcache\">
+ Deny From All
+ </Directory>
+
+ # simple wp-login brute force protection
+ # http://www.frameloss.org/2013/04/26/even-easier-brute-force-login-protection-for-wordpress/
+ RewriteEngine On
+ RewriteCond %{HTTP_COOKIE} !359422a82c97336dc082622faf72013a8e857bfd
+ RewriteRule ^/wp-login.php /wordpress-login-576a63fdc98202e7c7283713f2ddfee334bf13ee.php [R,L]
+ <Location /wordpress-login-576a63fdc98202e7c7283713f2ddfee334bf13ee.php>
+ CookieTracking on
+ CookieExpires 30
+ CookieName 359422a82c97336dc082622faf72013a8e857bfd
+ </Location>
+ RewriteRule ^/wordpress-login-576a63fdc98202e7c7283713f2ddfee334bf13ee.php /wp-login.php [NE]
+
+
+ <IfModule mod_security2.c>
+ SecRuleEngine On
+ SecAuditEngine RelevantOnly
+ SecAuditLogType Concurrent
+ SecAuditLogStorageDir /var/www/vhosts/example.com/logs/
+ SecAuditLog /var/www/vhosts/example.com/logs/mod_security_audit.log
+ SecDebugLog /var/www/vhosts/example.com/logs/mod_security_debug.log
+
+ SecRuleRemoveById \"960010\"
+ SecRuleRemoveById \"950018\"
+ </IfModule>
+
+</VirtualHost>
+"
+)}
+ end
+ describe 'with mod_fcgid' do
+ let(:params){
+ {
+ :run_mode => 'fcgid',
+ :run_uid => 'foo',
+ :run_gid => 'bar',
+ }
+ }
+ # only test variables that are tuned
+ it { should contain_apache__vhost__php__webapp('example.com').with(
+ :run_mode => 'fcgid',
+ :run_uid => 'foo',
+ :run_gid => 'bar',
+ :template_partial => 'apache/vhosts/php_wordpress/partial.erb',
+ :mod_security_rules_to_disable => ["960010", "950018"],
+ :manage_directories => true,
+ :managed_directories => '/var/www/vhosts/example.com/www/wp-content',
+ :manage_config => true,
+ :config_webwriteable => false,
+ :config_file => 'wp-config.php',
+ )}
+ # go deeper in the catalog and test the produced template
+ it { should contain_apache__vhost__file('example.com').with_content(
+"<VirtualHost *:80 >
+
+ Include include.d/defaults.inc
+ ServerName example.com
+ DocumentRoot /var/www/vhosts/example.com/www/
+ DirectoryIndex index.htm index.html index.php
+
+
+ ErrorLog /var/www/vhosts/example.com/logs/error_log
+ CustomLog /var/www/vhosts/example.com/logs/access_log combined
+
+
+
+ <IfModule mod_fcgid.c>
+ SuexecUserGroup foo bar
+ FcgidMaxRequestsPerProcess 5000
+ FCGIWrapper /var/www/mod_fcgid-starters/example.com/example.com-starter .php
+ AddHandler fcgid-script .php
+ </IfModule>
+
+ <Directory \"/var/www/vhosts/example.com/www/\">
+ AllowOverride FileInfo
+ Options +ExecCGI
+
+
+ </Directory>
+
+
+ # fixes: http://git.zx2c4.com/w3-total-fail/tree/w3-total-fail.sh
+ <Directory \"/var/www/vhosts/example.com/www/wp-content/w3tc/dbcache\">
+ Deny From All
+ </Directory>
+
+ # simple wp-login brute force protection
+ # http://www.frameloss.org/2013/04/26/even-easier-brute-force-login-protection-for-wordpress/
+ RewriteEngine On
+ RewriteCond %{HTTP_COOKIE} !359422a82c97336dc082622faf72013a8e857bfd
+ RewriteRule ^/wp-login.php /wordpress-login-576a63fdc98202e7c7283713f2ddfee334bf13ee.php [R,L]
+ <Location /wordpress-login-576a63fdc98202e7c7283713f2ddfee334bf13ee.php>
+ CookieTracking on
+ CookieExpires 30
+ CookieName 359422a82c97336dc082622faf72013a8e857bfd
+ </Location>
+ RewriteRule ^/wordpress-login-576a63fdc98202e7c7283713f2ddfee334bf13ee.php /wp-login.php [NE]
+
+
+ <IfModule mod_security2.c>
+ SecRuleEngine On
+ SecAuditEngine RelevantOnly
+ SecAuditLogType Concurrent
+ SecAuditLogStorageDir /var/www/vhosts/example.com/logs/
+ SecAuditLog /var/www/vhosts/example.com/logs/mod_security_audit.log
+ SecDebugLog /var/www/vhosts/example.com/logs/mod_security_debug.log
+
+ SecRuleRemoveById \"960010\"
+ SecRuleRemoveById \"950018\"
+ </IfModule>
+
+</VirtualHost>
+"
+)}
+ end
+end
diff --git a/puppet/modules/apache/spec/defines/vhost_spec.rb b/puppet/modules/apache/spec/defines/vhost_spec.rb
new file mode 100644
index 00000000..051ad0d4
--- /dev/null
+++ b/puppet/modules/apache/spec/defines/vhost_spec.rb
@@ -0,0 +1,202 @@
+require File.expand_path(File.join(File.dirname(__FILE__),'../spec_helper'))
+
+describe 'apache::vhost', :type => 'define' do
+ let(:title){ 'example.com' }
+ let(:facts){
+ {
+ :fqdn => 'apache.example.com',
+ :operatingsystem => 'CentOS',
+ :operatingsystemmajrelease => '7',
+ }
+ }
+ let(:pre_condition) {
+ 'include apache'
+ }
+ describe 'with standard' do
+ it { should contain_apache__vhost__template('example.com').with(
+ :ensure => 'present',
+ :do_includes => false,
+ :run_mode => 'normal',
+ :ssl_mode => false,
+ :logmode => 'default',
+ :mod_security => true,
+ :htpasswd_file => 'absent',
+ :htpasswd_path => 'absent',
+ :use_mod_macro => false,
+ )}
+ # go deeper in the catalog and the test the produced content from the template
+ it { should contain_apache__vhost__file('example.com').with_content(
+"<VirtualHost *:80 >
+
+ Include include.d/defaults.inc
+ ServerName example.com
+ DocumentRoot /var/www/vhosts/example.com/www/
+
+
+ ErrorLog /var/www/vhosts/example.com/logs/error_log
+ CustomLog /var/www/vhosts/example.com/logs/access_log combined
+
+
+
+ <Directory \"/var/www/vhosts/example.com/www/\">
+ AllowOverride None
+
+
+ </Directory>
+
+ <IfModule mod_security2.c>
+ SecRuleEngine On
+ SecAuditEngine RelevantOnly
+ SecAuditLogType Concurrent
+ SecAuditLogStorageDir /var/www/vhosts/example.com/logs/
+ SecAuditLog /var/www/vhosts/example.com/logs/mod_security_audit.log
+ SecDebugLog /var/www/vhosts/example.com/logs/mod_security_debug.log
+ </IfModule>
+
+</VirtualHost>
+"
+)}
+ end
+ describe 'with params' do
+ let(:params){
+ {
+ :do_includes => true,
+ :ssl_mode => true,
+ :logmode => 'anonym',
+ :mod_security => false,
+ :htpasswd_file => true,
+ }
+ }
+ it { should contain_apache__vhost__template('example.com').with(
+ :ensure => 'present',
+ :path => 'absent',
+ :path_is_webdir => false,
+ :logpath => 'absent',
+ :logmode => 'anonym',
+ :logprefix => '',
+ :domain => 'absent',
+ :domainalias => 'absent',
+ :server_admin => 'absent',
+ :allow_override => 'None',
+ :do_includes => true,
+ :options => 'absent',
+ :additional_options => 'absent',
+ :default_charset => 'absent',
+ :php_settings => {},
+ :php_options => {},
+ :run_mode => 'normal',
+ :run_uid => 'absent',
+ :run_gid => 'absent',
+ :template_partial => 'apache/vhosts/static/partial.erb',
+ :ssl_mode => true,
+ :htpasswd_file => true,
+ :htpasswd_path => 'absent',
+ :ldap_auth => false,
+ :ldap_user => 'any',
+ :mod_security => false,
+ :mod_security_relevantonly => true,
+ :mod_security_rules_to_disable => [],
+ :mod_security_additional_options => 'absent',
+ :use_mod_macro => false,
+ :passing_extension => 'absent',
+ :gempath => 'absent',
+ )}
+ # go deeper in the catalog and the test the produced content from the template
+ it { should contain_apache__vhost__file('example.com').with_content(
+"<VirtualHost *:80 >
+
+ Include include.d/defaults.inc
+ ServerName example.com
+ DocumentRoot /var/www/vhosts/example.com/www/
+
+
+ ErrorLog /dev/null
+ CustomLog /var/www/vhosts/example.com/logs/access_log noip
+
+
+
+ <Directory \"/var/www/vhosts/example.com/www/\">
+ AllowOverride None
+ Options +Includes
+ AuthType Basic
+ AuthName \"Access fuer example.com\"
+ AuthUserFile /var/www/htpasswds/example.com
+ require valid-user
+
+ </Directory>
+
+ <IfModule mod_security2.c>
+ SecRuleEngine Off
+ SecAuditEngine Off
+ SecAuditLogType Concurrent
+ SecAuditLogStorageDir /var/www/vhosts/example.com/logs/
+ SecAuditLog /var/www/vhosts/example.com/logs/mod_security_audit.log
+ SecDebugLog /var/www/vhosts/example.com/logs/mod_security_debug.log
+ </IfModule>
+
+</VirtualHost>
+<VirtualHost *:443 >
+
+ Include include.d/defaults.inc
+ Include include.d/ssl_defaults.inc
+ ServerName example.com
+ DocumentRoot /var/www/vhosts/example.com/www/
+
+
+ ErrorLog /dev/null
+ CustomLog /var/www/vhosts/example.com/logs/access_log noip
+
+
+
+ <Directory \"/var/www/vhosts/example.com/www/\">
+ AllowOverride None
+ Options +Includes
+ AuthType Basic
+ AuthName \"Access fuer example.com\"
+ AuthUserFile /var/www/htpasswds/example.com
+ require valid-user
+
+ </Directory>
+
+ <IfModule mod_security2.c>
+ SecRuleEngine Off
+ SecAuditEngine Off
+ SecAuditLogType Concurrent
+ SecAuditLogStorageDir /var/www/vhosts/example.com/logs/
+ SecAuditLog /var/www/vhosts/example.com/logs/mod_security_audit.log
+ SecDebugLog /var/www/vhosts/example.com/logs/mod_security_debug.log
+ </IfModule>
+
+</VirtualHost>
+"
+)}
+ end
+ describe 'with params II' do
+ let(:params){
+ {
+ :vhost_mode => 'file',
+ }
+ }
+ it { should_not contain_apache__vhost__template('example.com') }
+ it { should contain_apache__vhost__file('example.com').with(
+ :ensure => 'present',
+ :vhost_source => 'absent',
+ :vhost_destination => 'absent',
+ :do_includes => false,
+ :run_mode => 'normal',
+ :mod_security => true,
+ :htpasswd_file => 'absent',
+ :htpasswd_path => 'absent',
+ :use_mod_macro => false,
+ )}
+ end
+ describe 'with wrong vhost_mode' do
+ let(:params){
+ {
+ :vhost_mode => 'foo',
+ }
+ }
+ it { expect { should compile }.to raise_error(Puppet::Error, /No such vhost_mode: foo defined for example.com\./)
+ }
+ end
+end
diff --git a/puppet/modules/apache/spec/defines/vhost_static_spec.rb b/puppet/modules/apache/spec/defines/vhost_static_spec.rb
new file mode 100644
index 00000000..37891bb5
--- /dev/null
+++ b/puppet/modules/apache/spec/defines/vhost_static_spec.rb
@@ -0,0 +1,54 @@
+require File.expand_path(File.join(File.dirname(__FILE__),'../spec_helper'))
+
+describe 'apache::vhost::static', :type => 'define' do
+ let(:title){ 'example.com' }
+ let(:facts){
+ {
+ :fqdn => 'apache.example.com',
+ :operatingsystem => 'CentOS',
+ :operatingsystemmajrelease => '7',
+ }
+ }
+ let(:pre_condition) {
+ 'include apache'
+ }
+ describe 'with standard' do
+ # only test the relevant options
+ it { should contain_apache__vhost__webdir('example.com').with(
+ :datadir => false,
+ )}
+ it { should contain_apache__vhost('example.com') }
+ # go deeper in the catalog and test the produced template
+ it { should contain_apache__vhost__file('example.com').with_content(
+"<VirtualHost *:80 >
+
+ Include include.d/defaults.inc
+ ServerName example.com
+ DocumentRoot /var/www/vhosts/example.com/www/
+
+
+ ErrorLog /var/www/vhosts/example.com/logs/error_log
+ CustomLog /var/www/vhosts/example.com/logs/access_log combined
+
+
+
+ <Directory \"/var/www/vhosts/example.com/www/\">
+ AllowOverride None
+
+
+ </Directory>
+
+ <IfModule mod_security2.c>
+ SecRuleEngine Off
+ SecAuditEngine Off
+ SecAuditLogType Concurrent
+ SecAuditLogStorageDir /var/www/vhosts/example.com/logs/
+ SecAuditLog /var/www/vhosts/example.com/logs/mod_security_audit.log
+ SecDebugLog /var/www/vhosts/example.com/logs/mod_security_debug.log
+ </IfModule>
+
+</VirtualHost>
+"
+)}
+ end
+end
diff --git a/puppet/modules/apache/spec/defines/vhost_template_spec.rb b/puppet/modules/apache/spec/defines/vhost_template_spec.rb
new file mode 100644
index 00000000..96fb9ac3
--- /dev/null
+++ b/puppet/modules/apache/spec/defines/vhost_template_spec.rb
@@ -0,0 +1,297 @@
+require File.expand_path(File.join(File.dirname(__FILE__),'../spec_helper'))
+
+describe 'apache::vhost::template', :type => 'define' do
+ let(:title){ 'example.com' }
+ let(:facts){
+ {
+ :fqdn => 'apache.example.com',
+ :operatingsystem => 'CentOS',
+ :operatingsystemmajrelease => '7',
+ }
+ }
+ let(:pre_condition) {
+ 'include apache'
+ }
+ describe 'with standard' do
+ it { should contain_apache__vhost__file('example.com').with(
+ :ensure => 'present',
+ :do_includes => false,
+ :run_mode => 'normal',
+ :ssl_mode => false,
+ :logmode => 'default',
+ :mod_security => true,
+ :htpasswd_file => 'absent',
+ :htpasswd_path => 'absent',
+ :use_mod_macro => false,
+ )}
+ it { should contain_apache__vhost__file('example.com').with_content(
+"<VirtualHost *:80 >
+
+ Include include.d/defaults.inc
+ ServerName example.com
+ DocumentRoot /var/www/vhosts/example.com/www/
+
+
+ ErrorLog /var/www/vhosts/example.com/logs/error_log
+ CustomLog /var/www/vhosts/example.com/logs/access_log combined
+
+
+
+ <Directory \"/var/www/vhosts/example.com/www/\">
+ AllowOverride None
+
+
+ </Directory>
+
+ <IfModule mod_security2.c>
+ SecRuleEngine On
+ SecAuditEngine RelevantOnly
+ SecAuditLogType Concurrent
+ SecAuditLogStorageDir /var/www/vhosts/example.com/logs/
+ SecAuditLog /var/www/vhosts/example.com/logs/mod_security_audit.log
+ SecDebugLog /var/www/vhosts/example.com/logs/mod_security_debug.log
+ </IfModule>
+
+</VirtualHost>
+"
+)}
+ end
+ describe 'with params' do
+ let(:params){
+ {
+ :do_includes => true,
+ :ssl_mode => true,
+ :logmode => 'anonym',
+ :mod_security => false,
+ :htpasswd_file => true,
+ }
+ }
+ it { should contain_apache__vhost__file('example.com').with(
+ :ensure => 'present',
+ :do_includes => true,
+ :run_mode => 'normal',
+ :ssl_mode => true,
+ :logmode => 'anonym',
+ :mod_security => false,
+ :htpasswd_file => true,
+ :htpasswd_path => 'absent',
+ :use_mod_macro => false,
+ )}
+ it { should contain_apache__vhost__file('example.com').with_content(
+"<VirtualHost *:80 >
+
+ Include include.d/defaults.inc
+ ServerName example.com
+ DocumentRoot /var/www/vhosts/example.com/www/
+
+
+ ErrorLog /dev/null
+ CustomLog /var/www/vhosts/example.com/logs/access_log noip
+
+
+
+ <Directory \"/var/www/vhosts/example.com/www/\">
+ AllowOverride None
+ Options +Includes
+ AuthType Basic
+ AuthName \"Access fuer example.com\"
+ AuthUserFile /var/www/htpasswds/example.com
+ require valid-user
+
+ </Directory>
+
+ <IfModule mod_security2.c>
+ SecRuleEngine Off
+ SecAuditEngine Off
+ SecAuditLogType Concurrent
+ SecAuditLogStorageDir /var/www/vhosts/example.com/logs/
+ SecAuditLog /var/www/vhosts/example.com/logs/mod_security_audit.log
+ SecDebugLog /var/www/vhosts/example.com/logs/mod_security_debug.log
+ </IfModule>
+
+</VirtualHost>
+<VirtualHost *:443 >
+
+ Include include.d/defaults.inc
+ Include include.d/ssl_defaults.inc
+ ServerName example.com
+ DocumentRoot /var/www/vhosts/example.com/www/
+
+
+ ErrorLog /dev/null
+ CustomLog /var/www/vhosts/example.com/logs/access_log noip
+
+
+
+ <Directory \"/var/www/vhosts/example.com/www/\">
+ AllowOverride None
+ Options +Includes
+ AuthType Basic
+ AuthName \"Access fuer example.com\"
+ AuthUserFile /var/www/htpasswds/example.com
+ require valid-user
+
+ </Directory>
+
+ <IfModule mod_security2.c>
+ SecRuleEngine Off
+ SecAuditEngine Off
+ SecAuditLogType Concurrent
+ SecAuditLogStorageDir /var/www/vhosts/example.com/logs/
+ SecAuditLog /var/www/vhosts/example.com/logs/mod_security_audit.log
+ SecDebugLog /var/www/vhosts/example.com/logs/mod_security_debug.log
+ </IfModule>
+
+</VirtualHost>
+"
+)}
+ end
+ describe 'with params II' do
+ let(:params){
+ {
+ :do_includes => true,
+ :ssl_mode => 'force',
+ :logmode => 'semianonym',
+ :mod_security => false,
+ :htpasswd_file => true,
+ }
+ }
+ it { should contain_apache__vhost__file('example.com').with(
+ :ensure => 'present',
+ :do_includes => true,
+ :run_mode => 'normal',
+ :ssl_mode => 'force',
+ :logmode => 'semianonym',
+ :mod_security => false,
+ :htpasswd_file => true,
+ :htpasswd_path => 'absent',
+ :use_mod_macro => false,
+ )}
+ it { should contain_apache__vhost__file('example.com').with_content(
+"<VirtualHost *:80 >
+
+ Include include.d/defaults.inc
+ ServerName example.com
+ DocumentRoot /var/www/vhosts/example.com/www/
+
+
+ ErrorLog /var/www/vhosts/example.com/logs/error_log
+ CustomLog /var/www/vhosts/example.com/logs/access_log noip
+
+
+
+ RewriteEngine On
+ RewriteCond %{HTTPS} !=on
+ RewriteCond %{HTTP:X-Forwarded-Proto} !=https
+ RewriteRule (.*) https://%{SERVER_NAME}$1 [R=permanent,L]
+ <Directory \"/var/www/vhosts/example.com/www/\">
+ AllowOverride None
+ Options +Includes
+ AuthType Basic
+ AuthName \"Access fuer example.com\"
+ AuthUserFile /var/www/htpasswds/example.com
+ require valid-user
+
+ </Directory>
+
+ <IfModule mod_security2.c>
+ SecRuleEngine Off
+ SecAuditEngine Off
+ SecAuditLogType Concurrent
+ SecAuditLogStorageDir /var/www/vhosts/example.com/logs/
+ SecAuditLog /var/www/vhosts/example.com/logs/mod_security_audit.log
+ SecDebugLog /var/www/vhosts/example.com/logs/mod_security_debug.log
+ </IfModule>
+
+</VirtualHost>
+<VirtualHost *:443 >
+
+ Include include.d/defaults.inc
+ Include include.d/ssl_defaults.inc
+ ServerName example.com
+ DocumentRoot /var/www/vhosts/example.com/www/
+
+
+ ErrorLog /var/www/vhosts/example.com/logs/error_log
+ CustomLog /var/www/vhosts/example.com/logs/access_log noip
+
+
+
+ <Directory \"/var/www/vhosts/example.com/www/\">
+ AllowOverride None
+ Options +Includes
+ AuthType Basic
+ AuthName \"Access fuer example.com\"
+ AuthUserFile /var/www/htpasswds/example.com
+ require valid-user
+
+ </Directory>
+
+ <IfModule mod_security2.c>
+ SecRuleEngine Off
+ SecAuditEngine Off
+ SecAuditLogType Concurrent
+ SecAuditLogStorageDir /var/www/vhosts/example.com/logs/
+ SecAuditLog /var/www/vhosts/example.com/logs/mod_security_audit.log
+ SecDebugLog /var/www/vhosts/example.com/logs/mod_security_debug.log
+ </IfModule>
+
+</VirtualHost>
+"
+)}
+ end
+ describe 'with params III' do
+ let(:params){
+ {
+ :do_includes => false,
+ :ssl_mode => 'only',
+ :logmode => 'nologs',
+ :mod_security => true,
+ :htpasswd_file => 'absent',
+ }
+ }
+ it { should contain_apache__vhost__file('example.com').with(
+ :ensure => 'present',
+ :do_includes => false,
+ :run_mode => 'normal',
+ :ssl_mode => 'only',
+ :logmode => 'nologs',
+ :mod_security => true,
+ :htpasswd_file => 'absent',
+ :htpasswd_path => 'absent',
+ :use_mod_macro => false,
+ )}
+ it { should contain_apache__vhost__file('example.com').with_content(
+"<VirtualHost *:443 >
+
+ Include include.d/defaults.inc
+ Include include.d/ssl_defaults.inc
+ ServerName example.com
+ DocumentRoot /var/www/vhosts/example.com/www/
+
+
+ ErrorLog /dev/null
+ CustomLog /dev/null
+
+
+
+ <Directory \"/var/www/vhosts/example.com/www/\">
+ AllowOverride None
+
+
+ </Directory>
+
+ <IfModule mod_security2.c>
+ SecRuleEngine On
+ SecAuditEngine RelevantOnly
+ SecAuditLogType Concurrent
+ SecAuditLogStorageDir /var/www/vhosts/example.com/logs/
+ SecAuditLog /var/www/vhosts/example.com/logs/mod_security_audit.log
+ SecDebugLog /var/www/vhosts/example.com/logs/mod_security_debug.log
+ </IfModule>
+
+</VirtualHost>
+"
+)}
+ end
+end
diff --git a/puppet/modules/apache/spec/functions/guess_apache_version.rb b/puppet/modules/apache/spec/functions/guess_apache_version.rb
new file mode 100644
index 00000000..b57a7a0f
--- /dev/null
+++ b/puppet/modules/apache/spec/functions/guess_apache_version.rb
@@ -0,0 +1,50 @@
+require File.expand_path(File.join(File.dirname(__FILE__),'../spec_helper'))
+
+describe 'guess_apache_version function' do
+
+ #let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("guess_apache_version")).to eq("function_guess_apache_version")
+ end
+
+ context 'on debian 7.8' do
+ let(:facts) do
+ {
+ :operatingsystem => 'Debian',
+ :operatingsystemrelease => '7.8'
+ }
+ end
+ it "should return 2.2" do
+ result = scope.function_guess_apache_version([])
+ expect(result).to(eq('2.2'))
+ end
+ end
+
+ context 'on debian 8.0' do
+ let(:facts) do
+ {
+ :operatingsystem => 'Debian',
+ :operatingsystemrelease => '8.0'
+ }
+ end
+ it "should return 2.4" do
+ result = scope.function_guess_apache_version([])
+ expect(result).to(eq('2.4'))
+ end
+ end
+
+ context 'on ubuntu 15.10' do
+ let(:facts) do
+ {
+ :operatingsystem => 'Ubuntu',
+ :operatingsystemrelease => '15.10'
+ }
+ end
+ it "should return 2.4" do
+ result = scope.function_guess_apache_version([])
+ expect(result).to(eq('2.4'))
+ end
+ end
+
+end
diff --git a/puppet/modules/apache/spec/spec_helper.rb b/puppet/modules/apache/spec/spec_helper.rb
new file mode 100644
index 00000000..381f9720
--- /dev/null
+++ b/puppet/modules/apache/spec/spec_helper.rb
@@ -0,0 +1,13 @@
+require 'puppetlabs_spec_helper/module_spec_helper'
+require 'rake'
+
+fixture_path = File.expand_path(File.join(__FILE__, '..', 'fixtures'))
+
+RSpec.configure do |c|
+ c.module_path = File.join(fixture_path, 'modules')
+ c.manifest_dir = File.join(fixture_path, 'manifests')
+ c.pattern = FileList[c.pattern].exclude(/^spec\/fixtures/)
+end
+
+Puppet::Util::Log.level = :warning
+Puppet::Util::Log.newdestination(:console)
diff --git a/puppet/modules/apache/templates/default/default_index.erb b/puppet/modules/apache/templates/default/default_index.erb
new file mode 100644
index 00000000..b35ecd91
--- /dev/null
+++ b/puppet/modules/apache/templates/default/default_index.erb
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<head>
+ <title><%= scope.lookupvar('::hostname') %></title>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+</head>
+<body>
+ <h1> No page @ this location</h1>
+ <p>
+ <small><em><%= scope.lookupvar('::hostname') %></em></small>
+ </p>
+</body>
+</html>
diff --git a/puppet/modules/apache/templates/include.d/ssl_defaults.inc.erb b/puppet/modules/apache/templates/include.d/ssl_defaults.inc.erb
new file mode 100644
index 00000000..77f8e77a
--- /dev/null
+++ b/puppet/modules/apache/templates/include.d/ssl_defaults.inc.erb
@@ -0,0 +1,78 @@
+# SSL Engine Switch:
+# Enable/Disable SSL for this virtual host.
+SSLEngine on
+
+# SSL Protocol support:
+# List the enable protocol levels with which clients will be able to
+# connect. Disable SSLv2 access by default:
+SSLProtocol All -SSLv2 -SSLv3
+
+# SSL Cipher Suite:
+# List the ciphers that the client is permitted to negotiate.
+# See the mod_ssl documentation for a complete list.
+SSLCipherSuite "<%= scope.lookupvar('apache::ssl_cipher_suite') %>"
+
+SSLHonorCipherOrder on
+
+# SSL Engine Options:
+# Set various options for the SSL engine.
+# o FakeBasicAuth:
+# Translate the client X.509 into a Basic Authorisation. This means that
+# the standard Auth/DBMAuth methods can be used for access control. The
+# user name is the `one line' version of the client's X.509 certificate.
+# Note that no password is obtained from the user. Every entry in the user
+# file needs this password: `xxj31ZMTZzkVA'.
+# o ExportCertData:
+# This exports two additional environment variables: SSL_CLIENT_CERT and
+# SSL_SERVER_CERT. These contain the PEM-encoded certificates of the
+# server (always existing) and the client (only existing when client
+# authentication is used). This can be used to import the certificates
+# into CGI scripts.
+# o StdEnvVars:
+# This exports the standard SSL/TLS related `SSL_*' environment variables.
+# Per default this exportation is switched off for performance reasons,
+# because the extraction step is an expensive operation and is usually
+# useless for serving static content. So one usually enables the
+# exportation for CGI and SSI requests only.
+# o StrictRequire:
+# This denies access when "SSLRequireSSL" or "SSLRequire" applied even
+# under a "Satisfy any" situation, i.e. when it applies access is denied
+# and no other module can change it.
+# o OptRenegotiate:
+# This enables optimized SSL connection renegotiation handling when SSL
+# directives are used in per-directory context.
+#SSLOptions +FakeBasicAuth +ExportCertData +StrictRequire
+<Files ~ "\.(cgi|shtml|phtml|php3?)$">
+ SSLOptions +StdEnvVars
+</Files>
+<Directory "/var/www/cgi-bin">
+ SSLOptions +StdEnvVars
+</Directory>
+
+# SSL Protocol Adjustments:
+# The safe and default but still SSL/TLS standard compliant shutdown
+# approach is that mod_ssl sends the close notify alert but doesn't wait for
+# the close notify alert from client. When you need a different shutdown
+# approach you can use one of the following variables:
+# o ssl-unclean-shutdown:
+# This forces an unclean shutdown when the connection is closed, i.e. no
+# SSL close notify alert is send or allowed to received. This violates
+# the SSL/TLS standard but is needed for some brain-dead browsers. Use
+# this when you receive I/O errors because of the standard approach where
+# mod_ssl sends the close notify alert.
+# o ssl-accurate-shutdown:
+# This forces an accurate shutdown when the connection is closed, i.e. a
+# SSL close notify alert is send and mod_ssl waits for the close notify
+# alert of the client. This is 100% SSL/TLS standard compliant, but in
+# practice often causes hanging connections with brain-dead browsers. Use
+# this only for browsers where you know that their SSL implementation
+# works correctly.
+# Notice: Most problems of broken clients are also related to the HTTP
+# keep-alive facility, so you usually additionally want to disable
+# keep-alive for those clients, too. Use variable "nokeepalive" for this.
+# Similarly, one has to force some clients to use HTTP/1.0 to workaround
+# their broken HTTP/1.1 implementation. Use variables "downgrade-1.0" and
+# "force-response-1.0" for this.
+SetEnvIf User-Agent ".*MSIE.*" \
+ nokeepalive ssl-unclean-shutdown \
+ downgrade-1.0 force-response-1.0
diff --git a/puppet/modules/apache/templates/itk_plus/CentOS/00-listen-ssl.conf.erb b/puppet/modules/apache/templates/itk_plus/CentOS/00-listen-ssl.conf.erb
new file mode 100644
index 00000000..83f7beeb
--- /dev/null
+++ b/puppet/modules/apache/templates/itk_plus/CentOS/00-listen-ssl.conf.erb
@@ -0,0 +1,6 @@
+<IfDefine HttpdLocal>
+Listen 127.0.0.1:443
+</IfDefine>
+<IfDefine !HttpdLocal>
+Listen <%= scope.lookupvar('::ipaddress') %>:443
+</IfDefine>
diff --git a/puppet/modules/apache/templates/itk_plus/CentOS/00-listen.conf.erb b/puppet/modules/apache/templates/itk_plus/CentOS/00-listen.conf.erb
new file mode 100644
index 00000000..30b20466
--- /dev/null
+++ b/puppet/modules/apache/templates/itk_plus/CentOS/00-listen.conf.erb
@@ -0,0 +1,8 @@
+<IfDefine HttpdLocal>
+Listen 127.0.0.1:80
+PidFile run/httpdlocal.pid
+</IfDefine>
+<IfDefine !HttpdLocal>
+Listen <%= scope.lookupvar('::ipaddress') %>:80
+PidFile run/httpd.pid
+</IfDefine>
diff --git a/puppet/modules/apache/templates/vhosts/0-default_ssl.conf.erb b/puppet/modules/apache/templates/vhosts/0-default_ssl.conf.erb
new file mode 100644
index 00000000..86e4979f
--- /dev/null
+++ b/puppet/modules/apache/templates/vhosts/0-default_ssl.conf.erb
@@ -0,0 +1,21 @@
+############################################################
+### This file is managed by PUPPET! ####
+### Only modify in repo or you will loose the changes! ####
+############################################################
+
+<VirtualHost *:443>
+ Include include.d/defaults.inc
+ Include include.d/ssl_defaults.inc
+ DocumentRoot /var/www/html
+
+ # Use separate log files for the SSL virtual host; note that LogLevel
+ # is not inherited from httpd.conf.
+ ErrorLog logs/ssl_error_log
+ TransferLog logs/ssl_access_log
+ LogLevel warn
+
+<%= scope.function_templatewlv(['apache/vhosts/partials/ssl.erb',
+ {'configuration' => {}}]) %>
+</VirtualHost>
+
+# vim: ts=4 filetype=apache
diff --git a/puppet/modules/apache/templates/vhosts/default.erb b/puppet/modules/apache/templates/vhosts/default.erb
new file mode 100644
index 00000000..1ef8023d
--- /dev/null
+++ b/puppet/modules/apache/templates/vhosts/default.erb
@@ -0,0 +1,44 @@
+<%
+vhost_parts = case @ssl_mode
+ when 'only' then [:ssl]
+ when false,'false' then [:normal]
+ else [:normal,:ssl]
+end
+vhost_parts.each do |vhost_part| -%>
+<VirtualHost *:<%= vhost_part == :ssl ? '443' : '80' %> >
+
+<%= scope.function_templatewlv(['apache/vhosts/partials/header_default.erb',
+ {'vhost_part' => vhost_part,
+ 'configuration' => @configuration,}]) %>
+
+<%= scope.function_template(['apache/vhosts/partials/logs.erb']) %>
+
+<% if @run_mode.to_s =~ /(proxy\-|static\-)?itk/ -%>
+ <IfModule mpm_itk_module>
+ AssignUserId <%= "#{@run_uid} #{@run_gid}" %>
+ </IfModule>
+
+<% elsif @run_mode.to_s == 'fcgid' -%>
+ <IfModule mod_fcgid.c>
+ SuexecUserGroup <%= "#{@run_uid} #{@run_gid}" %>
+ FcgidMaxRequestsPerProcess 5000
+ FCGIWrapper /var/www/mod_fcgid-starters/<%= @name %>/<%= @name %>-starter .<%= @passing_extension %>
+ AddHandler fcgid-script .<%= @passing_extension %>
+ </IfModule>
+
+<% end -%>
+<% if @ssl_mode == 'force' && vhost_part == :normal -%>
+ RewriteEngine On
+ RewriteCond %{HTTPS} !=on
+ RewriteCond %{HTTP:X-Forwarded-Proto} !=https
+ RewriteRule (.*) https://%{SERVER_NAME}$1 [R=permanent,L]
+<% end -%>
+<%= scope.function_templatewlv([@template_partial, {'vhost_part' => vhost_part } ]) %>
+<% unless @template_partial == 'apache/vhosts/itk_plus/partial.erb' -%>
+<%= scope.function_template(['apache/vhosts/partials/mod_security.erb']) %>
+<% end -%>
+<% unless @additional_options.to_s == 'absent' -%>
+ <%= @additional_options %>
+<% end -%>
+</VirtualHost>
+<% end -%>
diff --git a/puppet/modules/apache/templates/vhosts/gitweb/partial.erb b/puppet/modules/apache/templates/vhosts/gitweb/partial.erb
new file mode 100644
index 00000000..a8475f60
--- /dev/null
+++ b/puppet/modules/apache/templates/vhosts/gitweb/partial.erb
@@ -0,0 +1,16 @@
+ SetEnv GITWEB_CONFIG <%= @gitweb_config %>
+ DirectoryIndex gitweb.cgi
+ <Directory "<%= @documentroot %>/">
+<% if @options.to_s != 'absent' || @do_includes.to_s == 'true'-%>
+ Options <% unless @options.to_s == 'absent' -%><%= @options %><% end -%><% if @do_includes.to_s == 'true' && !@options.include?('+Includes') -%> +Includes<% end -%><% unless @options.include?('+ExecCGI') -%> +ExecCGI<% end -%>
+<% end -%>
+ AddHandler cgi-script .cgi
+ <Files gitweb.cgi>
+ Options ExecCGI FollowSymLinks
+ SetHandler cgi-script
+ </Files>
+ RewriteEngine on
+ RewriteRule ^[a-zA-Z0-9_-]+.git/?(\?.)?$ /gitweb.cgi%{REQUESTURI} [L,PT]
+
+<%= scope.function_template(['apache/vhosts/partials/authentication.erb']) %>
+ </Directory>
diff --git a/puppet/modules/apache/templates/vhosts/itk_plus.erb b/puppet/modules/apache/templates/vhosts/itk_plus.erb
new file mode 100644
index 00000000..b5461968
--- /dev/null
+++ b/puppet/modules/apache/templates/vhosts/itk_plus.erb
@@ -0,0 +1,6 @@
+<IfDefine HttpdLocal>
+<%= scope.function_template(['apache/vhost/default.erb']) %>
+</IfDefine>
+<IfDefine !HttpdLocal>
+<%= scope.function_templatewlv(['apache/vhost/default.erb', {'template_partial' => 'apache/vhosts/itk_plus/partial.erb' }]) %>
+</IfDefine>
diff --git a/puppet/modules/apache/templates/vhosts/itk_plus/partial.erb b/puppet/modules/apache/templates/vhosts/itk_plus/partial.erb
new file mode 100644
index 00000000..df045433
--- /dev/null
+++ b/puppet/modules/apache/templates/vhosts/itk_plus/partial.erb
@@ -0,0 +1,31 @@
+
+ ProxyPreserveHost On
+ ProxyRequests off
+<% if vhost_part == :ssl -%>
+ SSLProxyEngine On
+<% if run_mode.to_s == 'static-itk' -%>
+ ProxyPassMatch ^/(.*\.<%= @passing_extension %>/?.*)$ https://127.0.0.1/$1
+<% else -%>
+ ProxyPass / https://127.0.0.1/
+<% end -%>
+ ProxyPassReverse / https://127.0.0.1/
+<% else -%>
+<% if run_mode.to_s == 'static-itk' -%>
+ ProxyPassMatch ^/(.*\.<%= @passing_extension %>/?.*)$ http://127.0.0.1/$1
+<% else -%>
+ ProxyPass / http://127.0.0.1/
+<% end -%>
+ ProxyPassReverse / http://127.0.0.1/
+<% end -%>
+
+<% if @run_mode.to_s == 'static-itk' && (@ssl_mode.to_s != 'force' || vhost_part == :ssl) -%>
+ <Directory "<%= @documentroot %>/">
+ AllowOverride <%= @allow_override %>
+<% if @options.to_s != 'absent' || @do_includes.to_s == 'true' -%>
+ Options <% unless @options.to_s == 'absent' -%><%= @options %><% end -%><% if @do_includes.to_s == 'true' && !@options.include?('+Includes') -%> +Includes<% end -%>
+<% end -%>
+<%= scope.function_template(['apache/vhosts/partials/authentication.erb']) %>
+ </Directory>
+<% end -%>
+
+
diff --git a/puppet/modules/apache/templates/vhosts/partials/authentication.erb b/puppet/modules/apache/templates/vhosts/partials/authentication.erb
new file mode 100644
index 00000000..ed832210
--- /dev/null
+++ b/puppet/modules/apache/templates/vhosts/partials/authentication.erb
@@ -0,0 +1,6 @@
+<% unless @htpasswd_file.to_s == 'absent' -%>
+ AuthType Basic
+ AuthName "Access fuer <%= @servername %>"
+ AuthUserFile <%= @real_htpasswd_path %>
+ require valid-user
+<% end -%>
diff --git a/puppet/modules/apache/templates/vhosts/partials/header_default.erb b/puppet/modules/apache/templates/vhosts/partials/header_default.erb
new file mode 100644
index 00000000..cd4d04ca
--- /dev/null
+++ b/puppet/modules/apache/templates/vhosts/partials/header_default.erb
@@ -0,0 +1,22 @@
+ Include include.d/defaults.inc
+<% if vhost_part == :ssl -%>
+ Include include.d/ssl_defaults.inc
+<%= scope.function_templatewlv(['apache/vhosts/partials/ssl.erb',
+ {'configuration' => configuration}]) %>
+<% end -%>
+ ServerName <%= @servername %>
+<% unless @serveralias.empty? || (@serveralias == 'absent') -%>
+ ServerAlias <%= Array(@serveralias).sort.join(' ') %>
+<% end -%>
+<% unless @server_admin.empty? || (@server_admin == 'absent') -%>
+ ServerAdmin <%= @server_admin %>
+<% end -%>
+<% unless @documentroot == 'really_absent' -%>
+ DocumentRoot <%= @documentroot %>/
+<% end -%>
+<% if @default_charset != 'absent' -%>
+ AddDefaultCharset <%= @default_charset %>
+<% end -%>
+<% if @passing_extension != 'absent' -%>
+ DirectoryIndex index.htm index.html index.<%= @passing_extension %>
+<% end -%>
diff --git a/puppet/modules/apache/templates/vhosts/partials/logs.erb b/puppet/modules/apache/templates/vhosts/partials/logs.erb
new file mode 100644
index 00000000..4fe1b0a6
--- /dev/null
+++ b/puppet/modules/apache/templates/vhosts/partials/logs.erb
@@ -0,0 +1,18 @@
+<% case @logmode.to_s
+ when 'nologs' -%>
+ ErrorLog /dev/null
+ CustomLog /dev/null %%
+<% when 'noaccess' -%>
+ ErrorLog <%= @logdir %>/<%= @logprefix %>error_log
+ CustomLog /dev/null noip
+<% when 'semianonym' -%>
+ ErrorLog <%= @logdir %>/<%= @logprefix %>error_log
+ CustomLog <%= @logdir %>/<%= @logprefix %>access_log noip
+<% when 'anonym' -%>
+ ErrorLog /dev/null
+ CustomLog <%= @logdir %>/<%= @logprefix %>access_log noip
+<% else -%>
+ ErrorLog <%= @logdir %>/<%= @logprefix %>error_log
+ CustomLog <%= @logdir %>/<%= @logprefix %>access_log combined
+<% end -%>
+
diff --git a/puppet/modules/apache/templates/vhosts/partials/mod_security.erb b/puppet/modules/apache/templates/vhosts/partials/mod_security.erb
new file mode 100644
index 00000000..380e78f1
--- /dev/null
+++ b/puppet/modules/apache/templates/vhosts/partials/mod_security.erb
@@ -0,0 +1,27 @@
+ <IfModule mod_security2.c>
+<% if @mod_security.to_s == 'true' -%>
+ SecRuleEngine On
+<% if @mod_security_relevantonly.to_s == 'true' -%>
+ SecAuditEngine RelevantOnly
+<% else -%>
+ SecAuditEngine On
+<% end -%>
+<% else -%>
+ SecRuleEngine Off
+ SecAuditEngine Off
+<% end -%>
+ SecAuditLogType Concurrent
+ SecAuditLogStorageDir <%= @logdir %>/
+ SecAuditLog <%= @logdir %>/mod_security_audit.log
+ SecDebugLog <%= @logdir %>/mod_security_debug.log
+<% unless (disabled_rules=Array(@mod_security_rules_to_disable)).empty? -%>
+
+<% disabled_rules.each do |rule| -%>
+ SecRuleRemoveById "<%= rule %>"
+<% end -%>
+<% end -%>
+<% unless (s=@mod_security_additional_options).to_s == 'absent' -%>
+
+ <%= s %>
+<% end -%>
+ </IfModule>
diff --git a/puppet/modules/apache/templates/vhosts/partials/php_settings.erb b/puppet/modules/apache/templates/vhosts/partials/php_settings.erb
new file mode 100644
index 00000000..74f6ecf2
--- /dev/null
+++ b/puppet/modules/apache/templates/vhosts/partials/php_settings.erb
@@ -0,0 +1,20 @@
+<% if @run_mode != 'fcgid'
+ @php_settings.reject{|k,v| (v == :undef) || v.nil? }.keys.sort.each do |key|
+ dvalue = @php_settings[key].to_s.downcase
+ munged_value = if dvalue == 'true'
+ 'on'
+ elsif dvalue == 'false'
+ 'off'
+ elsif ['on','off'].include?(dvalue)
+ dvalue
+ else
+ @php_settings[key]
+ end
+
+ if ['on','off' ].include?(munged_value) -%>
+ php_admin_flag <%= key %> <%= munged_value %>
+<% else -%>
+ php_admin_value <%= key %> <%= munged_value %>
+<% end -%>
+<% end -%>
+<% end -%>
diff --git a/puppet/modules/apache/templates/vhosts/partials/ssl.erb b/puppet/modules/apache/templates/vhosts/partials/ssl.erb
new file mode 100644
index 00000000..c9f39333
--- /dev/null
+++ b/puppet/modules/apache/templates/vhosts/partials/ssl.erb
@@ -0,0 +1,8 @@
+ SSLCertificateFile <%= configuration['ssl_certificate_file'] || scope.lookupvar('apache::default_ssl_certificate_file') %>
+ SSLCertificateKeyFile <%= configuration['ssl_certificate_key_file'] || scope.lookupvar('apache::default_ssl_certificate_key_file') %>
+<% if configuration['ssl_certificate_chain_file'] || scope.lookupvar('apache::default_ssl_certificate_chain_file') != 'absent' -%>
+ SSLCertificateChainFile <%= configuration['ssl_certificate_chain_file'] || scope.lookupvar('apache::default_ssl_certificate_chain_file') %>
+<% end -%>
+<% if configuration['hsts'] -%>
+ Header add Strict-Transport-Security "max-age=<%= (configuration['hsts']['age'] || 15768000) rescue 15768000 %>"
+<% end -%>
diff --git a/puppet/modules/apache/templates/vhosts/partials/std_override_options.erb b/puppet/modules/apache/templates/vhosts/partials/std_override_options.erb
new file mode 100644
index 00000000..6d8b74f8
--- /dev/null
+++ b/puppet/modules/apache/templates/vhosts/partials/std_override_options.erb
@@ -0,0 +1,4 @@
+ AllowOverride <%= @allow_override %>
+<% if @options.to_s != 'absent' || @do_includes.to_s == 'true' || @run_mode == 'fcgid' -%>
+ Options <%- unless @options.to_s == 'absent' -%><%= @options %><% end -%><% if @do_includes.to_s == 'true' && !@options.include?('+Includes') -%> +Includes<% end -%><% if @run_mode == 'fcgid' && !@options.include?('+ExecCGI') -%> +ExecCGI<% end -%>
+<% end -%>
diff --git a/puppet/modules/apache/templates/vhosts/passenger/partial.erb b/puppet/modules/apache/templates/vhosts/passenger/partial.erb
new file mode 100644
index 00000000..c3b63f55
--- /dev/null
+++ b/puppet/modules/apache/templates/vhosts/passenger/partial.erb
@@ -0,0 +1,7 @@
+ SetEnv GEM_HOME <%= @gempath %>
+ <Directory <%= @documentroot %>/>
+ AllowOverride <%= @allow_override %>
+ Options <%- unless @options.to_s == 'absent' -%><%= @options %><%- end -%><%- unless !@options.to_s.include?('MultiViews') -%>-MultiViews<%- end -%>
+
+<%= scope.function_template(['apache/vhosts/partials/authentication.erb']) %>
+ </Directory>
diff --git a/puppet/modules/apache/templates/vhosts/perl/partial.erb b/puppet/modules/apache/templates/vhosts/perl/partial.erb
new file mode 100644
index 00000000..8c1f0a5a
--- /dev/null
+++ b/puppet/modules/apache/templates/vhosts/perl/partial.erb
@@ -0,0 +1,14 @@
+ <Directory "<%= @documentroot %>/">
+<%= scope.function_template(['apache/vhosts/partials/std_override_options.erb']) %>
+<%= scope.function_template(['apache/vhosts/partials/authentication.erb']) %>
+ </Directory>
+
+<% unless @htpasswd_file.to_s == 'absent' -%>
+ <Directory "<%= @cgi_binpath %>/">
+ AuthType Basic
+ AuthName "Access fuer <%= @servername %>"
+ AuthUserFile <%= @real_htpasswd_path %>
+ require valid-user
+ </Directory>
+<% end -%>
+ ScriptAlias /cgi-bin/ <%= @cgi_binpath %>/
diff --git a/puppet/modules/apache/templates/vhosts/php/partial.erb b/puppet/modules/apache/templates/vhosts/php/partial.erb
new file mode 100644
index 00000000..c19ae7b4
--- /dev/null
+++ b/puppet/modules/apache/templates/vhosts/php/partial.erb
@@ -0,0 +1,5 @@
+ <Directory "<%= @documentroot %>/">
+<%= scope.function_template(['apache/vhosts/partials/std_override_options.erb']) %>
+<%= scope.function_template(['apache/vhosts/partials/php_settings.erb']) %>
+<%= scope.function_template(['apache/vhosts/partials/authentication.erb']) %>
+ </Directory>
diff --git a/puppet/modules/apache/templates/vhosts/php_drupal/partial.erb b/puppet/modules/apache/templates/vhosts/php_drupal/partial.erb
new file mode 100644
index 00000000..316942fd
--- /dev/null
+++ b/puppet/modules/apache/templates/vhosts/php_drupal/partial.erb
@@ -0,0 +1,22 @@
+ <Directory "<%= @documentroot %>/">
+<%= scope.function_template(['apache/vhosts/partials/std_override_options.erb']) %>
+<%= scope.function_template(['apache/vhosts/partials/authentication.erb']) %>
+<%= scope.function_template(['apache/vhosts/partials/php_settings.erb']) %>
+ # Protect files and directories from prying eyes.
+ <FilesMatch "\.(engine|inc|info|install|module|profile|po|sh|.*sql|theme|tpl(\.php)?|xtmpl)$|^(code-style\.pl|Entries.*|Repository|Root|Tag|Template)$">
+ Order allow,deny
+ </FilesMatch>
+
+ # Customized error messages.
+ ErrorDocument 404 /index.php
+
+ RewriteEngine on
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]
+ </Directory>
+ <Directory "<%= @documentroot %>/files/">
+ SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006
+ Options None
+ Options +FollowSymLinks
+ </Directory>
diff --git a/puppet/modules/apache/templates/vhosts/php_gallery2/partial.erb b/puppet/modules/apache/templates/vhosts/php_gallery2/partial.erb
new file mode 100644
index 00000000..218c0e71
--- /dev/null
+++ b/puppet/modules/apache/templates/vhosts/php_gallery2/partial.erb
@@ -0,0 +1,14 @@
+ <Directory "<%= @documentroot %>/">
+<%= scope.function_template(['apache/vhosts/partials/std_override_options.erb']) %>
+<%= scope.function_template(['apache/vhosts/partials/php_settings.erb']) %>
+<%= scope.function_template(['apache/vhosts/partials/authentication.erb']) %>
+
+ # Always rewrite login's
+ # Source: http://gallery.menalto.com/node/30558
+ RewriteEngine On
+ RewriteCond %{HTTPS} !=on
+ RewriteCond %{HTTP:X-Forwarded-Proto} !=https
+ RewriteCond %{HTTP_COOKIE} ^GALLERYSID= [OR]
+ RewriteCond %{QUERY_STRING} subView=core\.UserLogin
+ RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [NE,R,L]
+ </Directory>
diff --git a/puppet/modules/apache/templates/vhosts/php_joomla/partial.erb b/puppet/modules/apache/templates/vhosts/php_joomla/partial.erb
new file mode 100644
index 00000000..55445bfc
--- /dev/null
+++ b/puppet/modules/apache/templates/vhosts/php_joomla/partial.erb
@@ -0,0 +1,30 @@
+ <Directory "<%= @documentroot %>/">
+<%= scope.function_template(['apache/vhosts/partials/std_override_options.erb']) %>
+<%= scope.function_template(['apache/vhosts/partials/php_settings.erb']) %>
+<%= scope.function_template(['apache/vhosts/partials/authentication.erb']) %>
+
+ Include include.d/joomla.inc
+ </Directory>
+
+ <Directory "<%= @documentroot %>/administrator/">
+ RewriteEngine on
+
+ # Rewrite URLs to https that go for the admin area
+ RewriteCond %{REMOTE_ADDR} !^127\.[0-9]+\.[0-9]+\.[0-9]+$
+ RewriteCond %{HTTPS} !=on
+ RewriteCond %{REQUEST_URI} (.*/administrator/.*)
+ RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R]
+ </Directory>
+
+ # Deny various directories that
+ # shouldn't be webaccessible
+ <Directory "<%= @documentroot %>/tmp/">
+ Deny From All
+ </Directory>
+ <Directory "<%= @documentroot %>/logs/">
+ Deny From All
+ </Directory>
+ <Directory "<%= @documentroot %>/cli/">
+ Deny From All
+ </Directory>
+
diff --git a/puppet/modules/apache/templates/vhosts/php_mediawiki/partial.erb b/puppet/modules/apache/templates/vhosts/php_mediawiki/partial.erb
new file mode 100644
index 00000000..1ed6ee3e
--- /dev/null
+++ b/puppet/modules/apache/templates/vhosts/php_mediawiki/partial.erb
@@ -0,0 +1,7 @@
+<% if @run_mode == 'fcgid' -%>
+ RewriteEngine On
+ RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
+ RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-d
+ RewriteRule ^/?index.php/(.*)$ /index.php?title=$1 [PT,L,QSA]
+<% end -%>
+<%= scope.function_template(['apache/vhosts/php/partial.erb']) %>
diff --git a/puppet/modules/apache/templates/vhosts/php_silverstripe/partial.erb b/puppet/modules/apache/templates/vhosts/php_silverstripe/partial.erb
new file mode 100644
index 00000000..75a81931
--- /dev/null
+++ b/puppet/modules/apache/templates/vhosts/php_silverstripe/partial.erb
@@ -0,0 +1,12 @@
+ # silverstripe
+ RewriteEngine On
+ RewriteCond %{HTTPS} !=on
+ RewriteCond %{HTTP:X-Forwarded-Proto} !=https
+ RewriteRule /(Security|admin)(.*) https://%{HTTP_HOST}/admin$1$2 [L,R,NE]
+
+ <Directory "<%= @documentroot %>/">
+<%= scope.function_template(['apache/vhosts/partials/std_override_options.erb']) %>
+<%= scope.function_template(['apache/vhosts/partials/php_settings.erb']) %>
+<%= scope.function_template(['apache/vhosts/partials/authentication.erb']) %>
+ Include include.d/silverstripe.inc
+ </Directory>
diff --git a/puppet/modules/apache/templates/vhosts/php_typo3/partial.erb b/puppet/modules/apache/templates/vhosts/php_typo3/partial.erb
new file mode 100644
index 00000000..afb756df
--- /dev/null
+++ b/puppet/modules/apache/templates/vhosts/php_typo3/partial.erb
@@ -0,0 +1,10 @@
+<%= scope.function_template(['apache/vhosts/php/partial.erb']) %>
+ <Directory "<%= @documentroot %>/typo3/">
+ RewriteEngine on
+
+ # Rewrite URLs to https that go for the admin area
+ RewriteCond %{HTTPS} !=on
+ RewriteCond %{HTTP:X-Forwarded-Proto} !=https
+ RewriteCond %{REQUEST_URI} (.*/typo3/.*)
+ RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [L,R,NE]
+ </Directory>
diff --git a/puppet/modules/apache/templates/vhosts/php_wordpress/partial.erb b/puppet/modules/apache/templates/vhosts/php_wordpress/partial.erb
new file mode 100644
index 00000000..5e6ebd5e
--- /dev/null
+++ b/puppet/modules/apache/templates/vhosts/php_wordpress/partial.erb
@@ -0,0 +1,19 @@
+<%= scope.function_template(['apache/vhosts/php/partial.erb']) %>
+
+ # fixes: http://git.zx2c4.com/w3-total-fail/tree/w3-total-fail.sh
+ <Directory "<%= @documentroot %>/wp-content/w3tc/dbcache">
+ Deny From All
+ </Directory>
+
+ # simple wp-login brute force protection
+ # http://www.frameloss.org/2013/04/26/even-easier-brute-force-login-protection-for-wordpress/
+ RewriteEngine On
+ RewriteCond %{HTTP_COOKIE} !<%= cookie = scope.function_sha1([scope.function_fqdn_rand([9999999999999,@name]).to_s + "cookie"]) %>
+ RewriteRule ^/wp-login.php /wordpress-login-<%= tmpuri = scope.function_sha1([scope.function_fqdn_rand([9999999999999,@name]).to_s + "wp-login"]) %>.php [R,L]
+ <Location /wordpress-login-<%= tmpuri %>.php>
+ CookieTracking on
+ CookieExpires 30
+ CookieName <%= cookie %>
+ </Location>
+ RewriteRule ^/wordpress-login-<%= tmpuri %>.php /wp-login.php [NE]
+
diff --git a/puppet/modules/apache/templates/vhosts/proxy/partial.erb b/puppet/modules/apache/templates/vhosts/proxy/partial.erb
new file mode 100644
index 00000000..0eecf820
--- /dev/null
+++ b/puppet/modules/apache/templates/vhosts/proxy/partial.erb
@@ -0,0 +1,8 @@
+ <Proxy *>
+ Order deny,allow
+ Allow from all
+<%= scope.function_template(['apache/vhosts/partials/authentication.erb']) %>
+ </Proxy>
+ ProxyRequests Off
+ ProxyPass / <%= @options %>/
+ ProxyPassReverse / <%= @options %>/
diff --git a/puppet/modules/apache/templates/vhosts/redirect/partial.erb b/puppet/modules/apache/templates/vhosts/redirect/partial.erb
new file mode 100644
index 00000000..c8d7d11e
--- /dev/null
+++ b/puppet/modules/apache/templates/vhosts/redirect/partial.erb
@@ -0,0 +1 @@
+ Redirect permanent / https://<%= @options %>
diff --git a/puppet/modules/apache/templates/vhosts/static/partial.erb b/puppet/modules/apache/templates/vhosts/static/partial.erb
new file mode 100644
index 00000000..dc6f11ca
--- /dev/null
+++ b/puppet/modules/apache/templates/vhosts/static/partial.erb
@@ -0,0 +1,4 @@
+ <Directory "<%= @documentroot %>/">
+<%= scope.function_template(['apache/vhosts/partials/std_override_options.erb']) %>
+<%= scope.function_template(['apache/vhosts/partials/authentication.erb']) %>
+ </Directory>
diff --git a/puppet/modules/apache/templates/vhosts/webdav/partial.erb b/puppet/modules/apache/templates/vhosts/webdav/partial.erb
new file mode 100644
index 00000000..09ce632f
--- /dev/null
+++ b/puppet/modules/apache/templates/vhosts/webdav/partial.erb
@@ -0,0 +1,21 @@
+ DAVLockDB <%= @real_dav_db_dir %>/DAVLock
+ <Directory "<%= @documentroot %>/">
+ Dav on
+ AllowOverride None
+<% if @options.to_s != 'absent' || @do_includes.to_s == 'true' -%>
+ Options <% unless @options.to_s == 'absent' -%><%= @options %><% end -%><% unless @options.include?('Indexes') -%> Indexes<%- end -%>
+
+<% else -%>
+ Options Indexes
+
+<% end -%>
+<%= scope.function_template(['apache/vhosts/partials/authentication.erb']) %>
+<% if @ldap_auth.to_s == 'true' then -%>
+ Include include.d/ldap_auth.inc
+<% unless ldap_user.to_s == 'any' -%>
+ Require ldap-user <%= ldap_user.to_s %>
+<% else -%>
+ Require valid-user
+<% end
+ end -%>
+ </Directory>
diff --git a/puppet/modules/apache/templates/webfiles/autoconfig/config.shtml.erb b/puppet/modules/apache/templates/webfiles/autoconfig/config.shtml.erb
new file mode 100644
index 00000000..3a3d6bb5
--- /dev/null
+++ b/puppet/modules/apache/templates/webfiles/autoconfig/config.shtml.erb
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--#if expr="$SERVER_NAME = /([^.]*\.[^.]*)$/" -->
+ <!--#set var="DOMAIN" value="$1" -->
+<!--#endif -->
+
+<clientConfig version="1.1">
+ <emailProvider id="<%= @provider %>">
+ <domain><!--#echo var="DOMAIN" --></domain>
+ <displayName><%= @display_name || @provider %> Mail (<!--#echo var="DOMAIN" -->)</displayName>
+ <displayShortName><%= @shortname || @provider.split('.').first %></displayShortName>
+ <incomingServer type="imap">
+ <hostname><%= @imap_server || "imap.#{@provider}" %></hostname>
+ <port>143</port>
+ <socketType>STARTTLS</socketType>
+ <authentication>password-cleartext</authentication>
+ <username>%EMAILADDRESS%</username>
+ </incomingServer>
+ <incomingServer type="imap">
+ <hostname><%= @imap_server || "imap.#{@provider}" %></hostname>
+ <port>993</port>
+ <socketType>SSL</socketType>
+ <authentication>password-cleartext</authentication>
+ <username>%EMAILADDRESS%</username>
+ </incomingServer>
+ <incomingServer type="pop3">
+ <hostname><%= @pop_server || "pop.#{@provider}" %></hostname>
+ <port>110</port>
+ <socketType>STARTTLS</socketType>
+ <authentication>password-cleartext</authentication>
+ <username>%EMAILADDRESS%</username>
+ </incomingServer>
+ <incomingServer type="pop3">
+ <hostname><%= @pop_server || "pop.#{@provider}" %></hostname>
+ <port>995</port>
+ <socketType>SSL</socketType>
+ <authentication>password-cleartext</authentication>
+ <username>%EMAILADDRESS%</username>
+ </incomingServer>
+ <outgoingServer type="smtp">
+ <hostname><%= @smtp_server || "smtp.#{@provider}" %></hostname>
+ <port>587</port>
+ <socketType>STARTTLS</socketType>
+ <authentication>password-cleartext</authentication>
+ <username>%EMAILADDRESS%</username>
+ </outgoingServer>
+ <outgoingServer type="smtp">
+ <hostname><%= @smtp_server || "smtp.#{@provider}"%></hostname>
+ <port>465</port>
+ <socketType>SSL</socketType>
+ <authentication>password-cleartext</authentication>
+ <username>%EMAILADDRESS%</username>
+ </outgoingServer>
+ <documentation url="<%= @documentation_url || "http://#{@provider}" %>">
+ <descr lang="de">Allgemeine Beschreibung der Einstellungen</descr>
+ <descr lang="en">Generic settings page</descr>
+ </documentation>
+ </emailProvider>
+</clientConfig>
diff --git a/puppet/modules/apt/.gitignore b/puppet/modules/apt/.gitignore
new file mode 100644
index 00000000..a54aa971
--- /dev/null
+++ b/puppet/modules/apt/.gitignore
@@ -0,0 +1,12 @@
+/pkg/
+/Gemfile.lock
+/vendor/
+/spec/fixtures/manifests/*
+/spec/fixtures/modules/*
+!/spec/fixtures/modules/apt
+!/spec/fixtures/modules/apt/*
+/.vagrant/
+/.bundle/
+/coverage/
+/.idea/
+*.iml
diff --git a/puppet/modules/apt/.gitlab-ci.yml b/puppet/modules/apt/.gitlab-ci.yml
new file mode 100644
index 00000000..f7b8ecad
--- /dev/null
+++ b/puppet/modules/apt/.gitlab-ci.yml
@@ -0,0 +1,12 @@
+before_script:
+ - ruby -v
+ - gem install bundler --no-ri --no-rdoc
+ - bundle install --jobs $(nproc) "${FLAGS[@]}"
+
+# don't fail on lint warnings
+rspec:
+ script:
+ - bundle exec rake lint || /bin/true
+ - bundle exec rake syntax
+ - bundle exec rake validate
+ - bundle exec rake spec
diff --git a/puppet/modules/apt/Gemfile b/puppet/modules/apt/Gemfile
new file mode 100644
index 00000000..8925a904
--- /dev/null
+++ b/puppet/modules/apt/Gemfile
@@ -0,0 +1,13 @@
+source "https://rubygems.org"
+
+group :test do
+ gem "rake"
+ gem "rspec", '< 3.2.0'
+ gem "puppet", ENV['PUPPET_VERSION'] || ENV['GEM_PUPPET_VERSION'] || ENV['PUPPET_GEM_VERSION'] || '~> 3.7.0'
+ gem "facter", ENV['FACTER_VERSION'] || ENV['GEM_FACTER_VERSION'] || ENV['FACTER_GEM_VERSION'] || '~> 2.2.0'
+ gem "rspec-puppet"
+ gem "puppetlabs_spec_helper"
+ gem "metadata-json-lint"
+ gem "rspec-puppet-facts"
+ gem "mocha"
+end
diff --git a/puppet/modules/apt/LICENSE b/puppet/modules/apt/LICENSE
new file mode 100644
index 00000000..94a9ed02
--- /dev/null
+++ b/puppet/modules/apt/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/puppet/modules/apt/README b/puppet/modules/apt/README
new file mode 100644
index 00000000..00db7d8e
--- /dev/null
+++ b/puppet/modules/apt/README
@@ -0,0 +1,602 @@
+
+Overview
+========
+
+This module manages apt on Debian.
+
+It keeps dpkg's and apt's databases as well as the keyrings for securing
+package download current.
+
+backports.debian.org is added.
+
+/etc/apt/sources.list and /etc/apt/preferences are managed. More
+recent Debian releases are pinned to very low values by default to
+prevent accidental upgrades.
+
+Ubuntu support is lagging behind but not absent either.
+
+! Upgrade Notice !
+
+ * The `disable_update` parameter has been removed. The main apt class
+ defaults to *not* run an `apt-get update` on every run anyway so this
+ parameter seems useless.
+ You can include the `apt::update` class if you want it to be run every time.
+
+ * The `apt::upgrade_package` now doesn't automatically call an Exec['apt_updated']
+ anymore, so you would need to include `apt::update` now by hand.
+
+ * The apt::codename parameter has been removed. In its place, the
+ debian_codename fact may be overridden via an environment variable. This
+ will affect all other debian_* facts, and achieve the same result.
+
+ FACTER_debian_codename=jessie puppet agent -t
+
+ * If you were using custom 50unattended-upgrades.${::lsbdistcodename} in your
+ site_apt, these are no longer supported. You should migrate to passing
+ $blacklisted_packages to the apt::unattended_upgrades class.
+
+ * the apt class has been moved to a paramterized class. if you were including
+ this class before, after passing some variables, you will need to move to
+ instantiating the class with those variables instead. For example, if you
+ had the following in your manifests:
+
+ $apt_debian_url = 'http://localhost:9999/debian/'
+ $apt_use_next_release = true
+ include apt
+
+ you will need to remove the variables, and the include and instead do
+ the following:
+
+ class { 'apt': debian_url => 'http://localhost:9999/debian/', use_next_release => true }
+
+ previously, you could manually set $lsbdistcodename which would enable forced
+ upgrades, but because this is a top-level facter variable, and newer puppet
+ versions do not let you assign variables to other namespaces, this is no
+ longer possible. However, there is a way to obtain this functionality, and
+ that is to pass the 'codename' parameter to the apt class, which will change
+ the sources.list and preferences files to be the codename you set, allowing
+ you to trigger upgrades:
+
+ include apt::dist_upgrade
+ class { 'apt': codename => 'wheezy', notify => Exec['apt_dist-upgrade'] }
+
+ * the apticron class has been moved to a parameterized class. if you were
+ including this class before, you will need to move to instantiating the
+ class instead. For example, if you had the following in your manifests:
+
+ $apticron_email = 'foo@example.com'
+ $apticron_notifynew = '1'
+ ... any $apticron_* variables
+ include apticron
+
+ you will need to remove the variables, and the include and instead do the
+ following:
+
+ class { 'apt::apticron': email => 'foo@example.com', notifynew => '1' }
+
+ * the apt::listchanges class has been moved to a paramterized class. if you
+ were including this class before, after passing some variables, you will need
+ to move to instantiating the class with those variables instead. For example,
+ if you had the following in your manifests:
+
+ $apt_listchanges_email = 'foo@example.com'
+ ... any $apt_listchanges_* variables
+ include apt::listchanges
+
+ you will need to remove the variables, and the include and instead do the
+ following:
+
+ class { 'apt::listchanges': email => 'foo@example.com' }
+
+ * the apt::proxy_client class has been moved to a paramterized class. if you
+ were including this class before, after passing some variables, you will need
+ to move to instantiating the class with those variables instead. For example,
+ if you had the following in your manifests:
+
+ $apt_proxy = 'http://proxy.domain'
+ $apt_proxy_port = 666
+ include apt::proxy_client
+
+ you will need to remove the variables, and the include and instead do the
+ following:
+
+ class { 'apt::proxy_client': proxy => 'http://proxy.domain', port => '666' }
+
+Requirements
+============
+
+This module needs:
+
+- the lsb-release package should be installed on the server prior to running
+ puppet. otherwise, all of the $::lsb* facts will be empty during runs.
+- the common module: https://gitlab.com/shared-puppet-modules-group/common
+
+By default, on normal hosts, this module sets the configuration option
+DSelect::Clean to 'auto'. On virtual servers, the value is set by default to
+'pre-auto', because virtual servers are usually more space-bound and have better
+recovery mechanisms via the host:
+
+From apt.conf(5), 0.7.2:
+ "Cache Clean mode; this value may be one of always, prompt, auto,
+ pre-auto and never. always and prompt will remove all packages
+ from the cache after upgrading, prompt (the default) does so
+ conditionally. auto removes only those packages which are no
+ longer downloadable (replaced with a new version for
+ instance). pre-auto performs this action before downloading new
+ packages."
+
+To change the default setting for DSelect::Clean, you can create a file named
+"03clean" or "03clean_vserver" in your site_apt module's files directory. You
+can also define this for a specific host by creating a file in a subdirectory of
+the site_apt modules' files directory that is named the same as the
+host. (example: site_apt/files/some.host.com/03clean, or
+site_apt/files/some.host.com/03clean_vserver)
+
+Classes
+=======
+
+apt
+---
+
+The apt class sets up most of the documented functionality. To use functionality
+that is not enabled by default, you must set one of the following parameters.
+
+Example usage:
+
+ class { 'apt': use_next_release => true, debian_url => 'http://localhost:9999/debian/' }
+
+Class parameters:
+
+* use_lts
+
+ If this variable is set to true the CODENAME-lts sources (such as
+ squeeze-lts) are added.
+
+ By default this is false for backward compatibility with older
+ versions of this module.
+
+* use_volatile
+
+ If this variable is set to true the CODENAME-updates sources (such as
+ squeeze-updates) are added.
+
+ By default this is false for backward compatibility with older
+ versions of this module.
+
+* include_src
+
+ If this variable is set to true a deb-src source is added for every
+ added binary archive source.
+
+ By default this is false for backward compatibility with older
+ versions of this module.
+
+* use_next_release
+
+ If this variable is set to true the sources for the next Debian
+ release are added. The default pinning configuration pins it to very
+ low values.
+
+ By default this is false for backward compatibility with older
+ versions of this module.
+
+* debian_url, security_url, backports_url, volatile_url
+
+ These variables allow to override the default APT mirrors respectively
+ used for the standard Debian archives, the Debian security archive,
+ the Debian official backports and the Debian Volatile archive.
+
+* ubuntu_url
+
+ These variables allows to override the default APT mirror used for all
+ standard Ubuntu archives (including updates, security, backports).
+
+* repos
+
+ If this variable is set the default repositories list ("main contrib non-free")
+ is overriden.
+
+* disable_update
+
+ Disable "apt-get update" which is normally triggered by apt::upgrade_package
+ and apt::dist_upgrade.
+
+ Note that nodes can be updated once a day by using
+ APT::Periodic::Update-Package-Lists "1";
+ in i.e. /etc/apt/apt.conf.d/80_apt_update_daily.
+
+* custom_preferences
+
+ For historical reasons (Debian Lenny's version of APT did not support the use
+ of the preferences.d directory for putting fragments of 'preferences'), this
+ module will manage a default generic apt/preferences file with more
+ recent releases pinned to very low values so that any package
+ installation will not accidentally pull in packages from those suites
+ unless you explicitly specify the version number. This file will be
+ complemented with all of the preferences_snippet calls (see below).
+
+ If the default preferences template doesn't suit your needs, you can create a
+ template located in your site_apt module, and set custom_preferences with the
+ content (eg. custom_preferences => template('site_apt/preferences') )
+
+ Setting this variable to false before including this class will force the
+ apt/preferences file to be absent:
+
+ class { 'apt': custom_preferences => false }
+
+* custom_sources_list
+
+ By default this module will use a basic apt/sources.list template with
+ a generic Debian mirror. If you need to set more specific sources,
+ e.g. changing the sections included in the source, etc. you can set
+ this variable to the content that you desire to use instead.
+
+ For example, setting this variable will pull in the
+ templates/site_apt/sources.list file:
+
+ class { 'apt': custom_sources_list => template('site_apt/sources.list') }
+
+* custom_key_dir
+
+ If you have different apt-key files that you want to get added to your
+ apt keyring, you can set this variable to a path in your fileserver
+ where individual key files can be placed. If this is set and keys
+ exist there, this module will 'apt-key add' each key.
+
+ The debian-archive-keyring package is installed and kept current up to the
+ latest revision (this includes the backports archive keyring).
+
+apt::apticron
+-------------
+
+When you instantiate this class, apticron will be installed, with the following
+defaults, which you are free to change:
+
+ $ensure_version = 'installed',
+ $config = "apt/${::operatingsystem}/apticron_${::lsbdistcodename}.erb",
+ $email = 'root',
+ $diff_only = '1',
+ $listchanges_profile = 'apticron',
+ $system = false,
+ $ipaddressnum = false,
+ $ipaddresses = false,
+ $notifyholds = '0',
+ $notifynew = '0',
+ $customsubject = ''
+
+Example usage:
+
+ class { 'apt::apticron': email => 'foo@example.com', notifynew => '1' }
+
+apt::cron::download
+-------------------
+
+This class sets up cron-apt so that it downloads upgradable packages, does not
+actually do any upgrade and emails when the output changes.
+
+cron-apt defaults to run at 4 AM. You may want to set the
+$apt_cron_hours variable before you include the class: its value will
+be passed as the "hours" parameter of a cronjob. Example:
+
+ # Run cron-apt every three hours
+ $apt_cron_hours = '*/3'
+
+Note that the default 4 AM cronjob won't be disabled.
+
+apt::cron::dist_upgrade
+-----------------------
+
+This class sets up cron-apt so that it dist-upgrades the system and
+emails when upgrades are performed.
+
+See apt::cron::download above if you need to run cron-apt more often
+than once a day.
+
+apt::dist_upgrade
+-----------------
+
+This class provides the Exec['apt_dist-upgrade'] resource that
+dist-upgrade's the system.
+
+This exec is set as refreshonly so including this class does not
+trigger any action per-se: other resources may notify it, other
+classes may inherit from this one and add to its subscription list
+using the plusignment ('+>') operator. A real-world example can be
+seen in the apt::dist_upgrade::initiator source.
+
+apt::dist_upgrade::initiator
+----------------------------
+
+This class automatically dist-upgrade's the system when an initiator
+file's content changes. The initiator file is copied from the first
+available source amongst the following ones, in decreasing priority
+order:
+
+- puppet:///modules/site_apt/${::fqdn}/upgrade_initiator
+- puppet:///modules/site_apt/upgrade_initiator
+- puppet:///modules/apt/upgrade_initiator
+
+This is useful when one does not want to setup a fully automated
+upgrade process but still needs a way to manually trigger full
+upgrades of any number of systems at scheduled times.
+
+Beware: a dist-upgrade is triggered the first time Puppet runs after
+this class has been included. This is actually the single reason why
+this class is not enabled by default.
+
+When this class is included the APT indexes are updated on every
+Puppet run due to the author's lack of Puppet wizardry.
+
+apt::dselect
+------------
+
+This class, when included, installs dselect and switches it to expert mode to
+suppress superfluous help screens.
+
+apt::listchanges
+----------------
+
+This class, when instantiated, installs apt-listchanges and configures it using
+the following parameterized variables, which can be changed:
+
+ version = 'present'
+ config = "apt/${::operatingsystem}/listchanges_${::lsbrelease}.erb"
+ frontend = 'pager'
+ email = 'root'
+ confirm = 0
+ saveseen = '/var/lib/apt/listchanges.db'
+ which = 'both'
+
+ Example usage:
+ class { 'apt::listchanges': email => 'foo@example.com' }
+
+apt::proxy_client
+-----------------
+
+This class adds the right configuration to apt to make it fetch packages via a
+proxy. The class parameters apt_proxy and apt_proxy_port need to be set:
+
+You can set the 'proxy' class parameter variable to the URL of the proxy that
+will be used. By default, the proxy will be queried on port 3142, but you can
+change the port number by setting the 'port' class parameter.
+
+Example:
+
+ class { 'apt::proxy_client': proxy => 'http://proxy.domain', port => '666' }
+
+apt::reboot_required_notify
+---------------------------
+
+This class installs a daily cronjob that checks if a package upgrade
+requires the system to be rebooted; if so, cron sends a notification
+email to root.
+
+apt::unattended_upgrades
+------------------------
+
+If this class is included, it will install the package 'unattended-upgrades'
+and configure it to daily upgrade the system.
+
+The class has the following parameters that you can use to change the contents
+of the configuration file. The values shown here are the default values:
+
+ * $config_content = undef
+ * $config_template = 'apt/50unattended-upgrades.erb'
+ * $mailonlyonerror = true
+ * $mail_recipient = 'root'
+ * $blacklisted_packages = []
+
+Note that using $config_content actually specifies all of the configuration
+contents and thus makes the other parameters useless.
+
+example:
+
+ class { 'apt::unattended_upgrades':
+ config_template => 'site_apt/50unattended-upgrades.jessie',
+ blacklisted_packages => [
+ 'libc6', 'libc6-dev', 'libc6-i686', 'mysql-server', 'redmine', 'nodejs',
+ 'bird'
+ ],
+ }
+
+Defines
+=======
+
+apt::apt_conf
+-------------
+
+Creates a file in the apt/apt.conf.d directory to easily add configuration
+components. One can use either the 'source' meta-parameter to specify a list of
+static files to include from the puppet fileserver or the 'content'
+meta-parameter to define content inline or with the help of a template.
+
+Example:
+
+ apt::apt_conf { '80download-only':
+ source => 'puppet:///modules/site_apt/80download-only',
+ }
+
+apt::preferences_snippet
+------------------------
+
+A way to add pinning information to files in /etc/apt/preferences.d/
+
+Example:
+
+ apt::preferences_snippet {
+ 'irssi-plugin-otr':
+ release => 'squeeze-backports',
+ priority => 999;
+ }
+
+ apt::preferences_snippet {
+ 'unstable_fallback':
+ package => '*',
+ release => 'unstable',
+ priority => 1;
+ }
+
+ apt::preferences_snippet {
+ 'ttdnsd':
+ pin => 'origin deb.torproject.org',
+ priority => 999;
+ }
+
+The names of the resources will be used as the names of the files in the
+preferences.d directory, so you should ensure that resource names follow the
+prescribed naming scheme.
+
+From apt_preferences(5):
+ Note that the files in the /etc/apt/preferences.d directory are parsed in
+ alphanumeric ascending order and need to obey the following naming
+ convention: The files have no or "pref" as filename extension and which
+ only contain alphanumeric, hyphen (-), underscore (_) and period (.)
+ characters - otherwise they will be silently ignored.
+
+apt::preseeded_package
+----------------------
+
+This simplifies installation of packages for which you wish to preseed the
+answers to debconf. For example, if you wish to provide a preseed file for the
+locales package, you would place the locales.seed file in
+'site_apt/templates/${::lsbdistcodename}/locales.seeds' and then include the
+following in your manifest:
+
+ apt::preseeded_package { locales: }
+
+You can also specify the content of the seed via the content parameter,
+for example:
+
+ apt::preseeded_package { 'apticron':
+ content => 'apticron apticron/notification string root@example.com',
+ }
+
+apt::sources_list
+-----------------
+
+Creates a file in the apt/sources.list.d directory to easily add additional apt
+sources. One can use either the 'source' meta-parameter to specify a list of
+static files to include from the puppet fileserver or the 'content'
+meta-parameter to define content inline or with the help of a template. Ending
+the resource name in '.list' is optional: it will be automatically added to the
+file name if not present in the resource name.
+
+Example:
+
+ apt::sources_list { 'company_internals':
+ source => [ "puppet:///modules/site_apt/${::fqdn}/company_internals.list",
+ 'puppet:///modules/site_apt/company_internals.list' ],
+ }
+
+apt::key
+--------
+
+Deploys a secure apt OpenPGP key. This usually accompanies the
+sources.list snippets above for third party repositories. For example,
+you would do:
+
+ apt::key { 'neurodebian.gpg':
+ ensure => present,
+ source => 'puppet:///modules/site_apt/neurodebian.gpg',
+ }
+
+This deploys the key in the `/etc/apt/trusted.gpg.d` directory, which
+is assumed by secure apt to be binary OpenPGP keys and *not*
+"ascii-armored" or "plain text" OpenPGP key material. For the latter,
+use `apt::key::plain`.
+
+The `.gpg` extension is compulsory for `apt` to pickup the key properly.
+
+apt::key::plain
+---------------
+
+Deploys a secure apt OpenPGP key. This usually accompanies the
+sources.list snippets above for third party repositories. For example,
+you would do:
+
+ apt::key::plain { 'neurodebian.asc':
+ source => 'puppet:///modules/site_apt/neurodebian.asc',
+ }
+
+This deploys the key in the `${apt_base_dir}/keys` directory (as
+opposed to `$custom_key_dir` which deploys it in `keys.d`). The reason
+this exists on top of `$custom_key_dir` is to allow a more
+decentralised distribution of those keys, without having all modules
+throw their keys in the same directory in the manifests.
+
+Note that this model does *not* currently allow keys to be removed!
+Use `apt::key` instead for a more practical, revokable approach, but
+that needs binary keys.
+
+apt::upgrade_package
+--------------------
+
+This simplifies upgrades for DSA security announcements or point-releases. This
+will ensure that the named package is upgraded to the version specified, only if
+the package is installed, otherwise nothing happens. If the specified version
+is 'latest' (the default), then the package is ensured to be upgraded to the
+latest package revision when it becomes available.
+
+For example, the following upgrades the perl package to version 5.8.8-7etch1
+(if it is installed), it also upgrades the syslog-ng and perl-modules packages
+to their latest (also, only if they are installed):
+
+upgrade_package { 'perl':
+ version => '5.8.8-7etch1';
+ 'syslog-ng':
+ version => latest;
+ 'perl-modules':
+}
+
+Resources
+=========
+
+File['apt_config']
+------------------
+
+Use this resource to depend on or add to a completed apt configuration
+
+Exec['apt_updated']
+-------------------
+
+After this point the APT indexes are up-to-date.
+This resource is set to `refreshonly => true` so it is not run on
+every puppetrun. To run this every time, you can include the `apt::update`
+class.
+
+This resource is usually used like this to ensure current packages are
+installed by Package resources:
+
+ include apt::update
+ Package { require => Exec['apt_updated'] }
+
+Note that nodes can be updated once a day by using
+
+ APT::Periodic::Update-Package-Lists "1";
+
+in i.e. /etc/apt/apt.conf.d/80_apt_update_daily.
+
+
+Tests
+=====
+
+To run pupept rspec tests:
+
+ bundle install --path vendor/bundle
+ bundle exec rake spec
+
+Using different facter/puppet versions:
+
+ FACTER_GEM_VERSION=1.6.10 PUPPET_GEM_VERSION=2.7.23 bundle install --path vendor/bundle
+ bundle exec rake spec
+
+Licensing
+=========
+
+This puppet module is licensed under the GPL version 3 or later. Redistribution
+and modification is encouraged.
+
+The GPL version 3 license text can be found in the "LICENSE" file accompanying
+this puppet module, or at the following URL:
+
+http://www.gnu.org/licenses/gpl-3.0.html
diff --git a/puppet/modules/apt/Rakefile b/puppet/modules/apt/Rakefile
new file mode 100644
index 00000000..85326bb4
--- /dev/null
+++ b/puppet/modules/apt/Rakefile
@@ -0,0 +1,19 @@
+require 'puppetlabs_spec_helper/rake_tasks'
+require 'puppet-lint/tasks/puppet-lint'
+PuppetLint.configuration.send('disable_80chars')
+PuppetLint.configuration.ignore_paths = ["spec/**/*.pp", "pkg/**/*.pp"]
+
+desc "Validate manifests, templates, and ruby files"
+task :validate do
+ Dir['manifests/**/*.pp'].each do |manifest|
+ sh "puppet parser validate --noop #{manifest}"
+ end
+ Dir['spec/**/*.rb','lib/**/*.rb'].each do |ruby_file|
+ sh "ruby -c #{ruby_file}" unless ruby_file =~ /spec\/fixtures/
+ end
+ Dir['templates/**/*.erb'].each do |template|
+ sh "erb -P -x -T '-' #{template} | ruby -c"
+ end
+end
+
+task :test => [:lint, :syntax , :validate, :spec]
diff --git a/puppet/modules/apt/files/02show_upgraded b/puppet/modules/apt/files/02show_upgraded
new file mode 100644
index 00000000..bb127d41
--- /dev/null
+++ b/puppet/modules/apt/files/02show_upgraded
@@ -0,0 +1,4 @@
+// This file is managed by Puppet
+// all local modifications will be overwritten
+
+APT::Get::Show-Upgraded true;
diff --git a/puppet/modules/apt/files/03clean b/puppet/modules/apt/files/03clean
new file mode 100644
index 00000000..3d20924a
--- /dev/null
+++ b/puppet/modules/apt/files/03clean
@@ -0,0 +1,4 @@
+// This file is managed by Puppet
+// all local modifications will be overwritten
+
+DSelect::Clean auto;
diff --git a/puppet/modules/apt/files/03clean_vserver b/puppet/modules/apt/files/03clean_vserver
new file mode 100644
index 00000000..6bb84e58
--- /dev/null
+++ b/puppet/modules/apt/files/03clean_vserver
@@ -0,0 +1,4 @@
+// This file is managed by Puppet
+// all local modifications will be overwritten
+
+DSelect::Clean pre-auto;
diff --git a/puppet/modules/apt/files/upgrade_initiator b/puppet/modules/apt/files/upgrade_initiator
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/puppet/modules/apt/files/upgrade_initiator
@@ -0,0 +1 @@
+
diff --git a/puppet/modules/apt/lib/facter/apt_running.rb b/puppet/modules/apt/lib/facter/apt_running.rb
new file mode 100644
index 00000000..e8f2156e
--- /dev/null
+++ b/puppet/modules/apt/lib/facter/apt_running.rb
@@ -0,0 +1,7 @@
+Facter.add("apt_running") do
+ setcode do
+ #Facter::Util::Resolution.exec('/usr/bin/dpkg -s mysql-server >/dev/null 2>&1 && echo true || echo false')
+ Facter::Util::Resolution.exec('pgrep apt-get >/dev/null 2>&1 && echo true || echo false')
+ end
+end
+
diff --git a/puppet/modules/apt/lib/facter/debian_codename.rb b/puppet/modules/apt/lib/facter/debian_codename.rb
new file mode 100644
index 00000000..254877aa
--- /dev/null
+++ b/puppet/modules/apt/lib/facter/debian_codename.rb
@@ -0,0 +1,42 @@
+begin
+ require 'facter/util/debian'
+rescue LoadError
+ require "#{File.dirname(__FILE__)}/util/debian"
+end
+
+def version_to_codename(version)
+ if Facter::Util::Debian::CODENAMES.has_key?(version)
+ return Facter::Util::Debian::CODENAMES[version]
+ else
+ Facter.warn("Could not determine codename from version '#{version}'")
+ end
+end
+
+Facter.add(:debian_codename) do
+ has_weight 99
+ confine :operatingsystem => 'Debian'
+ setcode do
+ Facter.value('lsbdistcodename')
+ end
+end
+
+Facter.add(:debian_codename) do
+ has_weight 66
+ confine :operatingsystem => 'Debian'
+ setcode do
+ version_to_codename(Facter.value('operatingsystemmajrelease'))
+ end
+end
+
+Facter.add(:debian_codename) do
+ has_weight 33
+ confine :operatingsystem => 'Debian'
+ setcode do
+ debian_version = File.open('/etc/debian_version', &:readline)
+ if debian_version.match(/^\d+/)
+ version_to_codename(debian_version.scan(/^(\d+)/)[0][0])
+ elsif debian_version.match(/^[a-z]+\/(sid|unstable)/)
+ debian_version.scan(/^([a-z]+)\//)[0][0]
+ end
+ end
+end
diff --git a/puppet/modules/apt/lib/facter/debian_lts.rb b/puppet/modules/apt/lib/facter/debian_lts.rb
new file mode 100644
index 00000000..f53a9eb8
--- /dev/null
+++ b/puppet/modules/apt/lib/facter/debian_lts.rb
@@ -0,0 +1,16 @@
+begin
+ require 'facter/util/debian'
+rescue LoadError
+ require "#{File.dirname(__FILE__)}/util/debian"
+end
+
+Facter.add(:debian_lts) do
+ confine :operatingsystem => 'Debian'
+ setcode do
+ if Facter::Util::Debian::LTS.include? Facter.value('debian_codename')
+ true
+ else
+ false
+ end
+ end
+end
diff --git a/puppet/modules/apt/lib/facter/debian_nextcodename.rb b/puppet/modules/apt/lib/facter/debian_nextcodename.rb
new file mode 100644
index 00000000..c4c569b2
--- /dev/null
+++ b/puppet/modules/apt/lib/facter/debian_nextcodename.rb
@@ -0,0 +1,23 @@
+begin
+ require 'facter/util/debian'
+rescue LoadError
+ require "#{File.dirname(__FILE__)}/util/debian"
+end
+
+def debian_codename_to_next(codename)
+ if codename == "sid"
+ return "experimental"
+ else
+ codenames = Facter::Util::Debian::CODENAMES
+ versions = Facter::Util::Debian::CODENAMES.invert
+ current_version = versions[codename]
+ return codenames[(current_version.to_i + 1).to_s]
+ end
+end
+
+Facter.add(:debian_nextcodename) do
+ confine :operatingsystem => 'Debian'
+ setcode do
+ debian_codename_to_next(Facter.value('debian_codename'))
+ end
+end
diff --git a/puppet/modules/apt/lib/facter/debian_nextrelease.rb b/puppet/modules/apt/lib/facter/debian_nextrelease.rb
new file mode 100644
index 00000000..2a9c4f5f
--- /dev/null
+++ b/puppet/modules/apt/lib/facter/debian_nextrelease.rb
@@ -0,0 +1,23 @@
+def debian_release_to_next(release)
+ releases = [
+ 'oldoldoldstable',
+ 'oldoldstable',
+ 'oldstable',
+ 'stable',
+ 'testing',
+ 'unstable',
+ 'experimental',
+ ]
+ if releases.include? release
+ if releases.index(release)+1 < releases.count
+ return releases[releases.index(release)+1]
+ end
+ end
+end
+
+Facter.add(:debian_nextrelease) do
+ confine :operatingsystem => 'Debian'
+ setcode do
+ debian_release_to_next(Facter.value('debian_release'))
+ end
+end
diff --git a/puppet/modules/apt/lib/facter/debian_release.rb b/puppet/modules/apt/lib/facter/debian_release.rb
new file mode 100644
index 00000000..2c334ccd
--- /dev/null
+++ b/puppet/modules/apt/lib/facter/debian_release.rb
@@ -0,0 +1,38 @@
+begin
+ require 'facter/util/debian'
+rescue LoadError
+ require "#{File.dirname(__FILE__)}/util/debian"
+end
+
+def debian_codename_to_release(codename)
+ stable = Facter::Util::Debian::STABLE
+ versions = Facter::Util::Debian::CODENAMES.invert
+ release = nil
+ if codename == "sid"
+ release = "unstable"
+ elsif versions.has_key? codename
+ version = versions[codename].to_i
+ if version == stable
+ release = "stable"
+ elsif version < stable
+ release = "stable"
+ for i in version..stable - 1
+ release = "old" + release
+ end
+ elsif version == stable + 1
+ release = "testing"
+ end
+ end
+ if release.nil?
+ Facter.warn("Could not determine release from codename #{codename}!")
+ end
+ return release
+end
+
+Facter.add(:debian_release) do
+ has_weight 99
+ confine :operatingsystem => 'Debian'
+ setcode do
+ debian_codename_to_release(Facter.value('debian_codename'))
+ end
+end
diff --git a/puppet/modules/apt/lib/facter/ubuntu_codename.rb b/puppet/modules/apt/lib/facter/ubuntu_codename.rb
new file mode 100644
index 00000000..814fd942
--- /dev/null
+++ b/puppet/modules/apt/lib/facter/ubuntu_codename.rb
@@ -0,0 +1,8 @@
+Facter.add(:ubuntu_codename) do
+ confine :operatingsystem => 'Ubuntu'
+ setcode do
+ Facter.value('lsbdistcodename')
+ end
+end
+
+
diff --git a/puppet/modules/apt/lib/facter/ubuntu_nextcodename.rb b/puppet/modules/apt/lib/facter/ubuntu_nextcodename.rb
new file mode 100644
index 00000000..dcd1d426
--- /dev/null
+++ b/puppet/modules/apt/lib/facter/ubuntu_nextcodename.rb
@@ -0,0 +1,20 @@
+begin
+ require 'facter/util/ubuntu'
+rescue LoadError
+ require "#{File.dirname(__FILE__)}/util/ubuntu"
+end
+
+def ubuntu_codename_to_next(codename)
+ codenames = Facter::Util::Ubuntu::CODENAMES
+ i = codenames.index(codename)
+ if i and i+1 < codenames.count
+ return codenames[i+1]
+ end
+end
+
+Facter.add(:ubuntu_nextcodename) do
+ confine :operatingsystem => 'Ubuntu'
+ setcode do
+ ubuntu_codename_to_next(Facter.value('ubuntu_codename'))
+ end
+end
diff --git a/puppet/modules/apt/lib/facter/util/debian.rb b/puppet/modules/apt/lib/facter/util/debian.rb
new file mode 100644
index 00000000..290c17b5
--- /dev/null
+++ b/puppet/modules/apt/lib/facter/util/debian.rb
@@ -0,0 +1,18 @@
+module Facter
+ module Util
+ module Debian
+ STABLE = 8
+ CODENAMES = {
+ "5" => "lenny",
+ "6" => "squeeze",
+ "7" => "wheezy",
+ "8" => "jessie",
+ "9" => "stretch",
+ "10" => "buster",
+ }
+ LTS = [
+ "squeeze",
+ ]
+ end
+ end
+end
diff --git a/puppet/modules/apt/lib/facter/util/ubuntu.rb b/puppet/modules/apt/lib/facter/util/ubuntu.rb
new file mode 100644
index 00000000..52c15e80
--- /dev/null
+++ b/puppet/modules/apt/lib/facter/util/ubuntu.rb
@@ -0,0 +1,21 @@
+module Facter
+ module Util
+ module Ubuntu
+ CODENAMES = [
+ "lucid",
+ "maverick",
+ "natty",
+ "oneiric",
+ "precise",
+ "quantal",
+ "raring",
+ "saucy",
+ "trusty",
+ "utopic",
+ "vivid",
+ "wily",
+ "xenial"
+ ]
+ end
+ end
+end
diff --git a/puppet/modules/apt/manifests/apt_conf.pp b/puppet/modules/apt/manifests/apt_conf.pp
new file mode 100644
index 00000000..949f6157
--- /dev/null
+++ b/puppet/modules/apt/manifests/apt_conf.pp
@@ -0,0 +1,45 @@
+define apt::apt_conf(
+ $ensure = 'present',
+ $source = '',
+ $content = undef,
+ $refresh_apt = true )
+{
+
+ if $source == '' and $content == undef {
+ fail("One of \$source or \$content must be specified for apt_conf ${name}")
+ }
+
+ if $source != '' and $content != undef {
+ fail("Only one of \$source or \$content must specified for apt_conf ${name}")
+ }
+
+ include apt::dot_d_directories
+
+ # One would expect the 'file' resource on sources.list.d to trigger an
+ # apt-get update when files are added or modified in the directory, but it
+ # apparently doesn't.
+ file { "/etc/apt/apt.conf.d/${name}":
+ ensure => $ensure,
+ owner => root,
+ group => 0,
+ mode => '0644',
+ }
+
+ if $source {
+ File["/etc/apt/apt.conf.d/${name}"] {
+ source => $source,
+ }
+ }
+ else {
+ File["/etc/apt/apt.conf.d/${name}"] {
+ content => $content,
+ }
+ }
+
+ if $refresh_apt {
+ File["/etc/apt/apt.conf.d/${name}"] {
+ notify => Exec['apt_updated'],
+ }
+ }
+
+}
diff --git a/puppet/modules/apt/manifests/apticron.pp b/puppet/modules/apt/manifests/apticron.pp
new file mode 100644
index 00000000..9c94f9c9
--- /dev/null
+++ b/puppet/modules/apt/manifests/apticron.pp
@@ -0,0 +1,24 @@
+class apt::apticron(
+ $ensure_version = 'installed',
+ $config = "apt/${::operatingsystem}/apticron_${::debian_codename}.erb",
+ $email = 'root',
+ $diff_only = '1',
+ $listchanges_profile = 'apticron',
+ $system = false,
+ $ipaddressnum = false,
+ $ipaddresses = false,
+ $notifyholds = '0',
+ $notifynew = '0',
+ $customsubject = ''
+) {
+
+ package { 'apticron': ensure => $ensure_version }
+
+ file { '/etc/apticron/apticron.conf':
+ content => template($apt::apticron::config),
+ owner => root,
+ group => root,
+ mode => '0644',
+ require => Package['apticron'];
+ }
+}
diff --git a/puppet/modules/apt/manifests/cron/base.pp b/puppet/modules/apt/manifests/cron/base.pp
new file mode 100644
index 00000000..39fc3061
--- /dev/null
+++ b/puppet/modules/apt/manifests/cron/base.pp
@@ -0,0 +1,20 @@
+class apt::cron::base {
+
+ package { 'cron-apt': ensure => installed }
+
+ case $apt_cron_hours {
+ '': {}
+ default: {
+ # cron-apt defaults to run every night at 4 o'clock
+ # so we try not to run at the same time.
+ cron { 'apt_cron_every_N_hours':
+ command => 'test -x /usr/sbin/cron-apt && /usr/sbin/cron-apt',
+ user => root,
+ hour => "${apt_cron_hours}",
+ minute => 10,
+ require => Package['cron-apt'],
+ }
+ }
+ }
+
+}
diff --git a/puppet/modules/apt/manifests/cron/dist_upgrade.pp b/puppet/modules/apt/manifests/cron/dist_upgrade.pp
new file mode 100644
index 00000000..74403bb7
--- /dev/null
+++ b/puppet/modules/apt/manifests/cron/dist_upgrade.pp
@@ -0,0 +1,29 @@
+class apt::cron::dist_upgrade inherits apt::cron::base {
+
+ $action = "autoclean -y
+dist-upgrade -y -o APT::Get::Show-Upgraded=true -o 'DPkg::Options::=--force-confold'
+"
+
+ file { '/etc/cron-apt/action.d/3-download':
+ ensure => absent,
+ }
+
+ package { 'apt-listbugs': ensure => absent }
+
+ file { '/etc/cron-apt/action.d/4-dist-upgrade':
+ content => $action,
+ owner => root,
+ group => 0,
+ mode => '0644',
+ require => Package[cron-apt];
+ }
+
+ file { '/etc/cron-apt/config.d/MAILON':
+ content => "MAILON=upgrade\n",
+ owner => root,
+ group => 0,
+ mode => '0644',
+ require => Package[cron-apt];
+ }
+
+}
diff --git a/puppet/modules/apt/manifests/cron/download.pp b/puppet/modules/apt/manifests/cron/download.pp
new file mode 100644
index 00000000..4a19fec1
--- /dev/null
+++ b/puppet/modules/apt/manifests/cron/download.pp
@@ -0,0 +1,27 @@
+class apt::cron::download inherits apt::cron::base {
+
+ $action = "autoclean -y
+dist-upgrade -d -y -o APT::Get::Show-Upgraded=true
+"
+
+ file { '/etc/cron-apt/action.d/4-dist-upgrade':
+ ensure => absent,
+ }
+
+ file { '/etc/cron-apt/action.d/3-download':
+ content => $action,
+ require => Package[cron-apt],
+ owner => root,
+ group => 0,
+ mode => '0644';
+ }
+
+ file { '/etc/cron-apt/config.d/MAILON':
+ content => "MAILON=changes\n",
+ require => Package[cron-apt],
+ owner => root,
+ group => 0,
+ mode => '0644';
+ }
+
+}
diff --git a/puppet/modules/apt/manifests/dist_upgrade.pp b/puppet/modules/apt/manifests/dist_upgrade.pp
new file mode 100644
index 00000000..19c031e0
--- /dev/null
+++ b/puppet/modules/apt/manifests/dist_upgrade.pp
@@ -0,0 +1,9 @@
+class apt::dist_upgrade {
+
+ exec { 'apt_dist-upgrade':
+ command => '/usr/bin/apt-get -q -y -o \'DPkg::Options::=--force-confold\' dist-upgrade',
+ refreshonly => true,
+ before => Exec['apt_updated']
+ }
+
+}
diff --git a/puppet/modules/apt/manifests/dist_upgrade/initiator.pp b/puppet/modules/apt/manifests/dist_upgrade/initiator.pp
new file mode 100644
index 00000000..d2389883
--- /dev/null
+++ b/puppet/modules/apt/manifests/dist_upgrade/initiator.pp
@@ -0,0 +1,23 @@
+class apt::dist_upgrade::initiator inherits apt::dist_upgrade {
+
+ $initiator = 'upgrade_initiator'
+ $initiator_abs = "${apt::apt_base_dir}/${initiator}"
+
+ file { 'apt_upgrade_initiator':
+ mode => '0644',
+ owner => root,
+ group => 0,
+ path => $initiator_abs,
+ checksum => md5,
+ source => [
+ "puppet:///modules/site_apt/${::fqdn}/${initiator}",
+ "puppet:///modules/site_apt/${initiator}",
+ "puppet:///modules/apt/${initiator}",
+ ],
+ }
+
+ Exec['apt_dist-upgrade'] {
+ subscribe +> File['apt_upgrade_initiator'],
+ }
+
+}
diff --git a/puppet/modules/apt/manifests/dot_d_directories.pp b/puppet/modules/apt/manifests/dot_d_directories.pp
new file mode 100644
index 00000000..0ace8630
--- /dev/null
+++ b/puppet/modules/apt/manifests/dot_d_directories.pp
@@ -0,0 +1,15 @@
+class apt::dot_d_directories {
+
+ # watch .d directories and ensure they are present
+ file {
+ '/etc/apt/apt.conf.d':
+ ensure => directory,
+ checksum => mtime,
+ notify => Exec['apt_updated'];
+ '/etc/apt/sources.list.d':
+ ensure => directory,
+ checksum => mtime,
+ notify => Exec['apt_updated'];
+ }
+
+}
diff --git a/puppet/modules/apt/manifests/dselect.pp b/puppet/modules/apt/manifests/dselect.pp
new file mode 100644
index 00000000..2b99a43d
--- /dev/null
+++ b/puppet/modules/apt/manifests/dselect.pp
@@ -0,0 +1,11 @@
+# manage dselect, like
+# suppressing the annoying help texts
+class apt::dselect {
+
+ file_line { 'dselect_expert':
+ path => '/etc/dpkg/dselect.cfg',
+ line => 'expert',
+ }
+
+ package { 'dselect': ensure => installed }
+}
diff --git a/puppet/modules/apt/manifests/init.pp b/puppet/modules/apt/manifests/init.pp
new file mode 100644
index 00000000..4c44af2a
--- /dev/null
+++ b/puppet/modules/apt/manifests/init.pp
@@ -0,0 +1,150 @@
+# apt.pp - common components and defaults for handling apt
+# Copyright (C) 2008 Micah Anerson <micah@riseup.net>
+# Copyright (C) 2007 David Schmitt <david@schmitt.edv-bus.at>
+# See LICENSE for the full license granted to you.
+
+class apt(
+ $use_lts = $apt::params::use_lts,
+ $use_volatile = $apt::params::use_volatile,
+ $use_backports = $apt::params::use_backports,
+ $include_src = $apt::params::include_src,
+ $use_next_release = $apt::params::use_next_release,
+ $debian_url = $apt::params::debian_url,
+ $security_url = $apt::params::security_url,
+ $backports_url = $apt::params::backports_url,
+ $lts_url = $apt::params::lts_url,
+ $volatile_url = $apt::params::volatile_url,
+ $ubuntu_url = $apt::params::ubuntu_url,
+ $repos = $apt::params::repos,
+ $custom_preferences = $apt::params::custom_preferences,
+ $custom_sources_list = '',
+ $custom_key_dir = $apt::params::custom_key_dir
+) inherits apt::params {
+ case $::operatingsystem {
+ 'debian': {
+ $real_repos = $repos ? {
+ 'auto' => 'main contrib non-free',
+ default => $repos,
+ }
+ }
+ 'ubuntu': {
+ $real_repos = $repos ? {
+ 'auto' => 'main restricted universe multiverse',
+ default => $repos,
+ }
+ }
+ }
+
+ package { 'apt':
+ ensure => installed,
+ require => undef,
+ }
+
+ $sources_content = $custom_sources_list ? {
+ '' => template( "apt/${::operatingsystem}/sources.list.erb"),
+ default => $custom_sources_list
+ }
+ file {
+ # include main and security
+ # additional sources should be included via the apt::sources_list define
+ '/etc/apt/sources.list':
+ content => $sources_content,
+ notify => Exec['apt_updated'],
+ owner => root,
+ group => 0,
+ mode => '0644';
+ }
+
+ apt_conf { '02show_upgraded':
+ source => [ "puppet:///modules/site_apt/${::fqdn}/02show_upgraded",
+ 'puppet:///modules/site_apt/02show_upgraded',
+ 'puppet:///modules/apt/02show_upgraded' ]
+ }
+
+ if ( $::virtual == 'vserver' ) {
+ apt_conf { '03clean_vserver':
+ source => [ "puppet:///modules/site_apt/${::fqdn}/03clean_vserver",
+ 'puppet:///modules/site_apt/03clean_vserver',
+ 'puppet:///modules/apt/03clean_vserver' ],
+ alias => '03clean';
+ }
+ }
+ else {
+ apt_conf { '03clean':
+ source => [ "puppet:///modules/site_apt/${::fqdn}/03clean",
+ 'puppet:///modules/site_apt/03clean',
+ 'puppet:///modules/apt/03clean' ]
+ }
+ }
+
+ case $custom_preferences {
+ false: {
+ include apt::preferences::absent
+ }
+ default: {
+ # When squeeze becomes the stable branch, transform this file's header
+ # into a preferences.d file
+ include apt::preferences
+ }
+ }
+
+ include apt::dot_d_directories
+
+ ## This package should really always be current
+ package { 'debian-archive-keyring': ensure => latest }
+
+ # backports uses the normal archive key now
+ package { 'debian-backports-keyring': ensure => absent }
+
+ if ($use_backports and !($::debian_release in ['testing', 'unstable', 'experimental'])) {
+ apt::sources_list {
+ 'backports':
+ content => "deb $backports_url ${::debian_codename}-backports ${apt::real_repos}",
+ }
+ if $include_src {
+ apt::sources_list {
+ 'backports-src':
+ content => "deb-src $backports_url ${::debian_codename}-backports ${apt::real_repos}",
+ }
+ }
+ }
+
+ include common::moduledir
+ common::module_dir { 'apt': }
+ $apt_base_dir = "${common::moduledir::module_dir_path}/apt"
+
+ if $custom_key_dir {
+ file { "${apt_base_dir}/keys.d":
+ source => $custom_key_dir,
+ recurse => true,
+ owner => root,
+ group => root,
+ mode => '0755',
+ }
+ exec { 'custom_keys':
+ command => "find ${apt_base_dir}/keys.d -type f -exec apt-key add '{}' \\;",
+ subscribe => File["${apt_base_dir}/keys.d"],
+ refreshonly => true,
+ notify => Exec[refresh_apt]
+ }
+ if $custom_preferences != false {
+ Exec['custom_keys'] {
+ before => File['apt_config']
+ }
+ }
+ }
+
+ # workaround for preseeded_package component
+ file { [ '/var/cache', '/var/cache/local', '/var/cache/local/preseeding' ]: ensure => directory }
+
+ exec { 'update_apt':
+ command => '/usr/bin/apt-get update',
+ require => [
+ File['/etc/apt/apt.conf.d', '/etc/apt/preferences' ],
+ File['/etc/apt/sources.list'] ],
+ refreshonly => true,
+ # Another Semaphor for all packages to reference
+ alias => [ 'apt_updated', 'refresh_apt']
+ }
+
+}
diff --git a/puppet/modules/apt/manifests/key.pp b/puppet/modules/apt/manifests/key.pp
new file mode 100644
index 00000000..cb70ec6a
--- /dev/null
+++ b/puppet/modules/apt/manifests/key.pp
@@ -0,0 +1,13 @@
+define apt::key ($source, $ensure = 'present') {
+ validate_re(
+ $name, '\.gpg$',
+ 'An apt::key resource name must have the .gpg extension',
+ )
+
+ file {
+ "/etc/apt/trusted.gpg.d/${name}":
+ ensure => $ensure,
+ source => $source,
+ notify => Exec['apt_updated'],
+ }
+}
diff --git a/puppet/modules/apt/manifests/key/plain.pp b/puppet/modules/apt/manifests/key/plain.pp
new file mode 100644
index 00000000..dff8b51b
--- /dev/null
+++ b/puppet/modules/apt/manifests/key/plain.pp
@@ -0,0 +1,13 @@
+define apt::key::plain ($source) {
+ file {
+ "${apt::apt_base_dir}/keys/${name}":
+ source => $source;
+ "${apt::apt_base_dir}/keys":
+ ensure => directory;
+ }
+ exec { "apt-key add '${apt::apt_base_dir}/keys/${name}'":
+ subscribe => File["${apt::apt_base_dir}/keys/${name}"],
+ refreshonly => true,
+ notify => Exec['apt_updated'],
+ }
+}
diff --git a/puppet/modules/apt/manifests/listchanges.pp b/puppet/modules/apt/manifests/listchanges.pp
new file mode 100644
index 00000000..e64bb1b7
--- /dev/null
+++ b/puppet/modules/apt/manifests/listchanges.pp
@@ -0,0 +1,19 @@
+class apt::listchanges(
+ $ensure_version = 'installed',
+ $config = "apt/${::operatingsystem}/listchanges_${::debian_codename}.erb",
+ $frontend = 'mail',
+ $email = 'root',
+ $confirm = '0',
+ $saveseen = '/var/lib/apt/listchanges.db',
+ $which = 'both'
+){
+ package { 'apt-listchanges': ensure => $ensure_version }
+
+ file { '/etc/apt/listchanges.conf':
+ content => template($apt::listchanges::config),
+ owner => root,
+ group => root,
+ mode => '0644',
+ require => Package['apt-listchanges'];
+ }
+}
diff --git a/puppet/modules/apt/manifests/params.pp b/puppet/modules/apt/manifests/params.pp
new file mode 100644
index 00000000..28af06eb
--- /dev/null
+++ b/puppet/modules/apt/manifests/params.pp
@@ -0,0 +1,22 @@
+class apt::params () {
+ $use_lts = false
+ $use_volatile = false
+ $use_backports = true
+ $include_src = false
+ $use_next_release = false
+ $debian_url = 'http://httpredir.debian.org/debian/'
+ $security_url = 'http://security.debian.org/'
+ $ubuntu_url = 'http://archive.ubuntu.com/ubuntu'
+ $backports_url = $::debian_codename ? {
+ 'squeeze' => 'http://backports.debian.org/debian-backports/',
+ default => $::operatingsystem ? {
+ 'Ubuntu' => $ubuntu_url,
+ default => $debian_url,
+ }
+ }
+ $lts_url = $debian_url
+ $volatile_url = 'http://volatile.debian.org/debian-volatile/'
+ $repos = 'auto'
+ $custom_preferences = ''
+ $custom_key_dir = false
+}
diff --git a/puppet/modules/apt/manifests/preferences.pp b/puppet/modules/apt/manifests/preferences.pp
new file mode 100644
index 00000000..6982ca05
--- /dev/null
+++ b/puppet/modules/apt/manifests/preferences.pp
@@ -0,0 +1,20 @@
+class apt::preferences {
+
+ $pref_contents = $apt::custom_preferences ? {
+ '' => $::operatingsystem ? {
+ 'debian' => template("apt/${::operatingsystem}/preferences_${::debian_codename}.erb"),
+ 'ubuntu' => template("apt/${::operatingsystem}/preferences_${::ubuntu_codename}.erb"),
+ },
+ default => $apt::custom_preferences
+ }
+
+ file { '/etc/apt/preferences':
+ ensure => present,
+ alias => 'apt_config',
+ # only update together
+ content => $pref_contents,
+ require => File['/etc/apt/sources.list'],
+ owner => root, group => 0, mode => '0644';
+ }
+
+}
diff --git a/puppet/modules/apt/manifests/preferences/absent.pp b/puppet/modules/apt/manifests/preferences/absent.pp
new file mode 100644
index 00000000..f32e0307
--- /dev/null
+++ b/puppet/modules/apt/manifests/preferences/absent.pp
@@ -0,0 +1,7 @@
+class apt::preferences::absent {
+
+ file { '/etc/apt/preferences':
+ ensure => absent,
+ alias => 'apt_config',
+ }
+}
diff --git a/puppet/modules/apt/manifests/preferences_snippet.pp b/puppet/modules/apt/manifests/preferences_snippet.pp
new file mode 100644
index 00000000..b7dba0d8
--- /dev/null
+++ b/puppet/modules/apt/manifests/preferences_snippet.pp
@@ -0,0 +1,59 @@
+define apt::preferences_snippet (
+ $priority = undef,
+ $package = false,
+ $ensure = 'present',
+ $source = '',
+ $release = '',
+ $pin = ''
+) {
+
+ $real_package = $package ? {
+ false => $name,
+ default => $package,
+ }
+
+ if $ensure == 'present' {
+ if $apt::custom_preferences == false {
+ fail('Trying to define a preferences_snippet with $custom_preferences set to false.')
+ }
+
+ if $priority == undef {
+ fail('apt::preferences_snippet requires the \'priority\' argument to be set')
+ }
+
+ if !$pin and !$release {
+ fail('apt::preferences_snippet requires one of the \'pin\' or \'release\' argument to be set')
+ }
+ if $pin and $release {
+ fail('apt::preferences_snippet requires either a \'pin\' or \'release\' argument, not both')
+ }
+ }
+
+ file { "/etc/apt/preferences.d/${name}":
+ ensure => $ensure,
+ owner => root, group => 0, mode => '0644',
+ before => Exec['apt_updated'];
+ }
+
+ case $source {
+ '': {
+ case $release {
+ '': {
+ File["/etc/apt/preferences.d/${name}"]{
+ content => template('apt/preferences_snippet.erb')
+ }
+ }
+ default: {
+ File["/etc/apt/preferences.d/${name}"]{
+ content => template('apt/preferences_snippet_release.erb')
+ }
+ }
+ }
+ }
+ default: {
+ File["/etc/apt/preferences.d/${name}"]{
+ source => $source
+ }
+ }
+ }
+}
diff --git a/puppet/modules/apt/manifests/preseeded_package.pp b/puppet/modules/apt/manifests/preseeded_package.pp
new file mode 100644
index 00000000..3ef06879
--- /dev/null
+++ b/puppet/modules/apt/manifests/preseeded_package.pp
@@ -0,0 +1,21 @@
+define apt::preseeded_package (
+ $ensure = 'installed',
+ $content = ''
+) {
+ $seedfile = "/var/cache/local/preseeding/${name}.seeds"
+ $real_content = $content ? {
+ '' => template ( "site_apt/${::debian_codename}/${name}.seeds" ),
+ default => $content
+ }
+
+ file { $seedfile:
+ content => $real_content,
+ mode => '0600', owner => root, group => root,
+ }
+
+ package { $name:
+ ensure => $ensure,
+ responsefile => $seedfile,
+ require => File[$seedfile],
+ }
+}
diff --git a/puppet/modules/apt/manifests/proxy_client.pp b/puppet/modules/apt/manifests/proxy_client.pp
new file mode 100644
index 00000000..9ba79f23
--- /dev/null
+++ b/puppet/modules/apt/manifests/proxy_client.pp
@@ -0,0 +1,9 @@
+class apt::proxy_client(
+ $proxy = 'http://localhost',
+ $port = '3142',
+){
+
+ apt_conf { '20proxy':
+ content => template('apt/20proxy.erb'),
+ }
+}
diff --git a/puppet/modules/apt/manifests/reboot_required_notify.pp b/puppet/modules/apt/manifests/reboot_required_notify.pp
new file mode 100644
index 00000000..722e8a5e
--- /dev/null
+++ b/puppet/modules/apt/manifests/reboot_required_notify.pp
@@ -0,0 +1,21 @@
+class apt::reboot_required_notify {
+
+ # This package installs the script that created /var/run/reboot-required*.
+ # This script (/usr/share/update-notifier/notify-reboot-required) is
+ # triggered e.g. by kernel packages.
+ package { 'update-notifier-common':
+ ensure => installed,
+ }
+
+ # cron-apt defaults to run every night at 4 o'clock
+ # plus some random time <1h.
+ # so we check if a reboot is required a bit later.
+ cron { 'apt_reboot_required_notify':
+ command => 'if [ -f /var/run/reboot-required ]; then echo "Reboot required\n" ; cat /var/run/reboot-required.pkgs ; fi',
+ user => root,
+ hour => 5,
+ minute => 20,
+ require => Package['update-notifier-common'],
+ }
+
+}
diff --git a/puppet/modules/apt/manifests/sources_list.pp b/puppet/modules/apt/manifests/sources_list.pp
new file mode 100644
index 00000000..0ee068d1
--- /dev/null
+++ b/puppet/modules/apt/manifests/sources_list.pp
@@ -0,0 +1,40 @@
+define apt::sources_list (
+ $ensure = 'present',
+ $source = '',
+ $content = undef
+) {
+
+ if $ensure == 'present' {
+ if $source == '' and $content == undef {
+ fail("One of \$source or \$content must be specified for apt_sources_snippet ${name}")
+ }
+ if $source != '' and $content != undef {
+ fail("Only one of \$source or \$content must specified for apt_sources_snippet ${name}")
+ }
+ }
+
+ include apt::dot_d_directories
+
+ $realname = regsubst($name, '\.list$', '')
+
+ # One would expect the 'file' resource on sources.list.d to trigger an
+ # apt-get update when files are added or modified in the directory, but it
+ # apparently doesn't.
+ file { "/etc/apt/sources.list.d/${realname}.list":
+ ensure => $ensure,
+ owner => root, group => 0, mode => '0644',
+ notify => Exec['apt_updated'],
+ }
+
+ if $source {
+ File["/etc/apt/sources.list.d/${realname}.list"] {
+ source => $source,
+ }
+ }
+ else {
+ File["/etc/apt/sources.list.d/${realname}.list"] {
+ content => $content,
+ }
+ }
+}
+
diff --git a/puppet/modules/apt/manifests/unattended_upgrades.pp b/puppet/modules/apt/manifests/unattended_upgrades.pp
new file mode 100644
index 00000000..52d75425
--- /dev/null
+++ b/puppet/modules/apt/manifests/unattended_upgrades.pp
@@ -0,0 +1,34 @@
+class apt::unattended_upgrades (
+ $config_content = undef,
+ $config_template = 'apt/50unattended-upgrades.erb',
+ $mailonlyonerror = true,
+ $mail_recipient = 'root',
+ $blacklisted_packages = [],
+ $ensure_version = present
+) {
+
+ package { 'unattended-upgrades':
+ ensure => $ensure_version
+ }
+
+ # For some reason, this directory is sometimes absent, which causes
+ # unattended-upgrades to crash.
+ file { '/var/log/unattended-upgrades':
+ ensure => directory,
+ owner => 'root',
+ group => 0,
+ mode => '0755',
+ require => Package['unattended-upgrades'],
+ }
+
+ $file_content = $config_content ? {
+ undef => template($config_template),
+ default => $config_content
+ }
+
+ apt_conf { '50unattended-upgrades':
+ content => $file_content,
+ require => Package['unattended-upgrades'],
+ refresh_apt => false
+ }
+}
diff --git a/puppet/modules/apt/manifests/update.pp b/puppet/modules/apt/manifests/update.pp
new file mode 100644
index 00000000..dde83200
--- /dev/null
+++ b/puppet/modules/apt/manifests/update.pp
@@ -0,0 +1,7 @@
+class apt::update inherits ::apt {
+
+ Exec['update_apt'] {
+ refreshonly => false
+ }
+
+}
diff --git a/puppet/modules/apt/manifests/upgrade_package.pp b/puppet/modules/apt/manifests/upgrade_package.pp
new file mode 100644
index 00000000..30572c96
--- /dev/null
+++ b/puppet/modules/apt/manifests/upgrade_package.pp
@@ -0,0 +1,31 @@
+define apt::upgrade_package (
+ $version = ''
+) {
+
+ $version_suffix = $version ? {
+ '' => '',
+ 'latest' => '',
+ default => "=${version}",
+ }
+
+ if !defined(Package['apt-show-versions']) {
+ package { 'apt-show-versions':
+ ensure => installed,
+ require => undef,
+ }
+ }
+
+ if !defined(Package['dctrl-tools']) {
+ package { 'dctrl-tools':
+ ensure => installed,
+ require => undef,
+ }
+ }
+
+ exec { "apt-get -q -y -o 'DPkg::Options::=--force-confold' install ${name}${version_suffix}":
+ onlyif => [ "grep-status -F Status installed -a -P $name -q", "apt-show-versions -u $name | grep -q upgradeable" ],
+ require => Package['apt-show-versions', 'dctrl-tools'],
+ before => Exec['apt_updated']
+ }
+
+}
diff --git a/puppet/modules/apt/spec/spec_helper.rb b/puppet/modules/apt/spec/spec_helper.rb
new file mode 100644
index 00000000..21d1a988
--- /dev/null
+++ b/puppet/modules/apt/spec/spec_helper.rb
@@ -0,0 +1,12 @@
+# https://puppetlabs.com/blog/testing-modules-in-the-puppet-forge
+require 'rspec-puppet'
+require 'mocha/api'
+
+RSpec.configure do |c|
+
+ c.module_path = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
+ c.color = true
+
+ #Puppet.features.stubs(:root? => true)
+
+end
diff --git a/puppet/modules/apt/spec/unit/custom_facts_spec.rb b/puppet/modules/apt/spec/unit/custom_facts_spec.rb
new file mode 100644
index 00000000..9a28d92e
--- /dev/null
+++ b/puppet/modules/apt/spec/unit/custom_facts_spec.rb
@@ -0,0 +1,86 @@
+require "spec_helper"
+
+describe "Facter::Util::Fact" do
+ before {
+ Facter.clear
+ }
+
+ describe 'custom facts' do
+
+ context 'Debian 7' do
+ before do
+ Facter.fact(:operatingsystem).stubs(:value).returns("Debian")
+ Facter.fact(:operatingsystemrelease).stubs(:value).returns("7.8")
+ Facter.fact(:lsbdistcodename).stubs(:value).returns("wheezy")
+ end
+
+ it "debian_release = oldstable" do
+ expect(Facter.fact(:debian_release).value).to eq('oldstable')
+ end
+
+ it "debian_codename = wheezy" do
+ expect(Facter.fact(:debian_codename).value).to eq('wheezy')
+ end
+
+ it "debian_nextcodename = jessie" do
+ expect(Facter.fact(:debian_nextcodename).value).to eq('jessie')
+ end
+
+ it "debian_nextrelease = stable" do
+ expect(Facter.fact(:debian_nextrelease).value).to eq('stable')
+ end
+ end
+
+ context 'Debian 8' do
+ before do
+ Facter.fact(:operatingsystem).stubs(:value).returns("Debian")
+ Facter.fact(:operatingsystemrelease).stubs(:value).returns("8.0")
+ Facter.fact(:lsbdistcodename).stubs(:value).returns("jessie")
+ end
+
+ it "debian_release = stable" do
+ expect(Facter.fact(:debian_release).value).to eq('stable')
+ end
+
+ it "debian_codename = jessie" do
+ expect(Facter.fact(:debian_codename).value).to eq('jessie')
+ end
+
+ it "debian_nextcodename = stretch" do
+ expect(Facter.fact(:debian_nextcodename).value).to eq('stretch')
+ end
+
+ it "debian_nextrelease = testing" do
+ expect(Facter.fact(:debian_nextrelease).value).to eq('testing')
+ end
+ end
+
+ context 'Ubuntu 15.10' do
+ before do
+ Facter.fact(:operatingsystem).stubs(:value).returns("Ubuntu")
+ Facter.fact(:operatingsystemrelease).stubs(:value).returns("15.10")
+ Facter.fact(:lsbdistcodename).stubs(:value).returns("wily")
+ end
+
+ it "ubuntu_codename = wily" do
+ expect(Facter.fact(:ubuntu_codename).value).to eq('wily')
+ end
+
+ it "ubuntu_nextcodename = xenial" do
+ expect(Facter.fact(:ubuntu_nextcodename).value).to eq('xenial')
+ end
+ end
+ end
+
+ describe "Test 'apt_running' fact" do
+ it "should return true when apt-get is running" do
+ Facter::Util::Resolution.stubs(:exec).with("pgrep apt-get >/dev/null 2>&1 && echo true || echo false").returns("true")
+ expect(Facter.fact(:apt_running).value).to eq('true')
+ end
+ it "should return false when apt-get is not running" do
+ Facter::Util::Resolution.stubs(:exec).with("pgrep apt-get >/dev/null 2>&1 && echo true || echo false").returns("false")
+ expect(Facter.fact(:apt_running).value).to eq('false')
+ end
+ end
+
+end
diff --git a/puppet/modules/apt/templates/20proxy.erb b/puppet/modules/apt/templates/20proxy.erb
new file mode 100644
index 00000000..520e7b1b
--- /dev/null
+++ b/puppet/modules/apt/templates/20proxy.erb
@@ -0,0 +1,5 @@
+// This file is managed by Puppet
+// all local modifications will be overwritten
+
+Acquire::http { Proxy "<%= @proxy %>:<%= @port %>"; };
+Acquire::HTTP::Proxy::bugs.debian.org "DIRECT";
diff --git a/puppet/modules/apt/templates/50unattended-upgrades.erb b/puppet/modules/apt/templates/50unattended-upgrades.erb
new file mode 100644
index 00000000..7c65d102
--- /dev/null
+++ b/puppet/modules/apt/templates/50unattended-upgrades.erb
@@ -0,0 +1,38 @@
+// this file is managed by puppet !
+
+<% if scope.lookupvar('::operatingsystem') == 'Ubuntu' -%>
+Unattended-Upgrade::Allowed-Origins {
+ "${distro_id}:${distro_codename}-security";
+ "${distro_id}:${distro_codename}-updates";
+ "${distro_id}:${distro_codename}-backports";
+<% elsif scope.lookupvar('::operatingsystem') == 'Debian' and scope.lookupvar('::debian_codename') == 'squeeze' -%>
+Unattended-Upgrade::Allowed-Origins {
+ "${distro_id}:<%= scope.lookupvar('::debian_release') %>";
+ "${distro_id}:squeeze-lts";
+<% elsif scope.lookupvar('::operatingsystem') == 'Debian' and scope.lookupvar('::debian_codename') == 'wheezy' -%>
+Unattended-Upgrade::Origins-Pattern {
+ "origin=Debian,archive=<%= scope.lookupvar('::debian_release') %>,label=Debian-Security";
+ "origin=Debian,archive=${distro_codename}-lts";
+<% else -%>
+Unattended-Upgrade::Origins-Pattern {
+ "origin=Debian,codename=${distro_codename},label=Debian";
+ "origin=Debian,codename=${distro_codename},label=Debian-Security";
+<% end -%>
+};
+
+<% if not @blacklisted_packages.empty? -%>
+Unattended-Upgrade::Package-Blacklist {
+<% @blacklisted_packages.each do |pkg| -%>
+ "<%= pkg %>";
+<% end -%>
+};
+<% end -%>
+
+APT::Periodic::Update-Package-Lists "1";
+APT::Periodic::Download-Upgradeable-Packages "1";
+APT::Periodic::Unattended-Upgrade "1";
+
+Unattended-Upgrade::Mail "<%= @mail_recipient -%>";
+<% if @mailonlyonerror -%>
+Unattended-Upgrade::MailOnlyOnError "true";
+<% end -%>
diff --git a/puppet/modules/apt/templates/Debian/apticron_jessie.erb b/puppet/modules/apt/templates/Debian/apticron_jessie.erb
new file mode 120000
index 00000000..a9a3a6fd
--- /dev/null
+++ b/puppet/modules/apt/templates/Debian/apticron_jessie.erb
@@ -0,0 +1 @@
+apticron_wheezy.erb \ No newline at end of file
diff --git a/puppet/modules/apt/templates/Debian/apticron_lenny.erb b/puppet/modules/apt/templates/Debian/apticron_lenny.erb
new file mode 100644
index 00000000..86b09977
--- /dev/null
+++ b/puppet/modules/apt/templates/Debian/apticron_lenny.erb
@@ -0,0 +1,50 @@
+# apticron.conf
+#
+# set EMAIL to a list of addresses which will be notified of impending updates
+#
+EMAIL="<%= scope.lookupvar('apt::apticron::email') %>"
+
+#
+# Set DIFF_ONLY to "1" to only output the difference of the current run
+# compared to the last run (ie. only new upgrades since the last run). If there
+# are no differences, no output/email will be generated. By default, apticron
+# will output everything that needs to be upgraded.
+#
+DIFF_ONLY="<%= scope.lookupvar('apt::apticron::diff_only') %>"
+
+#
+# Set LISTCHANGES_PROFILE if you would like apticron to invoke apt-listchanges
+# with the --profile option. You should add a corresponding profile to
+# /etc/apt/listchanges.conf
+#
+LISTCHANGES_PROFILE="<%= scope.lookupvar('apt::apticron::listchanges_profile') %>"
+
+#
+# Set SYSTEM if you would like apticron to use something other than the output
+# of "hostname -f" for the system name in the mails it generates
+#
+# SYSTEM="foobar.example.com"
+<% unless (v=scope.lookupvar('apt::apticron::system')).to_s == "false" -%>
+SYSTEM="<%= v %>"
+<% end -%>
+
+#
+# Set IPADDRESSNUM if you would like to configure the maximal number of IP
+# addresses apticron displays. The default is to display 1 address of each
+# family type (inet, inet6), if available.
+#
+# IPADDRESSNUM="1"
+<% unless (v=scope.lookupvar('apt::apticron::ipaddressnum')).to_s == "false" -%>
+IPADDRESSNUM="<%= v %>"
+<% end -%>
+
+#
+# Set IPADDRESSES to a whitespace seperated list of reachable addresses for
+# this system. By default, apticron will try to work these out using the
+# "ip" command
+#
+# IPADDRESSES="192.0.2.1 2001:db8:1:2:3::1"
+<% unless (v=scope.lookupvar('apt::apticron::ipaddresses')).to_s == "false" -%>
+IPADDRESSES="<%= v %>"
+<% end -%>
+
diff --git a/puppet/modules/apt/templates/Debian/apticron_sid.erb b/puppet/modules/apt/templates/Debian/apticron_sid.erb
new file mode 120000
index 00000000..a9a3a6fd
--- /dev/null
+++ b/puppet/modules/apt/templates/Debian/apticron_sid.erb
@@ -0,0 +1 @@
+apticron_wheezy.erb \ No newline at end of file
diff --git a/puppet/modules/apt/templates/Debian/apticron_squeeze.erb b/puppet/modules/apt/templates/Debian/apticron_squeeze.erb
new file mode 100644
index 00000000..05b7c9b8
--- /dev/null
+++ b/puppet/modules/apt/templates/Debian/apticron_squeeze.erb
@@ -0,0 +1,82 @@
+# apticron.conf
+#
+# set EMAIL to a space separated list of addresses which will be notified of
+# impending updates
+#
+EMAIL="<%= scope.lookupvar('apt::apticron::email') %>"
+
+
+#
+# Set DIFF_ONLY to "1" to only output the difference of the current run
+# compared to the last run (ie. only new upgrades since the last run). If there
+# are no differences, no output/email will be generated. By default, apticron
+# will output everything that needs to be upgraded.
+#
+DIFF_ONLY="<%= scope.lookupvar('apt::apticron::diff_only') %>"
+
+#
+# Set LISTCHANGES_PROFILE if you would like apticron to invoke apt-listchanges
+# with the --profile option. You should add a corresponding profile to
+# /etc/apt/listchanges.conf
+#
+LISTCHANGES_PROFILE="<%= scope.lookupvar('apt::apticron::listchanges_profile') %>"
+
+#
+# Set SYSTEM if you would like apticron to use something other than the output
+# of "hostname -f" for the system name in the mails it generates
+#
+# SYSTEM="foobar.example.com"
+<% unless (v=scope.lookupvar('apt::apticron::system')).to_s == "false" -%>
+SYSTEM="<%= v %>"
+<% end -%>
+
+
+#
+# Set IPADDRESSNUM if you would like to configure the maximal number of IP
+# addresses apticron displays. The default is to display 1 address of each
+# family type (inet, inet6), if available.
+#
+# IPADDRESSNUM="1"
+<% unless (v=scope.lookupvar('apt::apticron::ipaddressnum')).to_s == "false" -%>
+IPADDRESSNUM="<%= v %>"
+<% end -%>
+
+
+#
+# Set IPADDRESSES to a whitespace separated list of reachable addresses for
+# this system. By default, apticron will try to work these out using the
+# "ip" command
+#
+# IPADDRESSES="192.0.2.1 2001:db8:1:2:3::1"
+<% unless (v=scope.lookupvar('apt::apticron::ipaddresses')).to_s == "false" -%>
+IPADDRESSES="<%= v %>"
+<% end -%>
+
+
+#
+# Set NOTIFY_HOLDS="0" if you don't want to be notified about new versions of
+# packages on hold in your system. The default behavior is downloading and
+# listing them as any other package.
+#
+# NOTIFY_HOLDS="0"
+NOTIFY_HOLDS="<%= scope.lookupvar('apt::apticron::notifyholds') %>"
+
+#
+# Set NOTIFY_NEW="0" if you don't want to be notified about packages which
+# are not installed in your system. Yes, it's possible! There are some issues
+# related to systems which have mixed stable/unstable sources. In these cases
+# apt-get will consider for example that packages with "Priority:
+# required"/"Essential: yes" in unstable but not in stable should be installed,
+# so they will be listed in dist-upgrade output. Please take a look at
+# http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=531002#44
+#
+# NOTIFY_NEW="0"
+NOTIFY_NEW="<%= scope.lookupvar('apt::apticron::notifynew') %>"
+
+#
+# Set CUSTOM_SUBJECT if you want to replace the default subject used in
+# the notification e-mails. This may help filtering/sorting client-side e-mail.
+#
+# CUSTOM_SUBJECT=""
+CUSTOM_SUBJECT="<%= scope.lookupvar('apt::apticron::customsubject') %>"
+
diff --git a/puppet/modules/apt/templates/Debian/apticron_wheezy.erb b/puppet/modules/apt/templates/Debian/apticron_wheezy.erb
new file mode 100644
index 00000000..655854e6
--- /dev/null
+++ b/puppet/modules/apt/templates/Debian/apticron_wheezy.erb
@@ -0,0 +1,80 @@
+# apticron.conf
+#
+# set EMAIL to a space separated list of addresses which will be notified of
+# impending updates
+#
+EMAIL="<%= scope.lookupvar('apt::apticron::email') %>"
+
+#
+# Set DIFF_ONLY to "1" to only output the difference of the current run
+# compared to the last run (ie. only new upgrades since the last run). If there
+# are no differences, no output/email will be generated. By default, apticron
+# will output everything that needs to be upgraded.
+#
+DIFF_ONLY="<%= scope.lookupvar('apt::apticron::diff_only') %>"
+
+#
+# Set LISTCHANGES_PROFILE if you would like apticron to invoke apt-listchanges
+# with the --profile option. You should add a corresponding profile to
+# /etc/apt/listchanges.conf
+#
+LISTCHANGES_PROFILE="<%= scope.lookupvar('apt::apticron::listchanges_profile') %>"
+
+#
+# Set SYSTEM if you would like apticron to use something other than the output
+# of "hostname -f" for the system name in the mails it generates
+#
+# SYSTEM="foobar.example.com"
+<% unless (v=scope.lookupvar('apt::apticron::system')).to_s == "false" -%>
+SYSTEM="<%= v %>"
+<% end -%>
+
+#
+# Set IPADDRESSNUM if you would like to configure the maximal number of IP
+# addresses apticron displays. The default is to display 1 address of each
+# family type (inet, inet6), if available.
+#
+# IPADDRESSNUM="1"
+<% unless (v=scope.lookupvar('apt::apticron::ipaddressnum')).to_s == "false" -%>
+IPADDRESSNUM="<%= v %>"
+<% end -%>
+
+#
+# Set IPADDRESSES to a whitespace separated list of reachable addresses for
+# this system. By default, apticron will try to work these out using the
+# "ip" command
+#
+# IPADDRESSES="192.0.2.1 2001:db8:1:2:3::1"
+<% unless (v=scope.lookupvar('apt::apticron::ipaddresses')).to_s == "false" -%>
+IPADDRESSES=<%= v %>"
+<% end -%>
+
+#
+# Set NOTIFY_HOLDS="0" if you don't want to be notified about new versions of
+# packages on hold in your system. The default behavior is downloading and
+# listing them as any other package.
+#
+# NOTIFY_HOLDS="0"
+NOTIFY_HOLDS="<%= scope.lookupvar('apt::apticron::notifyholds') %>"
+
+#
+# Set NOTIFY_NEW="0" if you don't want to be notified about packages which
+# are not installed in your system. Yes, it's possible! There are some issues
+# related to systems which have mixed stable/unstable sources. In these cases
+# apt-get will consider for example that packages with "Priority:
+# required"/"Essential: yes" in unstable but not in stable should be installed,
+# so they will be listed in dist-upgrade output. Please take a look at
+# http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=531002#44
+#
+# NOTIFY_NEW="0"
+NOTIFY_NEW="<%= scope.lookupvar('apt::apticron::notifynew') %>"
+
+
+#
+# Set CUSTOM_SUBJECT if you want to replace the default subject used in
+# the notification e-mails. This may help filtering/sorting client-side e-mail.
+# If you want to use internal vars please use single quotes here. Ex:
+# ='[apticron] : package update(s)'
+#
+# CUSTOM_SUBJECT=""
+CUSTOM_SUBJECT="<%= scope.lookupvar('apt::apticron::customsubject') %>"
diff --git a/puppet/modules/apt/templates/Debian/listchanges_jessie.erb b/puppet/modules/apt/templates/Debian/listchanges_jessie.erb
new file mode 120000
index 00000000..74ab496d
--- /dev/null
+++ b/puppet/modules/apt/templates/Debian/listchanges_jessie.erb
@@ -0,0 +1 @@
+listchanges_lenny.erb \ No newline at end of file
diff --git a/puppet/modules/apt/templates/Debian/listchanges_lenny.erb b/puppet/modules/apt/templates/Debian/listchanges_lenny.erb
new file mode 100644
index 00000000..1025dd0e
--- /dev/null
+++ b/puppet/modules/apt/templates/Debian/listchanges_lenny.erb
@@ -0,0 +1,7 @@
+[apt]
+frontend=<%= scope.lookupvar('apt::listchanges::frontend') %>
+email_address=<%= scope.lookupvar('apt::listchanges::email') %>
+confirm=<%= scope.lookupvar('apt::listchanges::confirm') %>
+save_seen=<%= scope.lookupvar('apt::listchanges::saveseen') %>
+which=<%= scope.lookupvar('apt::listchanges::which') %>
+
diff --git a/puppet/modules/apt/templates/Debian/listchanges_sid.erb b/puppet/modules/apt/templates/Debian/listchanges_sid.erb
new file mode 120000
index 00000000..74ab496d
--- /dev/null
+++ b/puppet/modules/apt/templates/Debian/listchanges_sid.erb
@@ -0,0 +1 @@
+listchanges_lenny.erb \ No newline at end of file
diff --git a/puppet/modules/apt/templates/Debian/listchanges_squeeze.erb b/puppet/modules/apt/templates/Debian/listchanges_squeeze.erb
new file mode 120000
index 00000000..74ab496d
--- /dev/null
+++ b/puppet/modules/apt/templates/Debian/listchanges_squeeze.erb
@@ -0,0 +1 @@
+listchanges_lenny.erb \ No newline at end of file
diff --git a/puppet/modules/apt/templates/Debian/listchanges_wheezy.erb b/puppet/modules/apt/templates/Debian/listchanges_wheezy.erb
new file mode 120000
index 00000000..74ab496d
--- /dev/null
+++ b/puppet/modules/apt/templates/Debian/listchanges_wheezy.erb
@@ -0,0 +1 @@
+listchanges_lenny.erb \ No newline at end of file
diff --git a/puppet/modules/apt/templates/Debian/preferences_jessie.erb b/puppet/modules/apt/templates/Debian/preferences_jessie.erb
new file mode 100644
index 00000000..0888abe5
--- /dev/null
+++ b/puppet/modules/apt/templates/Debian/preferences_jessie.erb
@@ -0,0 +1,14 @@
+Explanation: Debian <%= codename=scope.lookupvar('::debian_codename') %>
+Package: *
+Pin: release o=Debian,n=<%= codename %>
+Pin-Priority: 990
+
+Explanation: Debian sid
+Package: *
+Pin: release o=Debian,n=sid
+Pin-Priority: 1
+
+Explanation: Debian fallback
+Package: *
+Pin: release o=Debian
+Pin-Priority: -10
diff --git a/puppet/modules/apt/templates/Debian/preferences_lenny.erb b/puppet/modules/apt/templates/Debian/preferences_lenny.erb
new file mode 100644
index 00000000..65001687
--- /dev/null
+++ b/puppet/modules/apt/templates/Debian/preferences_lenny.erb
@@ -0,0 +1,25 @@
+Explanation: Debian <%= codename=scope.lookupvar('::debian_codename') %>
+Package: *
+Pin: release o=Debian,a=<%= scope.lookupvar('::debian_release') %>,v=5*
+Pin-Priority: 990
+
+Explanation: Debian backports
+Package: *
+Pin: origin backports.debian.org
+Pin-Priority: 200
+
+Explanation: Debian <%= next_release=scope.lookupvar('::debian_nextrelease') %>
+Package: *
+Pin: release o=Debian,a=<%= next_release %>
+Pin-Priority: 2
+
+Explanation: Debian sid
+Package: *
+Pin: release o=Debian,a=unstable
+Pin-Priority: 1
+
+Explanation: Debian fallback
+Package: *
+Pin: release o=Debian
+Pin-Priority: -10
+
diff --git a/puppet/modules/apt/templates/Debian/preferences_sid.erb b/puppet/modules/apt/templates/Debian/preferences_sid.erb
new file mode 100644
index 00000000..eb185543
--- /dev/null
+++ b/puppet/modules/apt/templates/Debian/preferences_sid.erb
@@ -0,0 +1,10 @@
+Explanation: Debian sid
+Package: *
+Pin: release o=Debian,n=sid
+Pin-Priority: 990
+
+Explanation: Debian fallback
+Package: *
+Pin: release o=Debian
+Pin-Priority: -10
+
diff --git a/puppet/modules/apt/templates/Debian/preferences_squeeze.erb b/puppet/modules/apt/templates/Debian/preferences_squeeze.erb
new file mode 100644
index 00000000..885edc73
--- /dev/null
+++ b/puppet/modules/apt/templates/Debian/preferences_squeeze.erb
@@ -0,0 +1,30 @@
+Explanation: Debian <%= codename=scope.lookupvar('::debian_codename') %>
+Package: *
+Pin: release o=Debian,n=<%= codename %>
+Pin-Priority: 990
+
+Explanation: Debian <%= codename %>-updates
+Package: *
+Pin: release o=Debian,n=<%= codename %>-updates
+Pin-Priority: 990
+
+Explanation: Debian <%= codename %>-lts
+Package: *
+Pin: release o=Debian,n=<%= codename %>-lts
+Pin-Priority: 990
+
+Explanation: Debian <%= next_codename=scope.lookupvar('::debian_nextcodename') %>
+Package: *
+Pin: release o=Debian,n=<%= next_codename %>
+Pin-Priority: 2
+
+Explanation: Debian sid
+Package: *
+Pin: release o=Debian,n=sid
+Pin-Priority: 1
+
+Explanation: Debian fallback
+Package: *
+Pin: release o=Debian
+Pin-Priority: -10
+
diff --git a/puppet/modules/apt/templates/Debian/preferences_wheezy.erb b/puppet/modules/apt/templates/Debian/preferences_wheezy.erb
new file mode 100644
index 00000000..106108d5
--- /dev/null
+++ b/puppet/modules/apt/templates/Debian/preferences_wheezy.erb
@@ -0,0 +1,20 @@
+Explanation: Debian <%= codename=scope.lookupvar('::debian_codename') %>
+Package: *
+Pin: release o=Debian,n=<%= codename %>
+Pin-Priority: 990
+
+Explanation: Debian <%= codename %>-updates
+Package: *
+Pin: release o=Debian,n=<%= codename %>-updates
+Pin-Priority: 990
+
+Explanation: Debian sid
+Package: *
+Pin: release o=Debian,n=sid
+Pin-Priority: 1
+
+Explanation: Debian fallback
+Package: *
+Pin: release o=Debian
+Pin-Priority: -10
+
diff --git a/puppet/modules/apt/templates/Debian/sources.list.erb b/puppet/modules/apt/templates/Debian/sources.list.erb
new file mode 100644
index 00000000..44eea538
--- /dev/null
+++ b/puppet/modules/apt/templates/Debian/sources.list.erb
@@ -0,0 +1,76 @@
+# This file is managed by puppet
+# all local modifications will be overwritten
+
+### Debian current: <%= codename=scope.lookupvar('::debian_codename') %>
+
+# basic
+deb <%= debian_url=scope.lookupvar('apt::debian_url') %> <%= codename %> <%= lrepos=scope.lookupvar('apt::real_repos') %>
+<% if include_src=scope.lookupvar('apt::include_src') -%>
+deb-src <%= debian_url %> <%= codename %> <%= lrepos %>
+<% end -%>
+
+# security
+<% if ((release=scope.lookupvar('::debian_release')) == "stable" || release == "oldstable") -%>
+deb <%= security_url=scope.lookupvar('apt::security_url') %> <%= codename %>/updates <%= lrepos %>
+<% if include_src -%>
+deb-src <%= security_url %> <%= codename %>/updates <%= lrepos %>
+<% end -%>
+<% else -%>
+# There is no security support for <%= release %>
+<% end -%>
+
+<% if use_volatile=scope.lookupvar('apt::use_volatile') -%>
+# volatile
+<% if (release == "testing" || release == "unstable" || release == "experimental") -%>
+# There is no volatile archive for <%= release %>
+<% else -%>
+deb <%= debian_url %> <%= codename %>-updates <%= lrepos %>
+<% if include_src -%>
+deb-src <%= debian_url %> <%= codename %>-updates <%= lrepos %>
+<% end
+ end
+ end -%>
+
+<% if use_lts=scope.lookupvar('apt::use_lts') -%>
+# LTS
+<% if release_lts=scope.lookupvar('::debian_lts') == "false" -%>
+# There is no LTS archive for <%= release %>
+<% else -%>
+deb <%= debian_url %> <%= codename %>-lts <%= lrepos %>
+<% if include_src -%>
+deb-src <%= debian_url %> <%= codename %>-lts <%= lrepos %>
+<% end -%>
+<% end -%>
+<% end -%>
+
+<% if next_release=scope.lookupvar('apt::use_next_release') -%>
+### Debian next: <%= next_release=scope.lookupvar('::debian_nextrelease') ; next_codename=scope.lookupvar('::debian_nextcodename') %>
+
+# basic
+deb <%= debian_url %> <%= next_codename %> <%= lrepos %>
+<% if include_src -%>
+deb-src <%= debian_url %> <%= next_codename %> <%= lrepos %>
+<% end -%>
+
+# security
+<% if (next_release == "unstable" || next_release == "experimental") -%>
+# There is no security support for <%= next_release %>
+<% else -%>
+deb <%= security_url %> <%= next_codename %>/updates <%= lrepos %>
+<% if include_src then -%>
+deb-src <%= security_url %> <%= next_codename %>/updates <%= lrepos %>
+<% end
+ end -%>
+
+<% if use_volatile -%>
+# volatile
+<% if (next_release == "testing" || next_release == "unstable" || next_release == "experimental") -%>
+# There is no volatile archive for <%= next_release %>
+<% else -%>
+deb <%= debian_url %> <%= next_codename %>-updates <%= lrepos %>
+<% if include_src -%>
+deb-src <%= debian_url %> <%= next_codename %>-updates <%= lrepos %>
+<% end
+ end
+ end
+ end -%>
diff --git a/puppet/modules/apt/templates/Ubuntu/preferences_lucid.erb b/puppet/modules/apt/templates/Ubuntu/preferences_lucid.erb
new file mode 120000
index 00000000..3debe4fc
--- /dev/null
+++ b/puppet/modules/apt/templates/Ubuntu/preferences_lucid.erb
@@ -0,0 +1 @@
+preferences_maverick.erb \ No newline at end of file
diff --git a/puppet/modules/apt/templates/Ubuntu/preferences_maverick.erb b/puppet/modules/apt/templates/Ubuntu/preferences_maverick.erb
new file mode 100644
index 00000000..8e5481d3
--- /dev/null
+++ b/puppet/modules/apt/templates/Ubuntu/preferences_maverick.erb
@@ -0,0 +1,30 @@
+Explanation: Ubuntu <%= codename=scope.lookupvar('::ubuntu_codename') %> security
+Package: *
+Pin: release o=Ubuntu,a=<%= codename %>-security
+Pin-Priority: 990
+
+Explanation: Ubuntu <%= codename %> updates
+Package: *
+Pin: release o=Ubuntu,a=<%= codename %>-updates
+Pin-Priority: 980
+
+Explanation: Ubuntu <%= codename %>
+Package: *
+Pin: release o=Ubuntu,a=<%= codename %>
+Pin-Priority: 970
+
+Explanation: Ubuntu backports
+Package: *
+Pin: release a=<%= codename %>-backports
+Pin-Priority: 200
+
+Explanation: Ubuntu <%= next_release=scope.lookupvar('::ubuntu_nextcodename') %>
+Package: *
+Pin: release o=Ubuntu,a=<%= next_release %>
+Pin-Priority: 2
+
+Explanation: Ubuntu fallback
+Package: *
+Pin: release o=Ubuntu
+Pin-Priority: -10
+
diff --git a/puppet/modules/apt/templates/Ubuntu/preferences_oneiric.erb b/puppet/modules/apt/templates/Ubuntu/preferences_oneiric.erb
new file mode 120000
index 00000000..3debe4fc
--- /dev/null
+++ b/puppet/modules/apt/templates/Ubuntu/preferences_oneiric.erb
@@ -0,0 +1 @@
+preferences_maverick.erb \ No newline at end of file
diff --git a/puppet/modules/apt/templates/Ubuntu/preferences_precise.erb b/puppet/modules/apt/templates/Ubuntu/preferences_precise.erb
new file mode 120000
index 00000000..3debe4fc
--- /dev/null
+++ b/puppet/modules/apt/templates/Ubuntu/preferences_precise.erb
@@ -0,0 +1 @@
+preferences_maverick.erb \ No newline at end of file
diff --git a/puppet/modules/apt/templates/Ubuntu/preferences_utopic.erb b/puppet/modules/apt/templates/Ubuntu/preferences_utopic.erb
new file mode 120000
index 00000000..3debe4fc
--- /dev/null
+++ b/puppet/modules/apt/templates/Ubuntu/preferences_utopic.erb
@@ -0,0 +1 @@
+preferences_maverick.erb \ No newline at end of file
diff --git a/puppet/modules/apt/templates/Ubuntu/preferences_vivid.erb b/puppet/modules/apt/templates/Ubuntu/preferences_vivid.erb
new file mode 120000
index 00000000..3debe4fc
--- /dev/null
+++ b/puppet/modules/apt/templates/Ubuntu/preferences_vivid.erb
@@ -0,0 +1 @@
+preferences_maverick.erb \ No newline at end of file
diff --git a/puppet/modules/apt/templates/Ubuntu/preferences_wily.erb b/puppet/modules/apt/templates/Ubuntu/preferences_wily.erb
new file mode 120000
index 00000000..3debe4fc
--- /dev/null
+++ b/puppet/modules/apt/templates/Ubuntu/preferences_wily.erb
@@ -0,0 +1 @@
+preferences_maverick.erb \ No newline at end of file
diff --git a/puppet/modules/apt/templates/Ubuntu/preferences_xenial.erb b/puppet/modules/apt/templates/Ubuntu/preferences_xenial.erb
new file mode 120000
index 00000000..3debe4fc
--- /dev/null
+++ b/puppet/modules/apt/templates/Ubuntu/preferences_xenial.erb
@@ -0,0 +1 @@
+preferences_maverick.erb \ No newline at end of file
diff --git a/puppet/modules/apt/templates/Ubuntu/sources.list.erb b/puppet/modules/apt/templates/Ubuntu/sources.list.erb
new file mode 100644
index 00000000..e6d2f643
--- /dev/null
+++ b/puppet/modules/apt/templates/Ubuntu/sources.list.erb
@@ -0,0 +1,22 @@
+# This file is managed by puppet
+# all local modifications will be overwritten
+
+# basic <%= codename=scope.lookupvar('::ubuntu_codename') %>
+deb <%= ubuntu_url=scope.lookupvar('apt::ubuntu_url') %> <%= codename %> <%= lrepos=scope.lookupvar('apt::real_repos') %>
+<% if include_src=scope.lookupvar('apt::include_src') -%>
+deb-src <%= ubuntu_url %> <%= codename %> <%= lrepos %>
+<% end -%>
+
+<% if use_volatile=scope.lookupvar('apt::use_volatile') -%>
+# updates
+deb <%= ubuntu_url %> <%= codename %>-updates <%= lrepos %>
+<% if include_src -%>
+deb-src <%= ubuntu_url %> <%= codename %>-updates <%= lrepos %>
+<% end
+ end -%>
+
+# security suppport
+deb <%= ubuntu_url %> <%= codename %>-security <%= lrepos %>
+<% if include_src -%>
+deb-src <%= ubuntu_url %> <%= codename %>-security <%= lrepos %>
+<% end -%>
diff --git a/puppet/modules/apt/templates/preferences_snippet.erb b/puppet/modules/apt/templates/preferences_snippet.erb
new file mode 100644
index 00000000..903e73d6
--- /dev/null
+++ b/puppet/modules/apt/templates/preferences_snippet.erb
@@ -0,0 +1,4 @@
+Package: <%= @real_package %>
+Pin: <%= @pin %>
+Pin-Priority: <%= @priority %>
+
diff --git a/puppet/modules/apt/templates/preferences_snippet_release.erb b/puppet/modules/apt/templates/preferences_snippet_release.erb
new file mode 100644
index 00000000..b95d3f81
--- /dev/null
+++ b/puppet/modules/apt/templates/preferences_snippet_release.erb
@@ -0,0 +1,4 @@
+Package: <%= @real_package %>
+Pin: release a=<%= @release %>
+Pin-Priority: <%= @priority %>
+
diff --git a/puppet/modules/augeas/.fixtures.yml b/puppet/modules/augeas/.fixtures.yml
new file mode 100644
index 00000000..f074a0ff
--- /dev/null
+++ b/puppet/modules/augeas/.fixtures.yml
@@ -0,0 +1,5 @@
+fixtures:
+ repositories:
+ "stdlib": "git://github.com/puppetlabs/puppetlabs-stdlib.git"
+ symlinks:
+ "augeas": "#{source_dir}"
diff --git a/puppet/modules/augeas/.gitignore b/puppet/modules/augeas/.gitignore
new file mode 100644
index 00000000..b5b7a00d
--- /dev/null
+++ b/puppet/modules/augeas/.gitignore
@@ -0,0 +1,7 @@
+pkg/
+Gemfile.lock
+vendor/
+spec/fixtures/
+.vagrant/
+.bundle/
+coverage/
diff --git a/.puppet-lint.rc b/puppet/modules/augeas/.puppet-lint.rc
index d8f5c59e..d8f5c59e 100644
--- a/.puppet-lint.rc
+++ b/puppet/modules/augeas/.puppet-lint.rc
diff --git a/puppet/modules/augeas/.sync.yml b/puppet/modules/augeas/.sync.yml
new file mode 100644
index 00000000..677d00cf
--- /dev/null
+++ b/puppet/modules/augeas/.sync.yml
@@ -0,0 +1,3 @@
+---
+.travis.yml:
+ forge_password: "GYBg84VC7Mx8BhAJ/56VjPU8tctatVVafGjuM9rJVmvJpbHkTz+XORHkvdVxCCkAkiq0/NZjwWpbxkQGMz0MxnXT5V/H90+h6YRHnWIEEqlW+5dR76uKZ9mO65cqk+l8UA+GUr5ZWKTS0fEJzjNR8aFM56DaM1u+SWIfjBXfE0k="
diff --git a/puppet/modules/augeas/.travis.yml b/puppet/modules/augeas/.travis.yml
new file mode 100644
index 00000000..777290cc
--- /dev/null
+++ b/puppet/modules/augeas/.travis.yml
@@ -0,0 +1,30 @@
+---
+language: ruby
+sudo: false
+cache: bundler
+bundler_args: --without system_tests
+script: ["bundle exec rake validate", "bundle exec rake lint", "bundle exec rake spec SPEC_OPTS='--format documentation'", "bundle exec rake metadata"]
+matrix:
+ fast_finish: true
+ include:
+ - rvm: 1.8.7
+ env: PUPPET_GEM_VERSION="~> 3.0" FACTER_GEM_VERSION="~> 1.7.0"
+ - rvm: 1.9.3
+ env: PUPPET_GEM_VERSION="~> 3.0"
+ - rvm: 2.0.0
+ env: PUPPET_GEM_VERSION="~> 3.0"
+ - rvm: 2.0.0
+ env: PUPPET_GEM_VERSION="~> 3.0" FUTURE_PARSER="yes"
+notifications:
+ email: false
+deploy:
+ provider: puppetforge
+ user: camptocamp
+ password:
+ secure: "GYBg84VC7Mx8BhAJ/56VjPU8tctatVVafGjuM9rJVmvJpbHkTz+XORHkvdVxCCkAkiq0/NZjwWpbxkQGMz0MxnXT5V/H90+h6YRHnWIEEqlW+5dR76uKZ9mO65cqk+l8UA+GUr5ZWKTS0fEJzjNR8aFM56DaM1u+SWIfjBXfE0k="
+ on:
+ tags: true
+ # all_branches is required to use tags
+ all_branches: true
+ # Only publish if our main Ruby target builds
+ rvm: 1.9.3
diff --git a/puppet/modules/augeas/CHANGELOG.md b/puppet/modules/augeas/CHANGELOG.md
new file mode 100644
index 00000000..3bebf66a
--- /dev/null
+++ b/puppet/modules/augeas/CHANGELOG.md
@@ -0,0 +1,45 @@
+## 2015-01-19 - Release 1.1.6
+
+- Add puppet-ling plugins
+
+##2015-01-12 - Release 1.1.5
+
+- Fix LICENSE file
+- Add some puppet-lint plugins to Gemfile
+
+##2015-01-07 - Release 1.1.4
+
+- Manage unit tests with rspec-puppet-facts
+
+##2014-12-09 - Release 1.1.0
+
+- Add future parser tests
+- Convert specs to rspec3 syntax
+- Fix metadata.json
+
+##2014-11-17 - Release 1.0.3
+
+- Lint metadata.json
+
+##2014-11-04 - Release 1.0.2
+
+- Fix path in unit tests
+- Drop Puppet 2.7 support
+
+##2014-10-28 - Release 1.0.1
+- Add path to exec in augeas::lens
+
+##2014-10-20 - Release 1.0.0
+- Linting
+- Setup automatic Forge releases
+
+##2014-10-06 - Release 0.3.2
+- Remove symlink in spec/ directory (Fix #40)
+
+##2014-09-23 - Release 0.3.1
+- Centralize metadata files
+
+##2014-07-02 - Release 0.3.0
+###Summary
+- Add purge parameter
+- Cleanup unscoped variables
diff --git a/puppet/modules/augeas/Gemfile b/puppet/modules/augeas/Gemfile
new file mode 100644
index 00000000..a3a9eca0
--- /dev/null
+++ b/puppet/modules/augeas/Gemfile
@@ -0,0 +1,41 @@
+source ENV['GEM_SOURCE'] || "https://rubygems.org"
+
+group :development, :unit_tests do
+ gem 'rake', :require => false
+ gem 'rspec', '~> 3.1.0', :require => false
+ gem 'rspec-puppet', :require => false, :git => 'https://github.com/camptocamp/rspec-puppet', :branch => 'rspec3'
+ gem 'puppetlabs_spec_helper', :require => false
+ gem 'metadata-json-lint', :require => false
+ gem 'puppet-lint', :require => false
+ gem 'puppet-lint-unquoted_string-check', :require => false
+ gem 'puppet-lint-empty_string-check', :require => false
+ gem 'puppet-lint-spaceship_operator_without_tag-check', :require => false
+ gem 'puppet-lint-variable_contains_upcase', :require => false
+ gem 'puppet-lint-absolute_classname-check', :require => false
+ gem 'puppet-lint-undef_in_function-check', :require => false
+ gem 'puppet-lint-leading_zero-check', :require => false
+ gem 'puppet-lint-trailing_comma-check', :require => false
+ gem 'puppet-lint-file_ensure-check', :require => false
+ gem 'puppet-lint-version_comparison-check', :require => false
+ gem 'rspec-puppet-facts', :require => false
+end
+
+group :system_tests do
+ gem 'beaker', :require => false
+ gem 'beaker-rspec', :require => false
+ gem 'serverspec', :require => false
+end
+
+if facterversion = ENV['FACTER_GEM_VERSION']
+ gem 'facter', facterversion, :require => false
+else
+ gem 'facter', :require => false
+end
+
+if puppetversion = ENV['PUPPET_GEM_VERSION']
+ gem 'puppet', puppetversion, :require => false
+else
+ gem 'puppet', :require => false
+end
+
+# vim:ft=ruby
diff --git a/puppet/modules/augeas/LICENSE b/puppet/modules/augeas/LICENSE
new file mode 100644
index 00000000..8d968b6c
--- /dev/null
+++ b/puppet/modules/augeas/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/puppet/modules/augeas/README.md b/puppet/modules/augeas/README.md
new file mode 100644
index 00000000..e2055b07
--- /dev/null
+++ b/puppet/modules/augeas/README.md
@@ -0,0 +1,76 @@
+# Augeas Puppet module
+
+[![Puppet Forge](http://img.shields.io/puppetforge/v/camptocamp/augeas.svg)](https://forge.puppetlabs.com/camptocamp/augeas)
+[![Build Status](https://travis-ci.org/camptocamp/puppet-augeas.png?branch=master)](https://travis-ci.org/camptocamp/puppet-augeas)
+
+**Install and configure Augeas.**
+
+This module is provided by [Camptocamp](http://www.camptocamp.com/)
+
+## Usage
+
+Simple usage:
+
+ include augeas
+
+### Classes
+
+The module provides an `augeas` class which installs and configures Augeas.
+
+
+* lets you force the augeas version by defining `$augeas_version`, otherwise puppet will
+ only ensure the packages are present;
+* lets you force the ruby library version by defining `$augeas_ruby_version`, otherwise puppet will
+ only ensure the libaugeas-ruby version will be installed according to internal critera;
+* provides an `augeas()` master-side function to manipulate strings using Augeas;
+
+Note: the `augeas` class realizes all `augeas` resources in order to ensure they are managed after the required Augeas packages.
+
+
+### Definitions
+
+#### `augeas::lens`
+
+The `augeas::lens` definition allows you to deploy an Augeas lens and any associated test files, running unit tests and not installing if they fail:
+
+Parameters:
+
+- *ensure*: present/absent
+- *lens_source*: the source for the lens
+- *test_source*: optionally, the source for the test file.
+- *stock_since*: optionally, indicate in which version of Augeas
+ the lens became stock, so it will not be deployed above that version.
+
+Example usage:
+
+ augeas::lens { 'networkmanager':
+ lens_source => 'puppet:///modules/networkmanager/lenses/networkmanager.aug',
+ test_source => 'puppet:///modules/networkmanager/lenses/test_networkmanager.aug',
+ stock_since => '1.0.0',
+ }
+
+### Functions
+
+#### `augeas()`
+
+Modifies a string using Augeas.
+
+*Example:*
+
+ augeas("proc /proc proc nodev,noexec,nosuid 0 0\n", 'Fstab.lns', ['rm ./1/opt[3]'])
+
+Would result in:
+
+ "proc /proc proc nodev,noexec 0 0\n"
+
+
+- *Type*: rvalue
+
+## Contributing
+
+Please report bugs and feature request using [GitHub issue
+tracker](https://github.com/camptocamp/puppet-augeas/issues).
+
+For pull requests, it is very much appreciated to check your Puppet manifest
+with [puppet-lint](https://github.com/camptocamp/puppet-augeas/issues) to follow the recommended Puppet style guidelines from the
+[Puppet Labs style guide](http://docs.puppetlabs.com/guides/style_guide.html).
diff --git a/puppet/modules/augeas/Rakefile b/puppet/modules/augeas/Rakefile
new file mode 100644
index 00000000..f87e6088
--- /dev/null
+++ b/puppet/modules/augeas/Rakefile
@@ -0,0 +1,11 @@
+require 'puppetlabs_spec_helper/rake_tasks'
+require 'puppet-lint/tasks/puppet-lint'
+
+Rake::Task[:lint].clear
+PuppetLint::RakeTask.new :lint do |config|
+ config.ignore_paths = ["spec/**/*.pp", "pkg/**/*.pp", "vendor/**/*.pp"]
+ config.disable_checks = ['80chars']
+ config.fail_on_warnings = true
+end
+
+PuppetSyntax.exclude_paths = ["spec/fixtures/**/*.pp", "vendor/**/*"]
diff --git a/puppet/modules/augeas/lib/puppet/parser/functions/augeas.rb b/puppet/modules/augeas/lib/puppet/parser/functions/augeas.rb
new file mode 100644
index 00000000..08026f77
--- /dev/null
+++ b/puppet/modules/augeas/lib/puppet/parser/functions/augeas.rb
@@ -0,0 +1,68 @@
+#
+# augeas.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:augeas, :type => :rvalue, :doc => <<-EOS
+Modifies a string using Augeas.
+
+*Example:*
+
+ augeas("proc /proc proc nodev,noexec,nosuid 0 0\n", 'Fstab.lns', ['rm ./1/opt[3]'])
+
+Would result in:
+
+ "proc /proc proc nodev,noexec 0 0\n"
+ EOS
+ ) do |arguments|
+ unless Puppet.features.augeas?
+ raise Puppet::ParseError, ('augeas(): this function requires the augeas feature. See http://projects.puppetlabs.com/projects/puppet/wiki/Puppet_Augeas#Pre-requisites for how to activate it.')
+ end
+
+ # Check that 2 arguments have been given ...
+ raise(Puppet::ParseError, 'augeas(): Wrong number of arguments ' +
+ "given (#{arguments.size} for 3)") if arguments.size != 3
+
+ content = arguments[0]
+ lens = arguments[1]
+ changes = arguments[2]
+
+ # Check arguments
+ raise(Puppet::ParseError, 'augeas(): content must be a string') unless content.is_a?(String)
+ raise(Puppet::ParseError, 'augeas(): lens must be a string') unless lens.is_a?(String)
+ raise(Puppet::ParseError, 'augeas(): changes must be an array') unless changes.is_a?(Array)
+
+ require 'augeas'
+ aug = Augeas::open(nil, nil, Augeas::NO_MODL_AUTOLOAD)
+ augeas_version = aug.get('/augeas/version')
+ raise(Puppet::ParseError, 'augeas(): requires Augeas 1.0.0 or greater') unless Puppet::Util::Package.versioncmp(augeas_version, '1.0.0') >= 0
+ raise(Puppet::ParseError, 'augeas(): requires ruby-augeas 0.5.0 or greater') unless aug.methods.include?('text_store')
+
+ result = nil
+ begin
+ aug.set('/input', content)
+ aug.text_store(lens, '/input', '/store')
+ unless aug.match("/augeas/text/store//error").empty?
+ error = aug.get("/augeas/text/store//error/message")
+ raise Puppet::ParseError, "augeas(): Failed to parse string with lens #{lens}: #{error}"
+ end
+
+ # Apply changes
+ aug.context = '/store'
+ changes.each do |c|
+ r = aug.srun(c)
+ raise Puppet::ParseError, "augeas(): Failed to apply change to tree" unless r and r[0] >= 0
+ end
+ unless aug.text_retrieve(lens, '/input', '/store', '/output')
+ error = aug.get("/augeas/text/store//error/message")
+ raise Puppet::ParseError, "augeas(): Failed to apply changes with lens #{lens}: #{error}"
+ end
+ result = aug.get("/output")
+ ensure
+ aug.close
+ end
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/augeas/manifests/files.pp b/puppet/modules/augeas/manifests/files.pp
new file mode 100644
index 00000000..c73c19bb
--- /dev/null
+++ b/puppet/modules/augeas/manifests/files.pp
@@ -0,0 +1,36 @@
+# Class: augeas::files
+#
+# Sets up directories and files for Augeas
+#
+class augeas::files {
+ $lens_dir = $augeas::lens_dir
+
+ # ensure no file not managed by puppet ends up in there.
+ file { $lens_dir:
+ ensure => directory,
+ purge => $::augeas::purge,
+ force => true,
+ recurse => true,
+ recurselimit => 1,
+ mode => '0644',
+ owner => 'root',
+ group => 'root',
+ }
+
+ file { "${lens_dir}/dist":
+ ensure => directory,
+ purge => false,
+ mode => '0644',
+ owner => 'root',
+ group => 'root',
+ }
+
+ file { "${lens_dir}/tests":
+ ensure => directory,
+ purge => $::augeas::purge,
+ force => true,
+ mode => '0644',
+ owner => 'root',
+ group => 'root',
+ }
+}
diff --git a/puppet/modules/augeas/manifests/init.pp b/puppet/modules/augeas/manifests/init.pp
new file mode 100644
index 00000000..41f4f875
--- /dev/null
+++ b/puppet/modules/augeas/manifests/init.pp
@@ -0,0 +1,24 @@
+# Class: augeas
+#
+# Install and configure Augeas
+#
+# Parameters:
+# ['version'] - the desired version of Augeas
+# ['ruby_version'] - the desired version of the Ruby bindings for Augeas
+# ['lens_dir'] - the lens directory to use
+# ['purge'] - whether to purge lens directories
+class augeas (
+ $version = present,
+ $ruby_version = present,
+ $lens_dir = $augeas::params::lens_dir,
+ $purge = true,
+) inherits augeas::params {
+
+ class {'::augeas::packages': } ->
+ class {'::augeas::files': } ->
+ Class['augeas']
+
+ # lint:ignore:spaceship_operator_without_tag
+ Package['ruby-augeas', $augeas::params::augeas_pkgs] -> Augeas <| |>
+ # lint:endignore
+}
diff --git a/puppet/modules/augeas/manifests/lens.pp b/puppet/modules/augeas/manifests/lens.pp
new file mode 100644
index 00000000..b5b9acd6
--- /dev/null
+++ b/puppet/modules/augeas/manifests/lens.pp
@@ -0,0 +1,79 @@
+# Definition: augeas::lens
+#
+# Deploy an Augeas lens (and its test file).
+# Check the lens (and run the unit tests) automatically and remove the files if
+# the checks fail.
+#
+# Parameters:
+# ['ensure'] - present/absent
+# ['lens_source'] - the source for the lens
+# ['test_source'] - optionally, the source for the test file.
+# ['stock_since'] - optionally, indicate in which version of Augeas
+# the lens became stock, so it will not be deployed
+# above that version.
+#
+# Example usage:
+#
+# augeas::lens { 'networkmanager':
+# lens_source => 'puppet:///modules/networkmanager/lenses/networkmanager.aug',
+# test_source => 'puppet:///modules/networkmanager/lenses/test_networkmanager.aug',
+# stock_since => '1.0.0',
+# }
+#
+define augeas::lens (
+ $lens_source,
+ $ensure=present,
+ $test_source=false,
+ $stock_since=false,
+) {
+ if !defined(Class['augeas']) {
+ fail('You must declare the augeas class before using augeas::lens')
+ }
+
+ File {
+ owner => 'root',
+ group => 'root',
+ mode => '0644',
+ }
+
+ Exec {
+ path => $::path,
+ }
+
+ if (!$stock_since or versioncmp($::augeasversion, $stock_since) < 0) {
+
+ validate_re(
+ $augeas::lens_dir,
+ '/.*',
+ "'${augeas::lens_dir}' is not a valid path for lens ${name}"
+ )
+
+ $lens_dest = "${augeas::lens_dir}/${name}.aug"
+ $test_dest = "${augeas::lens_dir}/tests/test_${name}.aug"
+
+ file { $lens_dest:
+ ensure => $ensure,
+ source => $lens_source,
+ }
+
+ exec { "Typecheck lens ${name}":
+ command => "augparse -I ${augeas::lens_dir} ${lens_dest} || (rm -f ${lens_dest} && exit 1)",
+ refreshonly => true,
+ subscribe => File[$lens_dest],
+ }
+
+ if $test_source {
+ file { $test_dest:
+ ensure => $ensure,
+ source => $test_source,
+ notify => Exec["Test lens ${name}"],
+ }
+
+ exec { "Test lens ${name}":
+ command => "augparse -I ${augeas::lens_dir} ${test_dest} || (rm -f ${lens_dest} && rm -f ${test_dest} && exit 1)",
+ refreshonly => true,
+ subscribe => File[$lens_dest, $test_dest],
+ }
+ }
+ }
+}
diff --git a/puppet/modules/augeas/manifests/packages.pp b/puppet/modules/augeas/manifests/packages.pp
new file mode 100644
index 00000000..ff8628df
--- /dev/null
+++ b/puppet/modules/augeas/manifests/packages.pp
@@ -0,0 +1,14 @@
+# Class: augeas::packages
+#
+# Sets up packages for Augeas
+#
+class augeas::packages {
+ package { $::augeas::params::augeas_pkgs:
+ ensure => $::augeas::version,
+ }
+
+ package { 'ruby-augeas':
+ ensure => $::augeas::ruby_version,
+ name => $::augeas::params::ruby_pkg,
+ }
+}
diff --git a/puppet/modules/augeas/manifests/params.pp b/puppet/modules/augeas/manifests/params.pp
new file mode 100644
index 00000000..e5400339
--- /dev/null
+++ b/puppet/modules/augeas/manifests/params.pp
@@ -0,0 +1,37 @@
+# Class: augeas::params
+#
+# Default parameters for the Augeas module
+#
+class augeas::params {
+ $lens_dir = '/usr/share/augeas/lenses'
+
+ case $::osfamily {
+ 'RedHat': {
+ $ruby_pkg = 'ruby-augeas'
+ $augeas_pkgs = ['augeas', 'augeas-libs']
+ }
+
+ 'Suse': {
+ # RPM Sources: https://build.opensuse.org/project/show/systemsmanagement:puppet
+ # SLES 11 SP3
+ if versioncmp($::rubyversion, '1.8.7') >= 0 {
+ $ruby_pkg = 'ruby1.8-rubygem-ruby-augeas'
+ # SLES 12
+ } else {
+ $ruby_pkg = 'ruby2.1-rubygem-ruby-augeas'
+ }
+ $augeas_pkgs = ['augeas', 'augeas-lenses', 'libaugeas0' ]
+ }
+
+ 'Debian': {
+ if versioncmp($::rubyversion, '1.9.1') >= 0 {
+ $ruby_pkg = 'libaugeas-ruby1.9.1'
+ } else {
+ $ruby_pkg = 'libaugeas-ruby1.8'
+ }
+ $augeas_pkgs = ['augeas-lenses', 'libaugeas0', 'augeas-tools']
+ }
+
+ default: { fail("Unsupported OS family: ${::osfamily}") }
+ }
+}
diff --git a/puppet/modules/augeas/metadata.json b/puppet/modules/augeas/metadata.json
new file mode 100644
index 00000000..f6bfbf96
--- /dev/null
+++ b/puppet/modules/augeas/metadata.json
@@ -0,0 +1,62 @@
+{
+ "name": "camptocamp-augeas",
+ "version": "1.1.6",
+ "author": "camptocamp",
+ "summary": "Camptocamp Augeas module",
+ "license": "Apache-2.0",
+ "source": "https://github.com/camptocamp/puppet-augeas",
+ "project_page": "https://github.com/camptocamp/puppet-augeas",
+ "issues_url": "https://github.com/camptocamp/puppet-augeas/issues",
+ "description": "Augeas Module for Puppet",
+ "dependencies": [
+ {
+ "name": "puppetlabs/stdlib",
+ "version_requirement": ">= 3.2.0 <5.0.0"
+ }
+ ],
+ "requirements": [
+ {
+ "name": "pe",
+ "version_requirement": "3.x"
+ },
+ {
+ "name": "puppet",
+ "version_requirement": "3.x"
+ }
+ ],
+ "operatingsystem_support": [
+ {
+ "operatingsystem": "Debian",
+ "operatingsystemrelease": [
+ "6",
+ "7"
+ ]
+ },
+ {
+ "operatingsystem": "Ubuntu",
+ "operatingsystemrelease": [
+ "10.04",
+ "12.04",
+ "14.04"
+ ]
+ },
+ {
+ "operatingsystem": "RedHat",
+ "operatingsystemrelease": [
+ "5",
+ "6"
+ ]
+ }
+ ],
+ "puppet_version": [
+ "2.7",
+ "3.0",
+ "3.1",
+ "3.2",
+ "3.3",
+ "3.4",
+ "3.5",
+ "3.6",
+ "3.7"
+ ]
+}
diff --git a/spec/spec.opts b/puppet/modules/augeas/spec/.rspec
index 91cd6427..91cd6427 100644
--- a/spec/spec.opts
+++ b/puppet/modules/augeas/spec/.rspec
diff --git a/puppet/modules/augeas/spec/acceptance/nodesets/centos-7-x86_64-docker.yml b/puppet/modules/augeas/spec/acceptance/nodesets/centos-7-x86_64-docker.yml
new file mode 100644
index 00000000..599a681f
--- /dev/null
+++ b/puppet/modules/augeas/spec/acceptance/nodesets/centos-7-x86_64-docker.yml
@@ -0,0 +1,12 @@
+HOSTS:
+ centos-7-x64:
+ default_apply_opts:
+ strict_variables:
+ platform: el-7-x86_64
+ hypervisor : docker
+ image: centos:7
+ # This stops the image from being deleted on completion, speeding up the process.
+ docker_preserve_image: true
+CONFIG:
+ type: foss
+ log_level: debug
diff --git a/puppet/modules/augeas/spec/acceptance/nodesets/centos-7-x86_64-openstack.yml b/puppet/modules/augeas/spec/acceptance/nodesets/centos-7-x86_64-openstack.yml
new file mode 100644
index 00000000..37dfc5f9
--- /dev/null
+++ b/puppet/modules/augeas/spec/acceptance/nodesets/centos-7-x86_64-openstack.yml
@@ -0,0 +1,13 @@
+HOSTS:
+ centos-7-x64:
+ default_apply_opts:
+ strict_variables:
+ platform: el-7-x86_64
+ hypervisor : openstack
+ flavor: m1.small
+ image: centos-7-x86_64-genericcloud-20140929_01
+ user: centos
+CONFIG:
+ type: foss
+ log_level: debug
+ openstack_network: default
diff --git a/puppet/modules/augeas/spec/acceptance/nodesets/centos-7-x86_64-vagrant.yml b/puppet/modules/augeas/spec/acceptance/nodesets/centos-7-x86_64-vagrant.yml
new file mode 100644
index 00000000..fe0f83b8
--- /dev/null
+++ b/puppet/modules/augeas/spec/acceptance/nodesets/centos-7-x86_64-vagrant.yml
@@ -0,0 +1,10 @@
+HOSTS:
+ centos-7-x64:
+ default_apply_opts:
+ strict_variables:
+ platform: el-7-x86_64
+ hypervisor : vagrant
+ box : puppetlabs/centos-7.0-64-nocm
+CONFIG:
+ type: foss
+ log_level: debug
diff --git a/puppet/modules/augeas/spec/acceptance/nodesets/debian-6-x86_64-docker.yml b/puppet/modules/augeas/spec/acceptance/nodesets/debian-6-x86_64-docker.yml
new file mode 100644
index 00000000..0296d129
--- /dev/null
+++ b/puppet/modules/augeas/spec/acceptance/nodesets/debian-6-x86_64-docker.yml
@@ -0,0 +1,12 @@
+HOSTS:
+ debian-6-x64:
+ default_apply_opts:
+ strict_variables:
+ platform: debian-6-amd64
+ hypervisor : docker
+ image: debian:6
+ # This stops the image from being deleted on completion, speeding up the process.
+ docker_preserve_image: true
+CONFIG:
+ type: foss
+ log_level: debug
diff --git a/puppet/modules/augeas/spec/acceptance/nodesets/debian-6-x86_64-vagrant.yml b/puppet/modules/augeas/spec/acceptance/nodesets/debian-6-x86_64-vagrant.yml
new file mode 100644
index 00000000..23dae1b0
--- /dev/null
+++ b/puppet/modules/augeas/spec/acceptance/nodesets/debian-6-x86_64-vagrant.yml
@@ -0,0 +1,10 @@
+HOSTS:
+ debian-6-x64:
+ default_apply_opts:
+ strict_variables:
+ platform: debian-6-amd64
+ hypervisor : vagrant
+ box : puppetlabs/debian-6.0.10-64-nocm
+CONFIG:
+ type: foss
+ log_level: debug
diff --git a/puppet/modules/augeas/spec/acceptance/nodesets/debian-7-x86_64-docker.yml b/puppet/modules/augeas/spec/acceptance/nodesets/debian-7-x86_64-docker.yml
new file mode 100644
index 00000000..ef1c9b22
--- /dev/null
+++ b/puppet/modules/augeas/spec/acceptance/nodesets/debian-7-x86_64-docker.yml
@@ -0,0 +1,12 @@
+HOSTS:
+ debian-7-x64:
+ default_apply_opts:
+ strict_variables:
+ platform: debian-7-amd64
+ hypervisor : docker
+ image: debian:7
+ # This stops the image from being deleted on completion, speeding up the process.
+ docker_preserve_image: true
+CONFIG:
+ type: foss
+ log_level: debug
diff --git a/puppet/modules/augeas/spec/acceptance/nodesets/debian-7-x86_64-openstack.yml b/puppet/modules/augeas/spec/acceptance/nodesets/debian-7-x86_64-openstack.yml
new file mode 100644
index 00000000..e52336be
--- /dev/null
+++ b/puppet/modules/augeas/spec/acceptance/nodesets/debian-7-x86_64-openstack.yml
@@ -0,0 +1,13 @@
+HOSTS:
+ debian-7-x64:
+ default_apply_opts:
+ strict_variables:
+ platform: debian-7-amd64
+ hypervisor : openstack
+ flavor: m1.small
+ image: debian-7-amd64-20141121
+ user: debian
+CONFIG:
+ type: foss
+ log_level: debug
+ openstack_network: default
diff --git a/puppet/modules/augeas/spec/acceptance/nodesets/debian-7-x86_64-vagrant.yml b/puppet/modules/augeas/spec/acceptance/nodesets/debian-7-x86_64-vagrant.yml
new file mode 100644
index 00000000..86c2165d
--- /dev/null
+++ b/puppet/modules/augeas/spec/acceptance/nodesets/debian-7-x86_64-vagrant.yml
@@ -0,0 +1,10 @@
+HOSTS:
+ debian-7-x64:
+ default_apply_opts:
+ strict_variables:
+ platform: debian-7-amd64
+ hypervisor : vagrant
+ box : puppetlabs/debian-7.8-64-nocm
+CONFIG:
+ type: foss
+ log_level: debug
diff --git a/puppet/modules/augeas/spec/acceptance/nodesets/debian-8-x86_64-docker.yml b/puppet/modules/augeas/spec/acceptance/nodesets/debian-8-x86_64-docker.yml
new file mode 100644
index 00000000..28c3e02f
--- /dev/null
+++ b/puppet/modules/augeas/spec/acceptance/nodesets/debian-8-x86_64-docker.yml
@@ -0,0 +1,12 @@
+HOSTS:
+ debian-8-x64:
+ default_apply_opts:
+ strict_variables:
+ platform: debian-8-amd64
+ hypervisor : docker
+ image: debian:8
+ # This stops the image from being deleted on completion, speeding up the process.
+ docker_preserve_image: true
+CONFIG:
+ type: foss
+ log_level: debug
diff --git a/puppet/modules/augeas/spec/acceptance/nodesets/debian-8-x86_64-openstack.yml b/puppet/modules/augeas/spec/acceptance/nodesets/debian-8-x86_64-openstack.yml
new file mode 100644
index 00000000..194ca460
--- /dev/null
+++ b/puppet/modules/augeas/spec/acceptance/nodesets/debian-8-x86_64-openstack.yml
@@ -0,0 +1,13 @@
+HOSTS:
+ debian-8-x64:
+ default_apply_opts:
+ strict_variables:
+ platform: debian-8-amd64
+ hypervisor : openstack
+ flavor: m1.small
+ image: debian-8-amd64-20141121
+ user: debian
+CONFIG:
+ type: foss
+ openstack_network: default
+ log_level: debug
diff --git a/puppet/modules/augeas/spec/acceptance/nodesets/ubuntu-14.04-x86_64-vagrant.yml b/puppet/modules/augeas/spec/acceptance/nodesets/ubuntu-14.04-x86_64-vagrant.yml
new file mode 100644
index 00000000..a3edb70f
--- /dev/null
+++ b/puppet/modules/augeas/spec/acceptance/nodesets/ubuntu-14.04-x86_64-vagrant.yml
@@ -0,0 +1,10 @@
+HOSTS:
+ ubuntu-14.04-x64:
+ default_apply_opts:
+ strict_variables:
+ platform: ubuntu-14.04-amd64
+ hypervisor : vagrant
+ box : puppetlabs/ubuntu-14.04-64-nocm
+CONFIG:
+ type: foss
+ log_level: debug
diff --git a/puppet/modules/augeas/spec/classes/augeas_spec.rb b/puppet/modules/augeas/spec/classes/augeas_spec.rb
new file mode 100644
index 00000000..ab4241f4
--- /dev/null
+++ b/puppet/modules/augeas/spec/classes/augeas_spec.rb
@@ -0,0 +1,149 @@
+require 'spec_helper'
+
+describe 'augeas' do
+
+ context 'when on an unsupported Operating System' do
+ let (:facts) do
+ {
+ :osfamily => 'MS-DOS',
+ }
+ end
+
+ it 'should fail' do
+ expect { is_expected.to contain_package('ruby-augeas') }.to raise_error(Puppet::Error, /Unsupported OS family/)
+ end
+ end
+
+ on_supported_os.each do |os, facts|
+ context "on #{os}" do
+ let(:facts) do
+ facts
+ end
+
+ context 'without params' do
+ case facts[:osfamily]
+ when 'Debian'
+ it { is_expected.to contain_package('libaugeas0').with(
+ :ensure => 'present'
+ ) }
+ it { is_expected.to contain_package('augeas-tools').with(
+ :ensure => 'present'
+ ) }
+ it { is_expected.to contain_package('augeas-lenses').with(
+ :ensure => 'present'
+ ) }
+ case facts[:lsbdistcodename]
+ when 'squeeze', 'lucid', 'precise'
+ it { is_expected.to contain_package('ruby-augeas').with(
+ :ensure => 'present',
+ :name => 'libaugeas-ruby1.8'
+ ) }
+ else
+ it { is_expected.to contain_package('ruby-augeas').with(
+ :ensure => 'present',
+ :name => 'libaugeas-ruby1.9.1'
+ ) }
+ end
+ when 'RedHat'
+ it { is_expected.to contain_package('augeas').with(
+ :ensure => 'present'
+ ) }
+ it { is_expected.to contain_package('augeas-libs').with(
+ :ensure => 'present'
+ ) }
+ it { is_expected.to contain_package('ruby-augeas').with(
+ :ensure => 'present',
+ :name => 'ruby-augeas'
+ ) }
+ end
+ it { is_expected.to contain_file('/usr/share/augeas/lenses').with(
+ :ensure => 'directory',
+ :purge => 'true',
+ :force => 'true',
+ :recurse => 'true',
+ :recurselimit => 1
+ ) }
+ it { is_expected.to contain_file('/usr/share/augeas/lenses/dist').with(
+ :ensure => 'directory',
+ :purge => 'false'
+ ) }
+ it { is_expected.to contain_file('/usr/share/augeas/lenses/tests').with(
+ :ensure => 'directory',
+ :purge => 'true',
+ :force => 'true'
+ ).without(:recurse) }
+ end
+
+ context 'when versions are specified' do
+ let (:params) do
+ {
+ :version => '1.2.3',
+ :ruby_version => '3.2.1',
+ }
+ end
+
+ case facts[:osfamily]
+ when 'Debian'
+ it { is_expected.to contain_package('libaugeas0').with(
+ :ensure => '1.2.3'
+ ) }
+ it { is_expected.to contain_package('augeas-tools').with(
+ :ensure => '1.2.3'
+ ) }
+ it { is_expected.to contain_package('augeas-lenses').with(
+ :ensure => '1.2.3'
+ ) }
+ case facts[:lsbdistcodename]
+ when 'squeeze', 'lucid', 'precise'
+ it { is_expected.to contain_package('ruby-augeas').with(
+ :ensure => '3.2.1',
+ :name => 'libaugeas-ruby1.8'
+ ) }
+ else
+ it { is_expected.to contain_package('ruby-augeas').with(
+ :ensure => '3.2.1',
+ :name => 'libaugeas-ruby1.9.1'
+ ) }
+ end
+ when 'RedHat'
+ it { is_expected.to contain_package('augeas').with(
+ :ensure => '1.2.3'
+ ) }
+ it { is_expected.to contain_package('augeas-libs').with(
+ :ensure => '1.2.3'
+ ) }
+ it { is_expected.to contain_package('ruby-augeas').with(
+ :ensure => '3.2.1',
+ :name => 'ruby-augeas'
+ ) }
+ end
+
+ end
+
+ context 'with a non standard lens_dir' do
+ let (:params) do
+ {
+ :lens_dir => '/opt/augeas/lenses',
+ }
+ end
+
+ it { is_expected.to contain_file('/opt/augeas/lenses').with(
+ :ensure => 'directory',
+ :purge => 'true',
+ :force => 'true',
+ :recurse => 'true',
+ :recurselimit => 1
+ ) }
+ it { is_expected.to contain_file('/opt/augeas/lenses/dist').with(
+ :ensure => 'directory',
+ :purge => 'false'
+ ) }
+ it { is_expected.to contain_file('/opt/augeas/lenses/tests').with(
+ :ensure => 'directory',
+ :purge => 'true',
+ :force => 'true'
+ ).without(:recurse) }
+ end
+ end
+ end
+end
diff --git a/puppet/modules/augeas/spec/defines/augeas_lens_spec.rb b/puppet/modules/augeas/spec/defines/augeas_lens_spec.rb
new file mode 100644
index 00000000..7feeefbd
--- /dev/null
+++ b/puppet/modules/augeas/spec/defines/augeas_lens_spec.rb
@@ -0,0 +1,112 @@
+require 'spec_helper'
+
+describe 'augeas::lens' do
+ let (:title) { 'foo' }
+
+ context 'when not declaring augeas class first' do
+ let (:params) do
+ {
+ :lens_source => '/tmp/foo.aug',
+ }
+ end
+
+ it 'should error' do
+ expect {
+ is_expected.to contain_file('/usr/share/augeas/lenses/foo.aug')
+ }.to raise_error(Puppet::Error, /You must declare the augeas class/)
+ end
+ end
+
+ context 'when declaring augeas class first' do
+
+ on_supported_os.each do |os, facts|
+ context "on #{os}" do
+ let(:facts) do
+ facts.merge({
+ :augeasversion => :undef,
+ })
+ end
+
+ context 'With standard augeas version' do
+
+ let(:pre_condition) do
+ "class { '::augeas': }"
+ end
+
+ context 'when no lens_source is passed' do
+ it 'should error' do
+ expect {
+ is_expected.to contain_file('/usr/share/augeas/lenses/foo.aug')
+ }.to raise_error(Puppet::Error, /Must pass lens_source/)
+ end
+ end
+
+ context 'when lens_source is passed' do
+ let (:params) do
+ {
+ :lens_source => '/tmp/foo.aug',
+ }
+ end
+
+ it { is_expected.to contain_file('/usr/share/augeas/lenses/foo.aug') }
+ it { is_expected.to contain_exec('Typecheck lens foo') }
+ it { is_expected.not_to contain_file('/usr/share/augeas/lenses/tests/test_foo.aug') }
+ it { is_expected.not_to contain_exec('Test lens foo') }
+ end
+
+ context 'when lens_source and test_source are passed' do
+ let (:params) do
+ {
+ :lens_source => '/tmp/foo.aug',
+ :test_source => '/tmp/test_foo.aug',
+ }
+ end
+
+ it { is_expected.to contain_file('/usr/share/augeas/lenses/foo.aug') }
+ it { is_expected.to contain_exec('Typecheck lens foo') }
+ it { is_expected.to contain_file('/usr/share/augeas/lenses/tests/test_foo.aug') }
+ it { is_expected.to contain_exec('Test lens foo') }
+ end
+ end
+
+ context 'when stock_since is passed and augeas is older' do
+ let (:params) do
+ {
+ :lens_source => '/tmp/foo.aug',
+ :stock_since => '1.2.3',
+ }
+ end
+
+ let(:pre_condition) do
+ "class { '::augeas': version => '1.0.0' }"
+ end
+
+ it { is_expected.to contain_file('/usr/share/augeas/lenses/foo.aug') }
+ it { is_expected.to contain_exec('Typecheck lens foo') }
+ end
+
+ context 'when stock_since is passed and augeas is newer' do
+ let (:params) do
+ {
+ :lens_source => '/tmp/foo.aug',
+ :stock_since => '1.2.3',
+ }
+ end
+
+ let(:pre_condition) do
+ "class { '::augeas': version => '1.3.0' }"
+ end
+
+ it do
+ pending "undefined method `negative_failure_message'"
+ is_expected.not_to contain_file('/usr/share/augeas/lenses/foo.aug')
+ end
+ it do
+ pending "undefined method `negative_failure_message'"
+ is_expected.not_to contain_exec('Typecheck lens foo')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/puppet/modules/augeas/spec/spec.opts b/puppet/modules/augeas/spec/spec.opts
new file mode 100644
index 00000000..91cd6427
--- /dev/null
+++ b/puppet/modules/augeas/spec/spec.opts
@@ -0,0 +1,6 @@
+--format
+s
+--colour
+--loadby
+mtime
+--backtrace
diff --git a/puppet/modules/augeas/spec/spec_helper.rb b/puppet/modules/augeas/spec/spec_helper.rb
new file mode 100644
index 00000000..86cc740a
--- /dev/null
+++ b/puppet/modules/augeas/spec/spec_helper.rb
@@ -0,0 +1,42 @@
+require 'puppetlabs_spec_helper/module_spec_helper'
+require 'rspec-puppet-facts'
+include RspecPuppetFacts
+
+
+RSpec.configure do |c|
+ c.include PuppetlabsSpec::Files
+
+ c.before :each do
+ # Store any environment variables away to be restored later
+ @old_env = {}
+ ENV.each_key {|k| @old_env[k] = ENV[k]}
+
+ Puppet.settings[:strict_variables]=true if Gem::Version.new(Puppet::PUPPETVERSION) >= Gem::Version.new('3.5')
+ Puppet.features.stubs(:root?).returns(true)
+ end
+
+ c.after :each do
+ PuppetlabsSpec::Files.cleanup
+ end
+end
+
+require 'pathname'
+dir = Pathname.new(__FILE__).parent
+Puppet[:modulepath] = File.join(dir, 'fixtures', 'modules')
+
+# There's no real need to make this version dependent, but it helps find
+# regressions in Puppet
+#
+# 1. Workaround for issue #16277 where default settings aren't initialised from
+# a spec and so the libdir is never initialised (3.0.x)
+# 2. Workaround for 2.7.20 that now only loads types for the current node
+# environment (#13858) so Puppet[:modulepath] seems to get ignored
+# 3. Workaround for 3.5 where context hasn't been configured yet,
+# ticket https://tickets.puppetlabs.com/browse/MODULES-823
+#
+ver = Gem::Version.new(Puppet.version.split('-').first)
+if Gem::Requirement.new("~> 2.7.20") =~ ver || Gem::Requirement.new("~> 3.0.0") =~ ver || Gem::Requirement.new("~> 3.5") =~ ver
+ puts "augeasproviders: setting Puppet[:libdir] to work around broken type autoloading"
+ # libdir is only a single dir, so it can only workaround loading of one external module
+ Puppet[:libdir] = "#{Puppet[:modulepath]}/augeasproviders_core/lib"
+end
diff --git a/puppet/modules/augeas/spec/unit/puppet/parser/functions/augeas_spec.rb b/puppet/modules/augeas/spec/unit/puppet/parser/functions/augeas_spec.rb
new file mode 100644
index 00000000..b34fa5b5
--- /dev/null
+++ b/puppet/modules/augeas/spec/unit/puppet/parser/functions/augeas_spec.rb
@@ -0,0 +1,83 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe 'the augeas function' do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should fail if the augeas feature is not present" do
+ Puppet.features.expects(:augeas?).returns(false)
+ expect { scope.function_augeas([]) }.to raise_error(Puppet::ParseError, /requires the augeas feature/)
+ end
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("augeas")).to eq("function_augeas")
+ end
+
+ context "when passing wrong arguments" do
+ before :each do
+ Puppet.features.stubs(:augeas?).returns(true)
+ end
+
+ it "should raise a ParseError if there are no arguments" do
+ expect { scope.function_augeas([]) }.to raise_error(Puppet::ParseError, /Wrong number of arguments/)
+ end
+
+ it "should raise a ParseError if content is not a string" do
+ expect { scope.function_augeas([['foo'], 'Fstab.lns', []]) }.to raise_error(Puppet::ParseError, /content must be a string/)
+ end
+
+ it "should raise a ParseError if lens is not a string" do
+ expect { scope.function_augeas(['foo', ['Fstab.lns'], []]) }.to raise_error(Puppet::ParseError, /lens must be a string/)
+ end
+
+ it "should raise a ParseError if changes is not an array" do
+ expect { scope.function_augeas(['foo', 'Fstab.lns', 'changes']) }.to raise_error(Puppet::ParseError, /changes must be an array/)
+ end
+ end
+
+ if Puppet.features.augeas?
+ context "when passing invalid input" do
+ it "should fail to parse input with lens" do
+ expect { scope.function_augeas(['foo', 'Fstab.lns', []]) }.to raise_error(Puppet::ParseError, /Failed to parse string with lens Fstab.lns:/)
+ end
+ end
+
+ context "when passing illegal changes" do
+ it "should fail to apply illegal change" do
+ expect { scope.function_augeas(["\n", 'Fstab.lns', ['foo bar']]) }.to raise_error(Puppet::ParseError, /Failed to apply change to tree/)
+ end
+ end
+
+ context "when generating an invalid tree" do
+ it "should fail to apply changes with wrong tree" do
+ expect { scope.function_augeas(["\n", 'Fstab.lns', ['set ./1/opt 3']]) }.to raise_error(Puppet::ParseError, /Failed to apply changes with lens Fstab.lns:/)
+ end
+ end
+
+ context "when applying valid changes" do
+ it "should remove the 3rd option" do
+ result = scope.function_augeas(["proc /proc proc nodev,noexec,nosuid 0 0\n", 'Fstab.lns', ['rm ./1/opt[3]']])
+ expect(result.class).to eq(String)
+ #result.should == "proc /proc proc nodev,noexec 0 0\n"
+ end
+
+ it "should set a 4th option" do
+ result = scope.function_augeas(["proc /proc proc nodev,noexec,nosuid 0 0\n", 'Fstab.lns', ['ins opt after ./1/opt[last()]', 'set ./1/opt[last()] nofoo']])
+ expect(result.class).to eq(String)
+ #result.should == "proc /proc proc nodev,noexec,nosuid,nofoo 0 0\n"
+ end
+ end
+
+ context "when using old libs" do
+ it "should not work with Augeas prior to 1.0.0" do
+ Augeas.any_instance.expects(:get).with('/augeas/version').returns('0.10.0')
+ expect { scope.function_augeas(["\n", 'Fstab.lns', []]) }.to raise_error(Puppet::ParseError, /requires Augeas 1\.0\.0/)
+ end
+
+ it "should not work with ruby-augeas prior to 0.5.0" do
+ Augeas.any_instance.expects(:methods).returns([])
+ expect { scope.function_augeas(["\n", 'Fstab.lns', []]) }.to raise_error(Puppet::ParseError, /requires ruby-augeas 0\.5\.0/)
+ end
+ end
+ end
+end
diff --git a/puppet/modules/backupninja/LICENSE b/puppet/modules/backupninja/LICENSE
new file mode 100644
index 00000000..94a9ed02
--- /dev/null
+++ b/puppet/modules/backupninja/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/puppet/modules/backupninja/README b/puppet/modules/backupninja/README
new file mode 100644
index 00000000..42a8bfe2
--- /dev/null
+++ b/puppet/modules/backupninja/README
@@ -0,0 +1,202 @@
+Backupninja Module
+-------------------
+
+This module helps you configure all of your backups with puppet, using
+backupninja!
+
+!! UPGRADE NOTICE !!
+
+If you were previously using this module, some pieces have changed,
+and you need to carefully change your use of them, or you will find
+your backups could stop working or get duplicated.
+
+The backupninja::client class has been renamed to backupninja, and is
+now *required* in all node manifests. Make sure the backupninja class
+is now declared in all your node manifests! This new class now defines
+defaults which were previously provided by backupninja::client::defaults,
+and can now be overridden thanks to the brand new technology of class
+parameters. This class also manages the backupninja configuration file,
+replacing the backupninja::config ressource.
+
+The backupninja::server class now takes parameters, replacing several
+global variables such as $backupdir, $backupserver_tag and
+$nagios_server. The $manage_nagios parameter also replaces the
+$use_nagios global.
+
+As for handlers, they don't include the backupninja::client anymore and
+now read several default values from the backupninja base class. The
+$installkey parameter used in several handlers has been renamed to
+$keymanage, to keep in line with the base class parameter.
+
+If you were using the rdiff-backup handler, you need to read the
+following section carefully.
+
+Changes to the rdiff-backup handler
+-----------------------------------
+
+You will need to make sure you change all of your "$directory"
+parameters to be "$home" instead, and on your backupserver you will need
+to move all of your backups into "$home"/rdiff-backup. Previously, they
+were put in "$directory", which doubled as the home for the user that
+was created. This caused problems with rdiff-backup because of dot files
+and other things which were not part of any rdiff-backup.
+
+The rdiff resource name is now used as the subdirectory where rdiff
+backups are sent. This was previously hardcoded to "rdiff-backup", but
+in order to support multiple rdiff backups per host, we now use the
+resource name. So if you were using the following resource:
+
+ backupninja::rdiff { 'main': }
+
+You will want to use the following resource:
+
+ backupninja::rdiff { 'rdiff-backup': }
+ file { '/etc/backup.d/90_main.rdiff': ensure => absent; }
+
+Otherwise your backups may be duplicated!
+
+Changes to nagios integration
+-----------------------------
+
+The default nagios passive service name has changed from "backups" to
+"backups-${name}". If you want it to be automatically created on your
+nagios host, you will need to set $backupninja::manage_nagios to true.
+on the client.
+
+Use the following resource to remove the old "backups" passive service.
+
+ nagios::service { 'backups': ensure => absent }
+
+Getting started
+---------------
+
+This module requires Puppet versions 2.7 and up.
+
+An up-to-date version of the puppet-stdlib module is also required.
+
+Configure your backup server
+----------------------------
+
+Now you will need to configure a backup server by adding the following
+to your node definition for that server:
+
+ include backupninja::server
+
+The default configuration will store backup data in the "/backup"
+directory. To change this you may declare the class with a "backupdir"
+parameter:
+
+ class { 'backupninja::server':
+ backupdir => '/mnt/backupdata'
+ }
+
+By configuring a backupninja::server, this module will automatically
+create sandboxed users on the server for each client for their
+backups.
+
+Configure your backup clients
+-----------------------------
+
+First, you need to include the backupninja class or declare it with
+custom parameters:
+
+ class { 'backupninja':
+ loglvl => 3,
+ usecolors => false,
+ reportsuccess => false,
+ reportwarning => true,
+ ensure_backupninja_version => '1.0.1-1',
+ ensure_rdiffbackup_version => '1.2.8-7'
+ }
+
+In this case, the module will make sure that the backupninja package
+is installed (using puppet's ensure parameter language) and create the
+/etc/backupninja.conf configuration file.
+
+If you need to specify a specific version of either backupninja itself,
+or the specific programs that the handler class installs, you can
+specify the version you need installed by providing a class parameter,
+as shown in the example.
+
+Configuring handlers
+--------------------
+
+Depending on which backup method you want to use on your client, you
+can simply specify some configuration options for that handler that are
+necessary for your client.
+
+Each handler has its own configuration options necessary to make it
+work, each of those are available as puppet parameters. You can see
+the handler documentation, or look at the handler puppet files
+included in this module to see your different options.
+
+Included below are some configuration examples for different handlers.
+
+* An example mysql handler configuration:
+
+backupninja::mysql { 'all_databases':
+ user => root,
+ backupdir => '/var/backups',
+ compress => true,
+ sqldump => true
+}
+
+* An example rdiff-backup handler configuration:
+
+backupninja::rdiff { 'backup_all':
+ directory => '/media/backupdisk',
+ include => ['/var/backups', '/home', '/var/lib/dpkg/status'],
+ exclude => '/home/*/.gnupg'
+}
+
+* A remote rdiff-backup handler:
+
+backupninja::rdiff { 'main':
+ host => 'backup.example.com',
+ type => 'remote',
+ directory => "/backup/${::fqdn}",
+ user => "backup-${::hostname}",
+}
+
+Automatic creation of ssh-keys for duplicity
+--------------------------------------------
+
+backupninja::duplicity can be used to
+
+- create an ssh keypair for a client
+- place the keypair on the puppetmaster in a given location
+- place the keypair in /root/.ssh on the client
+
+i.e.:
+
+ backupninja::duplicity { "duplicity_${::fqdn}":
+ sshoptions => "-oIdentityFile=/root/.ssh/backupninja_${::hostname}_id_rsa",
+ desthost => 'HOST',
+ destdir => "/var/backup/backupninja/${::fqdn}",
+ destuser => "backupninja_${::hostname}",
+ encryptkey => 'KEYID',
+ password => 'PW',
+ backupkeystore => 'puppet:///keys',
+ backupkeystorefspath => '/etc/puppet/modules/keys/files',
+ backupkeydestname => "backupninja_${::hostname}_id_rsa",
+ createkey => true,
+ installkey => true,
+ ...
+ }
+
+
+Nagios alerts about backup freshness
+------------------------------------
+
+If you set the $backupninja::server::nagios_server variable to be the
+name of your nagios server, then a passive nagios service gets setup so
+that the backup server pushes checks, via a cronjob that calls
+/usr/local/bin/checkbackups.pl, to the nagios server to alert about
+relative backup freshness.
+
+To use this feature a few pre-requisites are necessary:
+
+ . configure nsca on your backup server (not done via puppet yet)
+ . configure nsca on your nagios server (not done via puppet yet)
+ . server backup directories are named after their $fqdn
+ . backups must be under $home/dup, $home/rdiff-backup depending on method
diff --git a/puppet/modules/backupninja/files/checkbackups.pl b/puppet/modules/backupninja/files/checkbackups.pl
new file mode 100755
index 00000000..39914469
--- /dev/null
+++ b/puppet/modules/backupninja/files/checkbackups.pl
@@ -0,0 +1,194 @@
+#!/usr/bin/perl -w
+
+# This script is designed to check a backup directory populated with
+# subdirectories named after hosts, within which there are backups of various
+# types.
+#
+# Example:
+# /home/backup:
+# foo.example.com
+#
+# foo.example.com:
+# rdiff-backup .ssh
+#
+# rdiff-backup:
+# root home rdiff-backup-data usr var
+#
+# There are heuristics to determine the backup type. Currently, the following
+# types are supported:
+#
+# rdiff-backup: assumes there is a rdiff-backup/rdiff-backup-data/backup.log file
+# duplicity: assumes there is a dup subdirectory, checks the latest file
+# dump files: assumes there is a dump subdirectory, checks the latest file
+#
+# This script returns output suitable for send_nsca to send the results to
+# nagios and should therefore be used like this:
+#
+# checkbackups.sh | send_nsca -H nagios.example.com
+
+use Getopt::Std;
+
+# XXX: taken from utils.sh from nagios-plugins-basic
+my $STATE_OK=0;
+my $STATE_WARNING=1;
+my $STATE_CRITICAL=2;
+my $STATE_UNKNOWN=3;
+my $STATE_DEPENDENT=4;
+my %ERRORS=(0=>'OK',1=>'WARNING',2=>'CRITICAL',3=>'UNKNOWN',4=>'DEPENDENT');
+
+# gross hack: we look into subdirs to find vservers
+my @vserver_dirs = qw{/var/lib/vservers /vservers};
+
+our $opt_d = "/backup";
+our $opt_c = 48 * 60 * 60;
+our $opt_w = 24 * 60 * 60;
+our $opt_v = 0;
+our $opt_o;
+our $opt_s;
+
+if (!getopts('d:c:w:s:vo')) {
+ print <<EOF
+Usage: $0 [ -d <backupdir> ] [ -c <threshold> ] [ -w <threshold> ] [ -o ] [ -s <host> ] [ -v ]
+EOF
+ ;
+ exit();
+}
+
+sub check_rdiff {
+ my ($host, $dir, $optv) = @_;
+ my $flag="$dir/rdiff-backup-data/backup.log";
+ my $extra_msg = '';
+ my @vservers;
+ if (open(FLAG, $flag)) {
+ while (<FLAG>) {
+ if (/EndTime ([0-9]*).[0-9]* \((.*)\)/) {
+ $last_bak = $1;
+ $extra_msg = ' [backup.log]';
+ $opt_v && print STDERR "found timestamp $1 ($2) in $flag\n";
+ }
+ }
+ if (!$last_bak) {
+ print_status($host, $STATE_UNKNOWN, "cannot parse $flag for a valid timestamp");
+ next;
+ }
+ } else {
+ $opt_v && print STDERR "cannot open $flag\n";
+ }
+ close(FLAG);
+ ($state, $delta) = check_age($last_bak);
+ $dir =~ /([^\/]+)\/?$/;
+ $service = "backups-$1";
+ print_status($host, $state, "$delta hours old$extra_msg", $service);
+ foreach my $vserver_dir (@vserver_dirs) {
+ $vsdir = "$dir/$vserver_dir";
+ if (opendir(DIR, $vsdir)) {
+ @vservers = grep { /^[^\.]/ && -d "$vsdir/$_" } readdir(DIR);
+ $opt_v && print STDERR "found vservers $vsdir: @vservers\n";
+ closedir DIR;
+ } else {
+ $opt_v && print STDERR "no vserver in $vsdir\n";
+ }
+ }
+ my @dom_sufx = split(/\./, $host);
+ my $dom_sufx = join('.', @dom_sufx[1,-1]);
+ foreach my $vserver (@vservers) {
+ print_status("$vserver.$dom_sufx", $state, "$delta hours old$extra_msg, same as parent: $host");
+ }
+}
+
+sub check_age {
+ my ($last_bak) = @_;
+ my $t = time();
+ my $delta = $t - $last_bak;
+ if ($delta > $opt_c) {
+ $state = $STATE_CRITICAL;
+ } elsif ($delta > $opt_w) {
+ $state = $STATE_WARNING;
+ } elsif ($delta >= 0) {
+ $state = $STATE_OK;
+ }
+ $delta = sprintf '%.2f', $delta/3600.0;
+ return ($state, $delta);
+}
+
+sub print_status {
+ my ($host, $state, $message, $service) = @_;
+ my $state_msg = $ERRORS{$state};
+ if (!$service) {
+ $service = 'backups';
+ }
+ $line = "$host\t$service\t$state\t$state_msg $message\n";
+ if ($opt_s) {
+ $opt_v && print STDERR "sending results to nagios...\n";
+ open(NSCA, "|/usr/sbin/send_nsca -H $opt_s") or die("cannot start send_nsca: $!\n");
+ print NSCA $line;
+ close(NSCA) or warn("could not close send_nsca pipe correctly: $!\n");
+ }
+ if (!$opt_s || $opt_v) {
+ printf $line;
+ }
+}
+
+sub check_flag {
+ my ($host, $flag) = @_;
+ my @stats = stat($flag);
+ if (not @stats) {
+ print_status($host, $STATE_UNKNOWN, "cannot stat flag $flag");
+ }
+ else {
+ ($state, $delta) = check_age($stats[9]);
+ print_status($host, $state, "$delta hours old");
+ }
+}
+
+my $backupdir= $opt_d;
+
+my @hosts;
+if (defined($opt_o)) {
+ @hosts=qx{hostname -f};
+} else {
+ # XXX: this should be a complete backup registry instead
+ @hosts=qx{ls $backupdir | grep -v lost+found};
+}
+
+chdir($backupdir);
+my ($delta, $state, $host);
+foreach $host (@hosts) {
+ chomp($host);
+ if ($opt_o) {
+ $dir = $backupdir;
+ } else {
+ $dir = $host;
+ }
+ my $flag;
+ if (-d $dir) {
+ # guess the backup type and find a proper stamp file to compare
+ @rdiffs = glob("$dir/*/rdiff-backup-data");
+ foreach $subdir (@rdiffs) {
+ $subdir =~ s/rdiff-backup-data$//;
+ $opt_v && print STDERR "inspecting dir $subdir\n";
+ check_rdiff($host, $subdir, $opt_v);
+ $flag = 1;
+ }
+ if (-d "$dir/dump") {
+ # XXX: this doesn't check backup consistency
+ $flag="$dir/dump/" . `ls -tr $dir/dump | tail -1`;
+ chomp($flag);
+ check_flag($host, $flag);
+ } elsif (-d "$dir/dup") {
+ # XXX: this doesn't check backup consistency
+ $flag="$dir/dup/" . `ls -tr $dir/dup | tail -1`;
+ chomp($flag);
+ check_flag($host, $flag);
+ } elsif (-r "$dir/rsync.log") {
+ # XXX: this doesn't check backup consistency
+ $flag="$dir/rsync.log";
+ check_flag($host, $flag);
+ }
+ if (!$flag) {
+ print_status($host, $STATE_UNKNOWN, 'unknown system');
+ }
+ } else {
+ print_status($host, $STATE_UNKNOWN, 'no directory');
+ }
+}
diff --git a/puppet/modules/backupninja/files/nagios_plugins/duplicity/README.md b/puppet/modules/backupninja/files/nagios_plugins/duplicity/README.md
new file mode 100644
index 00000000..1cd349af
--- /dev/null
+++ b/puppet/modules/backupninja/files/nagios_plugins/duplicity/README.md
@@ -0,0 +1,24 @@
+duplicity-backup-status
+=======================
+
+Backupninja generates duplicity configfiles, this nagios plugin can check their freshness. Currently only the config files generated by backupninja can be parsed and we depend on that.
+
+## Prerequisites
+
+Make sure you have python-argparse installed (yes an extra dependency, getopt doubles the amount of code, so I gave up on that). The Python script will look for the duplicity_freshness.sh shell script in /usr/local/lib/nagios/plugins/ or /usr/lib/nagios/plugins/ make sure you copy it there and make executable.
+
+## Getting started
+
+Run the python script from your nagios. Don't forget to specify some extras like when warnings or criticalities should be emerged.
+
+- -w WARNINC Number of hours allowed for incremential backup warning level default 28
+- -W WARNFULL Number of hours allowed for incremential backup critical level default 40
+- -c CRITINC Number of days allowed for full backup warning level default 52
+- -C CRITFULL Number of days allowed for full backup critical level default 60
+
+
+## TODO:
+
+- make it cuter, tidy up
+- make it more robust
+- support other config backends as backupninja - this can be done by writing more scripts like backupninja_duplicity_freshness.sh and parsing an extra parameter
diff --git a/puppet/modules/backupninja/files/nagios_plugins/duplicity/backupninja_duplicity_freshness.sh b/puppet/modules/backupninja/files/nagios_plugins/duplicity/backupninja_duplicity_freshness.sh
new file mode 100644
index 00000000..7af2bf7f
--- /dev/null
+++ b/puppet/modules/backupninja/files/nagios_plugins/duplicity/backupninja_duplicity_freshness.sh
@@ -0,0 +1,268 @@
+#!/bin/bash
+# -*- mode: sh; sh-basic-offset: 3; indent-tabs-mode: nil; -*-
+# vim: set filetype=sh sw=3 sts=3 expandtab autoindent:
+
+# Load backupninja library/helpers, because why reinventing the wheel? [Because my wheels weren't round]
+# some duplication is to be expected
+# this is only supposed to work with duplicity
+
+## Functions
+# simple lowercase function
+function tolower() {
+ echo "$1" | tr '[:upper:]' '[:lower:]'
+}
+
+# we grab the current time once, since processing
+# all the configs might take more than an hour.
+nowtime=`LC_ALL=C date +%H`
+nowday=`LC_ALL=C date +%d`
+nowdayofweek=`LC_ALL=C date +%A`
+nowdayofweek=`tolower "$nowdayofweek"`
+
+conffile="/etc/backupninja.conf"
+
+# find $libdirectory
+libdirectory=`grep '^libdirectory' $conffile | /usr/bin/awk '{print $3}'`
+if [ -z "$libdirectory" ]; then
+ if [ -d "/usr/lib/backupninja" ]; then
+ libdirectory="/usr/lib/backupninja"
+ else
+ echo "Could not find entry 'libdirectory' in $conffile."
+ fatal "Could not find entry 'libdirectory' in $conffile."
+ fi
+else
+ if [ ! -d "$libdirectory" ]; then
+ echo "Lib directory $libdirectory not found."
+ fatal "Lib directory $libdirectory not found."
+ fi
+fi
+
+. $libdirectory/tools
+
+setfile $conffile
+
+# get global config options (second param is the default)
+getconf configdirectory /etc/backup.d
+getconf scriptdirectory /usr/share/backupninja
+getconf reportdirectory
+getconf reportemail
+getconf reporthost
+getconf reportspace
+getconf reportsuccess yes
+getconf reportinfo no
+getconf reportuser
+getconf reportwarning yes
+getconf loglevel 3
+getconf when "Everyday at 01:00"
+defaultwhen=$when
+getconf logfile /var/log/backupninja.log
+getconf usecolors "yes"
+getconf SLAPCAT /usr/sbin/slapcat
+getconf LDAPSEARCH /usr/bin/ldapsearch
+getconf RDIFFBACKUP /usr/bin/rdiff-backup
+getconf CSTREAM /usr/bin/cstream
+getconf MYSQLADMIN /usr/bin/mysqladmin
+getconf MYSQL /usr/bin/mysql
+getconf MYSQLHOTCOPY /usr/bin/mysqlhotcopy
+getconf MYSQLDUMP /usr/bin/mysqldump
+getconf PGSQLDUMP /usr/bin/pg_dump
+getconf PGSQLDUMPALL /usr/bin/pg_dumpall
+getconf PGSQLUSER postgres
+getconf GZIP /bin/gzip
+getconf GZIP_OPTS --rsyncable
+getconf RSYNC /usr/bin/rsync
+getconf admingroup root
+
+if [ ! -d "$configdirectory" ]; then
+ echo "Configuration directory '$configdirectory' not found."
+ fatal "Configuration directory '$configdirectory' not found."
+fi
+
+# get the duplicity configuration
+function get_dupconf(){
+ setfile $1
+ getconf options
+ getconf testconnect yes
+ getconf nicelevel 0
+ getconf tmpdir
+
+ setsection gpg
+ getconf password
+ getconf sign no
+ getconf encryptkey
+ getconf signkey
+
+ setsection source
+ getconf include
+ getconf vsnames all
+ getconf vsinclude
+ getconf exclude
+
+ setsection dest
+ getconf incremental yes
+ getconf increments 30
+ getconf keep 60
+ getconf keepincroffulls all
+ getconf desturl
+ getconf awsaccesskeyid
+ getconf awssecretaccesskey
+ getconf cfusername
+ getconf cfapikey
+ getconf cfauthurl
+ getconf ftp_password
+ getconf sshoptions
+ getconf bandwidthlimit 0
+ getconf desthost
+ getconf destdir
+ getconf destuser
+ destdir=${destdir%/}
+}
+
+### some voodoo to mangle the correct commands
+
+function mangle_cli(){
+
+ execstr_options="$options "
+ execstr_source=
+ if [ -n "$desturl" ]; then
+ [ -z "$destuser" ] || warning 'the configured destuser is ignored since desturl is set'
+ [ -z "$desthost" ] || warning 'the configured desthost is ignored since desturl is set'
+ [ -z "$destdir" ] || warning 'the configured destdir is ignored since desturl is set'
+ execstr_serverpart="$desturl"
+ else
+ execstr_serverpart="scp://$destuser@$desthost/$destdir"
+ fi
+
+
+ ### Symmetric or asymmetric (public/private key pair) encryption
+ if [ -n "$encryptkey" ]; then
+ execstr_options="${execstr_options} --encrypt-key $encryptkey"
+ fi
+
+ ### Data signing (or not)
+ if [ "$sign" == yes ]; then
+ # duplicity is not able to sign data when using symmetric encryption
+ [ -n "$encryptkey" ] || fatal "The encryptkey option must be set when signing."
+ # if needed, initialize signkey to a value that is not empty (checked above)
+ [ -n "$signkey" ] || signkey="$encryptkey"
+ execstr_options="${execstr_options} --sign-key $signkey"
+ fi
+
+ ### Temporary directory
+ precmd=
+ if [ -n "$tmpdir" ]; then
+ if [ ! -d "$tmpdir" ]; then
+ #info "Temporary directory ($tmpdir) does not exist, creating it."
+ mkdir -p "$tmpdir"
+ [ $? -eq 0 ] || fatal "Could not create temporary directory ($tmpdir)."
+ chmod 0700 "$tmpdir"
+ fi
+ #info "Using $tmpdir as TMPDIR"
+ precmd="${precmd}TMPDIR=$tmpdir "
+ fi
+
+ ### Source
+
+ set -o noglob
+
+ # excludes
+ SAVEIFS=$IFS
+ IFS=$(echo -en "\n\b")
+ for i in $exclude; do
+ str="${i//__star__/*}"
+ execstr_source="${execstr_source} --exclude '$str'"
+ done
+ IFS=$SAVEIFS
+
+ # includes
+ SAVEIFS=$IFS
+ IFS=$(echo -en "\n\b")
+ for i in $include; do
+ [ "$i" != "/" ] || fatal "Sorry, you cannot use 'include = /'"
+ str="${i//__star__/*}"
+ execstr_source="${execstr_source} --include '$str'"
+ done
+ IFS=$SAVEIFS
+
+ set +o noglob
+
+ execstr_options="${execstr_options} --ssh-options '$sshoptions'"
+ if [ "$bandwidthlimit" != 0 ]; then
+ [ -z "$desturl" ] || warning 'The bandwidthlimit option is not used when desturl is set.'
+ execstr_precmd="trickle -s -d $bandwidthlimit -u $bandwidthlimit"
+ fi
+}
+
+#function findlastdates(){
+# outputfile=$1
+# lastfull=0
+# lastinc=0
+# backuptime=0
+#
+# while read line; do
+# atime=0
+# arr=()
+# sort=''
+# test=$(echo $line|awk '{if (NF == 7); if ($1 == "Full" || $1 == "Incremental") {print $4, $3, $6, $5}}' )
+#
+# if [ -n "$test" ]; then
+# backuptime=$(date -u -d "$test" +%s)
+#
+# arr=($(echo $line|awk '{print $1, $2, $3, $4, $5, $6}'))
+# if [ ${arr[0]} == "Incremental" ] && [ "$lastinc" -lt "$backuptime" ] ; then
+# lastinc=$backuptime
+# elif [ ${arr[0]} == "Full" ] && [ "$lastfull" -lt "$backuptime" ] ; then
+# lastfull=$backuptime
+# fi
+#
+# fi
+#
+# done < $outputfile
+# # a full backup can be seen as incremental too
+# lastinc=$(echo $lastinc | awk 'max=="" || $1 > max {max=$1} END{ print max}')
+#}
+
+function check_status() {
+ grep -q 'No orphaned or incomplete backup sets found.' $1
+ if [ $? -ne 0 ] ; then
+ exit 2
+ fi
+}
+
+##
+## this function handles the freshness check of a backup action
+##
+
+function process_action() {
+ local file="$1"
+ local suffix="$2"
+ setfile $file
+ get_dupconf $1
+ mangle_cli
+
+ outputfile=`maketemp backupout`
+ export PASSPHRASE=$password
+ export FTP_PASSWORD=$ftp_password
+ output=` su -c \
+ "$execstr_precmd duplicity $execstr_options collection-status $execstr_serverpart >$outputfile 2>&1"`
+ exit_code=$?
+ echo -n $outputfile
+
+ #check_status
+ #findlastdates
+}
+
+files=`find $configdirectory -follow -mindepth 1 -maxdepth 1 -type f ! -name '.*.swp' | sort -n`
+
+for file in $files; do
+ [ -f "$file" ] || continue
+ suffix="${file##*.}"
+ base=`basename $file`
+ if [ "${base:0:1}" == "0" -o "$suffix" == "disabled" ]; then
+ continue
+ fi
+ if [ -e "$scriptdirectory/$suffix" -a "$suffix" == "dup" ]; then
+ process_action $file $suffix
+ fi
+done
+
diff --git a/puppet/modules/backupninja/files/nagios_plugins/duplicity/check_backupninja_duplicity.py b/puppet/modules/backupninja/files/nagios_plugins/duplicity/check_backupninja_duplicity.py
new file mode 100644
index 00000000..8ed9ce68
--- /dev/null
+++ b/puppet/modules/backupninja/files/nagios_plugins/duplicity/check_backupninja_duplicity.py
@@ -0,0 +1,123 @@
+#!/usr/bin/env python
+
+# Inspired by Arne Schwabe <arne-nagios@rfc2549.org> [with BSD license]
+# Inspired by backupninja [that's gpl some version]
+# minor changes by someon who doesn't understand all the license quirks
+
+from subprocess import Popen,PIPE
+import sys
+import time
+import os
+import argparse
+import getopt
+
+def main():
+ # getopt = much more writing
+ parser = argparse.ArgumentParser(description='Nagios Duplicity status checker')
+
+ parser.add_argument("-w", dest="warninc", default=28, type=int,
+ help="Number of hours allowed for incremential backup warning level, default 28")
+ parser.add_argument("-W", dest="warnfull", default=31, type=int,
+ help="Number of days allowed for full backup warning level, default 31")
+ parser.add_argument("-c", dest="critinc", default=52, type=int,
+ help="Number of hours allowed for incremential backup critical level, default 52")
+ parser.add_argument("-C", dest="critfull", default=33, type=int,
+ help="Number of days allowed for full backup critical level, default 33")
+ args = parser.parse_args()
+
+ okay = 0
+
+ # *sigh* check_output is from python 2.7 and onwards. Debian, upgrade yourself.
+ #output , err = check_output(['/root/freshness.sh'])
+
+ if os.path.isfile("/usr/lib/nagios/plugins/backupninja_duplicity_freshness.sh") and os.access("/usr/lib/nagios/plugins/backupninja_duplicity_freshness.sh", os.X_OK):
+ checkstatus, err = Popen(['/bin/bash', '/usr/lib/nagios/plugins/backupninja_duplicity_freshness.sh'], stdout=PIPE, stderr=PIPE, env={'HOME': '/root', 'PATH': os.environ['PATH']}).communicate()
+ elif os.path.isfile("/usr/local/lib/nagios/plugins/backupninja_duplicity_freshness.sh") and os.access("/usr/local/lib/nagios/plugins/backupninja_duplicity_freshness.sh", os.X_OK):
+ checkstatus, err = Popen(['/bin/bash', '/usr/local/lib/nagios/plugins/backupninja_duplicity_freshness.sh'], stdout=PIPE, stderr=PIPE, env={'HOME': '/root', 'PATH': os.environ['PATH']}).communicate()
+
+ # Don't use exec(), popen(), etc. to execute external commands without explicity using the full path of the external program. Hijacked search path could be problematic.
+ #checkstatus, err = Popen(['/bin/bash', './freshness.sh'], stdout=PIPE, stderr=PIPE, env={'HOME': '/root', 'PATH': os.environ['PATH']}).communicate()
+
+ #another sigh: Debian testing, upgrade yourself, this is only needed because Debian testing uses duplicity 0.6.18-3
+ # open file read/write
+ f = open (checkstatus,"r")
+ checklines = f.readlines()
+ f.close()
+
+ # remove the line that says Import of duplicity.backends.giobackend Failed: No module named gio
+ f = open(checkstatus,"w")
+ for line in checklines:
+ if not 'Import of duplicity.backends.giobackend Failed: No module named gio' in line:
+ f.write(line)
+ f.close()
+
+ output = open(checkstatus).read()
+
+ lastfull, lastinc = findlastdates(output)
+
+ sincelastfull = time.time() - lastfull
+ sincelastinc = time.time() - lastinc
+
+ msg = "OK: "
+
+ if sincelastfull > (args.warnfull * 24 * 3600) or sincelastinc > (args.warninc * 3600):
+ okay = 1
+ msg = "WARNING: "
+ if sincelastfull > (args.critfull * 24 * 3600) or sincelastinc > (args.critinc * 3600):
+ okay = 2
+ msg = "CRITICAL: "
+ if not checkoutput(output):
+ okay = max(okay,1)
+ msg = "WARNING: duplicity output: %s " % repr(output)
+ if err:
+ okay=2
+ msg = "Unexpected output: %s, " % repr(err)
+
+ print msg, "last full %s ago, last incremential %s ago|lastfull=%d, lastinc=%d" % ( formattime(sincelastfull), formattime(sincelastinc), sincelastfull, sincelastinc)
+
+ #clean up cruft
+ os.remove(checkstatus)
+ sys.exit(okay)
+
+def checkoutput(output):
+ if not 'No orphaned or incomplete backup sets found.' in output:
+ return False
+
+ return True
+
+def formattime(seconds):
+ days = seconds / (3600 * 24)
+ hours = seconds / 3600 % 24
+
+ if days:
+ return "%d days %d hours" % (days,hours)
+ else:
+ return "%d hours" % hours
+
+
+def findlastdates(output):
+ lastfull = 0
+ lastinc = 0
+
+ for line in output.split("\n"):
+ parts = line.split()
+
+ # ['Incremental', 'Sun', 'Oct', '31', '03:00:04', '2010', '1']
+ if len (parts) == 7 and parts[0] in ["Full","Incremental"]:
+ foo = time.strptime(" ".join(parts[1:6]),"%a %b %d %H:%M:%S %Y")
+
+ backuptime = time.mktime(foo)
+
+ if parts[0] == "Incremental" and lastinc < backuptime:
+ lastinc = backuptime
+ elif parts[0] == "Full" and lastfull < backuptime:
+ lastfull = backuptime
+
+
+ # Count a full backup as incremental backup
+ lastinc = max(lastfull,lastinc)
+ return (lastfull, lastinc)
+
+
+if __name__=='__main__':
+ main()
diff --git a/puppet/modules/backupninja/manifests/cron.pp b/puppet/modules/backupninja/manifests/cron.pp
new file mode 100644
index 00000000..bd4e857c
--- /dev/null
+++ b/puppet/modules/backupninja/manifests/cron.pp
@@ -0,0 +1,17 @@
+# Write the backupninja cron job, allowing you to specify an alternate backupninja
+# command (if you want to wrap it in any other commands, e.g. to allow it to use
+# the monkeysphere for authentication), or a different schedule to run it on.
+define backupninja::cron(
+ $backupninja_cmd = '/usr/sbin/backupninja',
+ $backupninja_test_cmd = $backupninja_cmd,
+ $cronfile = "/etc/cron.d/backupninja",
+ $min = "0", $hour = "*", $dom = "*", $month = "*",
+ $dow = "*")
+{
+ file { $cronfile:
+ content => template('backupninja/backupninja.cron.erb'),
+ owner => root,
+ group => root,
+ mode => 0644
+ }
+}
diff --git a/puppet/modules/backupninja/manifests/duplicity.pp b/puppet/modules/backupninja/manifests/duplicity.pp
new file mode 100644
index 00000000..a05da876
--- /dev/null
+++ b/puppet/modules/backupninja/manifests/duplicity.pp
@@ -0,0 +1,147 @@
+# Run duplicity-backup as part of a backupninja run.
+#
+# Valid attributes for this type are:
+#
+# order:
+#
+# The prefix to give to the handler config filename, to set order in
+# which the actions are executed during the backup run.
+#
+# ensure:
+#
+# Allows you to delete an entry if you don't want it any more (but be
+# sure to keep the configdir, name, and order the same, so that we can
+# find the correct file to remove).
+#
+# options, nicelevel, testconnect, tmpdir, sign, encryptkey, signkey,
+# password, include, exclude, vsinclude, incremental, keep, bandwidthlimit,
+# sshoptions, destdir, desthost, desuser:
+#
+# As defined in the backupninja documentation. The options will be
+# placed in the correct sections automatically. The include and
+# exclude options should be given as arrays if you want to specify
+# multiple directories.
+#
+# directory, ssh_dir_manage, ssh_dir, authorized_keys_file, installuser,
+# installkey, backuptag:
+#
+# Options for the bakupninja::server::sandbox define, check that
+# definition for more info.
+#
+# Some notes about this handler:
+#
+# - When specifying a password, be sure to enclose it in single quotes,
+# this is particularly important if you have any special characters, such
+# as a $ which puppet will attempt to interpret resulting in a different
+# password placed in the file than you expect!
+# - There's no support for a 'local' type in backupninja's duplicity
+# handler on version 0.9.6-4, which is the version available in stable and
+# testing debian repositories by the time of this writing.
+define backupninja::duplicity( $order = 90,
+ $ensure = present,
+ # options to the config file
+ $options = false,
+ $nicelevel = false,
+ $testconnect = false,
+ $tmpdir = false,
+ # [gpg]
+ $sign = false,
+ $encryptkey = false,
+ $signkey = false,
+ $password = false,
+ # [source]
+ $include = [ "/var/spool/cron/crontabs",
+ "/var/backups",
+ "/etc",
+ "/root",
+ "/home",
+ "/usr/local/*bin",
+ "/var/lib/dpkg/status*" ],
+ $exclude = [ "/home/*/.gnupg",
+ "/home/*/.local/share/Trash",
+ "/home/*/.Trash",
+ "/home/*/.thumbnails",
+ "/home/*/.beagle",
+ "/home/*/.aMule",
+ "/home/*/.gnupg",
+ "/home/*/.gpg",
+ "/home/*/.ssh",
+ "/home/*/gtk-gnutella-downloads",
+ "/etc/ssh/*" ],
+ $vsinclude = false,
+ # [dest]
+ $incremental = "yes",
+ $increments = false,
+ $keep = false,
+ $keepincroffulls = false,
+ $bandwidthlimit = false,
+ $sshoptions = false,
+ $destdir = false,
+ $desthost = false,
+ $destuser = false,
+ $desturl = false,
+ # configs to backupninja client
+ $backupkeystore = $backupninja::keystore,
+ $backupkeystorefspath = $backupninja::keystorefspath,
+ $backupkeytype = $backupninja::keytype,
+ $backupkeydest = $backupninja::keydest,
+ $backupkeydestname = $backupninja::keydestname,
+ # options to backupninja server sandbox
+ $ssh_dir_manage = true,
+ $ssh_dir = "${destdir}/.ssh",
+ $authorized_keys_file = 'authorized_keys',
+ $installuser = true,
+ $backuptag = "backupninja-${::fqdn}",
+ # key options
+ $createkey = false,
+ $keymanage = $backupninja::keymanage ) {
+
+ # install client dependencies
+ ensure_resource('package', 'duplicity', {'ensure' => $backupninja::ensure_duplicity_version})
+
+ case $desthost { false: { err("need to define a destination host for remote backups!") } }
+ case $destdir { false: { err("need to define a destination directory for remote backups!") } }
+ case $password { false: { err("a password is necessary either to unlock the GPG key, or for symmetric encryption!") } }
+
+ # guarantees there's a configured backup space for this backup
+ backupninja::server::sandbox { "${user}-${name}":
+ user => $destuser,
+ host => $desthost,
+ dir => $destdir,
+ manage_ssh_dir => $ssh_dir_manage,
+ ssh_dir => $ssh_dir,
+ authorized_keys_file => $authorized_keys_file,
+ installuser => $installuser,
+ backuptag => $backuptag,
+ backupkeys => $backupkeystore,
+ keytype => $backupkeytype,
+ }
+
+ # the client's ssh key
+ backupninja::key { "${destuser}-${name}":
+ user => $destuser,
+ createkey => $createkey,
+ keymanage => $keymanage,
+ keytype => $backupkeytype,
+ keystore => $backupkeystore,
+ keystorefspath => $backupkeystorefspath,
+ keydest => $backupkeydest,
+ keydestname => $backupkeydestname
+ }
+
+ # the backupninja rule for this duplicity backup
+ file { "${backupninja::configdir}/${order}_${name}.dup":
+ ensure => $ensure,
+ content => template('backupninja/dup.conf.erb'),
+ owner => root,
+ group => root,
+ mode => 0600,
+ require => File["${backupninja::configdir}"]
+ }
+
+ if $backupninja::manage_nagios {
+ nagios::service::passive { $nagios_description: }
+ }
+
+}
+
diff --git a/puppet/modules/backupninja/manifests/generate_sshkey.pp b/puppet/modules/backupninja/manifests/generate_sshkey.pp
new file mode 100644
index 00000000..a3008e50
--- /dev/null
+++ b/puppet/modules/backupninja/manifests/generate_sshkey.pp
@@ -0,0 +1,33 @@
+define backupninja::generate_sshkey(
+ $ssh_key_basepath = '/etc/puppet/modules/keys/files/backupkeys',
+){
+
+ # generate backupninja ssh keypair
+ $ssh_key_name = "backup_${::hostname}_id_rsa"
+ $ssh_keys = ssh_keygen("${ssh_key_basepath}/${ssh_key_name}")
+ $public = split($ssh_keys[1],' ')
+ $public_type = $public[0]
+ $public_key = $public[1]
+
+ file { '/root/.ssh':
+ ensure => directory,
+ owner => 'root',
+ group => 'root',
+ mode => '0600';
+ }
+
+ # install ssh keypair on client
+ file { "/root/.ssh/$ssh_key_name":
+ content => $ssh_keys[0],
+ owner => root,
+ group => 0,
+ mode => '0600';
+ }
+
+ file { "/root/.ssh/$ssh_key_name.pub":
+ content => $public_key,
+ owner => root,
+ group => 0,
+ mode => '0666';
+ }
+}
diff --git a/puppet/modules/backupninja/manifests/init.pp b/puppet/modules/backupninja/manifests/init.pp
new file mode 100644
index 00000000..e453e703
--- /dev/null
+++ b/puppet/modules/backupninja/manifests/init.pp
@@ -0,0 +1,52 @@
+# configure backupninja
+class backupninja (
+ $ensure_backupninja_version = 'installed',
+ $ensure_rsync_version = 'installed',
+ $ensure_rdiffbackup_version = 'installed',
+ $ensure_debconfutils_version = 'installed',
+ $ensure_hwinfo_version = 'installed',
+ $ensure_duplicity_version = 'installed',
+ $configdir = '/etc/backup.d',
+ $keystore = "${::fileserver}/keys/backupkeys",
+ $keystorefspath = false,
+ $keytype = 'rsa',
+ $keydest = '/root/.ssh',
+ $keyowner = 0,
+ $keygroup = 0,
+ $keymanage = true,
+ $configfile = '/etc/backupninja.conf',
+ $loglvl = 4,
+ $when = 'everyday at 01:00',
+ $reportemail = 'root',
+ $reportsuccess = false,
+ $reportwarning = true,
+ $reporthost = undef,
+ $reportuser = undef,
+ $reportdirectory = undef,
+ $logfile = '/var/log/backupninja.log',
+ $scriptdir = '/usr/share/backupninja',
+ $libdir = '/usr/lib/backupninja',
+ $usecolors = true,
+ $vservers = false,
+ $manage_nagios = false,
+) {
+
+ # install client dependencies
+ ensure_resource('package', 'backupninja', {'ensure' => $ensure_backupninja_version})
+
+ # set up backupninja config directory
+ file { $configdir:
+ ensure => directory,
+ mode => '0750',
+ owner => 0,
+ group => 0;
+ }
+
+ file { $configfile:
+ content => template('backupninja/backupninja.conf.erb'),
+ owner => root,
+ group => 0,
+ mode => '0644'
+ }
+
+}
diff --git a/puppet/modules/backupninja/manifests/key.pp b/puppet/modules/backupninja/manifests/key.pp
new file mode 100644
index 00000000..9d34cdbd
--- /dev/null
+++ b/puppet/modules/backupninja/manifests/key.pp
@@ -0,0 +1,41 @@
+# generate and deploy backupninja sshkeys
+define backupninja::key(
+ $user = $name,
+ $createkey = false,
+ $keymanage = $backupninja::keymanage,
+ $keyowner = $backupninja::keyowner,
+ $keygroup = $backupninja::keygroup,
+ $keystore= $backupninja::keystore,
+ $keystorefspath = $backupninja::keystorefspath,
+ $keytype = $backupninja::keytype,
+ $keydest = $backupninja::keydest,
+ $keydestname = "id_${backupninja::keytype}" )
+{
+
+ # generate the key
+ if $createkey == true {
+ if $keystorefspath == false {
+ err('need to define a destination directory for sshkey creation!')
+ }
+ $ssh_keys = ssh_keygen("${keystorefspath}/${keydestname}")
+ }
+
+ # deploy/manage the key
+ if $keymanage == true {
+ $keydestfile = "${keydest}/${keydestname}"
+ ensure_resource('file', $keydest, {
+ 'ensure' => 'directory',
+ 'mode' => '0700',
+ 'owner' => $keyowner,
+ 'group' => $keygroup
+ })
+ ensure_resource('file', $keydestfile, {
+ 'ensure' => 'present',
+ 'source' => "${keystore}/${user}_id_${keytype}",
+ 'mode' => '0700',
+ 'owner' => $keyowner,
+ 'group' => $keygroup,
+ 'require' => File[$keydest],
+ })
+ }
+}
diff --git a/puppet/modules/backupninja/manifests/labelmount.pp b/puppet/modules/backupninja/manifests/labelmount.pp
new file mode 100644
index 00000000..8974cec1
--- /dev/null
+++ b/puppet/modules/backupninja/manifests/labelmount.pp
@@ -0,0 +1,62 @@
+# Mount a labelled partition on a directory as part of a backupninja run.
+#
+# This type will automatically create an unmount action with an order of 99
+# for the destination directory you specify here.
+#
+# Valid attributes for this type are:
+#
+# order: The prefix to give to the handler config filename, to set
+# order in which the actions are executed during the backup run. Note
+# that the value given here should be less than any action which
+# requires the filesystem to be mounted!
+#
+# ensure: Allows you to delete an entry if you don't want it any more
+# (but be sure to keep the configdir, name, and order the same, so
+# that we can find the correct file to remove).
+#
+# label: The partition label to mount.
+#
+# dest: The directory to mount the partition onto.
+#
+define backupninja::labelmount($order = 10,
+ $ensure = present,
+ $label,
+ $dest
+ ) {
+ file { "${backupninja::configdir}/${order}_${name}.labelmount":
+ ensure => $ensure,
+ content => template('backupninja/labelmount.conf.erb'),
+ owner => root,
+ group => root,
+ mode => 0600,
+ require => File["${backupninja::configdir}"]
+ }
+
+ file { "${backupninja::configdir}/99_${name}.umount":
+ ensure => $ensure,
+ content => template('backupninja/umount.conf.erb'),
+ owner => root,
+ group => root,
+ mode => 0600,
+ require => File["${backupninja::configdir}"]
+ }
+
+ # Copy over the handler scripts themselves, since they're not in the
+ # standard distribution, and are unlikely to end up there any time
+ # soon because backupninja's "build" system is balls.
+ file { "/usr/share/backupninja/labelmount":
+ content => template('backupninja/labelmount.handler'),
+ owner => root,
+ group => root,
+ mode => 0755,
+ require => Package[backupninja]
+ }
+
+ file { "/usr/share/backupninja/umount":
+ content => template('backupninja/umount.handler'),
+ owner => root,
+ group => root,
+ mode => 0755,
+ require => Package[backupninja]
+ }
+}
diff --git a/puppet/modules/backupninja/manifests/maildir.pp b/puppet/modules/backupninja/manifests/maildir.pp
new file mode 100644
index 00000000..2454b82d
--- /dev/null
+++ b/puppet/modules/backupninja/manifests/maildir.pp
@@ -0,0 +1,43 @@
+# maildir handler, as part of a backupninja run.
+#
+# The maildir handler slowly creates a backup of each user's
+# maildir to a remote server. It is designed to be run with
+# low overhead in terms of CPU and bandwidth, so it runs pretty
+# slow. Hardlinking is used to save storage space. The actual
+# maildir is stored within each snapshot directory.
+#
+# Valid attributes for this type are:
+#
+# order: The prefix to give to the handler config filename, to set
+# order in which the actions are executed during the backup run.
+#
+# ensure: Allows you to delete an entry if you don't want it any more
+# (but be sure to keep the configdir, name, and order the same, so
+# that we can find the correct file to remove).
+#
+#
+define backupninja::maildir(
+ $order = 99, $ensure = present,
+ $when = 'everyday at 21:00', $srcdir = false,
+ $destdir = false, $desthost = false, $destuser = false, $destid_file = false,
+ $remove = false, $multiconnection = yes, $keepdaily='4', $keepweekly='2',
+ $keepmonthly='2')
+{
+ # install client dependencies
+ ensure_resource('package', 'rsync', {'ensure' => $backupninja::ensure_rsync_version})
+
+ case $srcdir { false: { err("need to define a source directory to backup!") } }
+ case $destdir { false: { err("need to define a destination directory to backup!") } }
+ case $desthost { false: { err("need to define a destination host for backups!") } }
+ case $destuser { false: { err("need to define a destination user for backups!") } }
+ case $destid_file { false: { err("need to define a ssh key id file to use!") } }
+
+ file { "${backupninja::configdir}/${order}_${name}.maildir":
+ ensure => $ensure,
+ content => template('backupninja/maildir.conf.erb'),
+ owner => root,
+ group => root,
+ mode => 0600,
+ require => File["${backupninja::configdir}"]
+ }
+}
diff --git a/puppet/modules/backupninja/manifests/mysql.pp b/puppet/modules/backupninja/manifests/mysql.pp
new file mode 100644
index 00000000..b8877c05
--- /dev/null
+++ b/puppet/modules/backupninja/manifests/mysql.pp
@@ -0,0 +1,38 @@
+# Safe MySQL dumps, as part of a backupninja run.
+#
+# Valid attributes for this type are:
+#
+# order: The prefix to give to the handler config filename, to set
+# order in which the actions are executed during the backup run.
+#
+# ensure: Allows you to delete an entry if you don't want it any more
+# (but be sure to keep the configdir, name, and order the same, so
+# that we can find the correct file to remove).
+#
+# user, dbusername, dbpassword, dbhost, databases, backupdir,
+# hotcopy, sqldump, compress, configfile: As defined in the
+# backupninja documentation, with the caveat that hotcopy, sqldump,
+# and compress take true/false rather than yes/no.
+#
+define backupninja::mysql(
+ $order = 10, $ensure = present, $user = false, $dbusername = false, $dbpassword = false,
+ $dbhost = 'localhost', $databases = 'all', $backupdir = false, $hotcopy = false,
+ $sqldump = false, $compress = false, $configfile = true,
+ $vsname = false, $sqldumpoptions = '--lock-tables --complete-insert --add-drop-table --quick --quote-names',
+ $nodata = false)
+{
+
+ $real_configfile = $configfile ? {
+ true => "/etc/mysql/debian.cnf",
+ default => $configfile,
+ }
+
+ file { "${backupninja::configdir}/${order}_${name}.mysql":
+ ensure => $ensure,
+ content => template('backupninja/mysql.conf.erb'),
+ owner => root,
+ group => root,
+ mode => 0600,
+ require => File["${backupninja::configdir}"]
+ }
+}
diff --git a/puppet/modules/backupninja/manifests/nagios_plugin/duplicity.pp b/puppet/modules/backupninja/manifests/nagios_plugin/duplicity.pp
new file mode 100644
index 00000000..7dbd2633
--- /dev/null
+++ b/puppet/modules/backupninja/manifests/nagios_plugin/duplicity.pp
@@ -0,0 +1,45 @@
+class backupninja::nagios_plugin::duplicity {
+ case $::operatingsystem {
+ 'Debian': { package { 'python-argparse': ensure => installed, } }
+ 'Ubuntu': { package { 'python-argh': ensure => installed, } }
+ default: {
+ notify {'Backupninja-Duplicity Nagios check needs python-argparse to be installed !':} }
+ }
+
+ file { '/usr/lib/nagios/plugins/check_backupninja_duplicity.py':
+ source => 'puppet:///modules/backupninja/nagios_plugins/duplicity/check_backupninja_duplicity.py',
+ mode => '0755',
+ owner => 'nagios',
+ group => 'nagios',
+ }
+
+ # deploy helper script
+ file { '/usr/lib/nagios/plugins/backupninja_duplicity_freshness.sh':
+ source => 'puppet:///modules/backupninja/nagios_plugins/duplicity/backupninja_duplicity_freshness.sh',
+ mode => '0755',
+ owner => 'nagios',
+ group => 'nagios',
+ }
+
+ nagios::nrpe::command { 'check_backupninja_duplicity':
+ command_line => "sudo ${::nagios::nrpe::nagios_plugin_dir}/check_backupninja_duplicity.py"
+ }
+ sudo::spec {'nrpe_check_backupninja_duplicity':
+ ensure => present,
+ users => 'nagios',
+ hosts => 'ALL',
+ commands => "NOPASSWD: ${::nagios::nrpe::nagios_plugin_dir}/check_backupninja_duplicity.py";
+ }
+
+ nagios::service { "Backupninja Duplicity $::fqdn":
+ use_nrpe => true,
+ check_command => 'check_backupninja_duplicity',
+ nrpe_timeout => '60',
+ # check only twice a day
+ normal_check_interval => '720',
+ # recheck every hour
+ retry_check_interval => '60',
+ }
+
+
+}
diff --git a/puppet/modules/backupninja/manifests/pgsql.pp b/puppet/modules/backupninja/manifests/pgsql.pp
new file mode 100644
index 00000000..d4814be9
--- /dev/null
+++ b/puppet/modules/backupninja/manifests/pgsql.pp
@@ -0,0 +1,27 @@
+# Safe PGSQL dumps, as part of a backupninja run.
+#
+# Valid attributes for this type are:
+#
+# order: The prefix to give to the handler config filename, to set
+# order in which the actions are executed during the backup run.
+#
+# ensure: Allows you to delete an entry if you don't want it any more
+# (but be sure to keep the configdir, name, and order the same, so
+# that we can find the correct file to remove).
+#
+# backupdir, compress, configfile: As defined in the
+# backupninja documentation, with the caveat that hotcopy, sqldump,
+# and compress take true/false rather than yes/no.
+#
+define backupninja::pgsql(
+ $order = 10, $ensure = present, $databases = 'all', $backupdir = "/var/backups/postgres", $compress = true, $vsname = false)
+{
+ file { "${backupninja::configdir}/${order}_${name}.pgsql":
+ ensure => $ensure,
+ content => template('backupninja/pgsql.conf.erb'),
+ owner => root,
+ group => root,
+ mode => 0600,
+ require => File["${backupninja::configdir}"]
+ }
+}
diff --git a/puppet/modules/backupninja/manifests/rdiff.pp b/puppet/modules/backupninja/manifests/rdiff.pp
new file mode 100644
index 00000000..cd73d22c
--- /dev/null
+++ b/puppet/modules/backupninja/manifests/rdiff.pp
@@ -0,0 +1,109 @@
+# Run rdiff-backup as part of a backupninja run.
+#
+# Valid attributes for this type are:
+#
+# order: The prefix to give to the handler config filename, to set
+# order in which the actions are executed during the backup run.
+#
+# ensure: Allows you to delete an entry if you don't want it any more
+# (but be sure to keep the configdir, name, and order the same, so
+# that we can find the correct file to remove).
+#
+# keep, include, exclude, type, host, directory, user, sshoptions: As
+# defined in the backupninja documentation. The options will be placed
+# in the correct sections automatically. The include and exclude
+# options should be given as arrays if you want to specify multiple
+# directories.
+#
+define backupninja::rdiff( $order = 90,
+ $ensure = present,
+ # [general]
+ $options = '--force',
+ $extras = false,
+ # [source]
+ $include = [ "/var/spool/cron/crontabs",
+ "/var/backups",
+ "/etc",
+ "/root",
+ "/home",
+ "/usr/local/*bin",
+ "/var/lib/dpkg/status*"
+ ],
+ $exclude = [ "/home/*/.gnupg",
+ "/home/*/.local/share/Trash",
+ "/home/*/.Trash",
+ "/home/*/.thumbnails",
+ "/home/*/.beagle",
+ "/home/*/.aMule",
+ "/home/*/gtk-gnutella-downloads"
+ ],
+ $vsinclude = false,
+ # [dest]
+ $type = 'local',
+ $host = false,
+ $user = false,
+ $home = "/home/${user}-${name}",
+ $keep = 30,
+ $sshoptions = false,
+ # ssh keypair config
+ $key = false,
+ $keymanage = $backupninja::keymanage,
+ $backupkeystore = $backupninja::keystore,
+ $backupkeytype = $backupninja::keytype,
+ $ssh_dir_manage = true,
+ $ssh_dir = "${home}/.ssh",
+ $authorized_keys_file = 'authorized_keys',
+ # sandbox config
+ $installuser = true,
+ $backuptag = "backupninja-${::fqdn}",
+ # monitoring
+ $nagios_description = "backups-${name}" ) {
+
+ # install client dependencies
+ ensure_resource('package', 'rdiff-backup', {'ensure' => $backupninja::ensure_rdiffbackup_version})
+
+ $directory = "$home/$name/"
+
+ case $type {
+ 'remote': {
+ case $host { false: { err("need to define a host for remote backups!") } }
+
+ backupninja::server::sandbox { "${user}-${name}":
+ user => $user,
+ host => $host,
+ dir => $home,
+ manage_ssh_dir => $ssh_dir_manage,
+ ssh_dir => $ssh_dir,
+ key => $key,
+ authorized_keys_file => $authorized_keys_file,
+ installuser => $installuser,
+ backuptag => $backuptag,
+ backupkeys => $backupkeystore,
+ keytype => $backupkeytype,
+ }
+
+ backupninja::key { "${user}-${name}":
+ user => $user,
+ keymanage => $keymanage,
+ keytype => $backupkeytype,
+ keystore => $backupkeystore,
+ }
+ }
+ }
+
+
+ file { "${backupninja::configdir}/${order}_${name}.rdiff":
+ ensure => $ensure,
+ content => template('backupninja/rdiff.conf.erb'),
+ owner => root,
+ group => root,
+ mode => 0600,
+ require => File["${backupninja::configdir}"]
+ }
+
+ if $backupninja::manage_nagios {
+ nagios::service::passive { $nagios_description: }
+ }
+
+}
+
diff --git a/puppet/modules/backupninja/manifests/rsync.pp b/puppet/modules/backupninja/manifests/rsync.pp
new file mode 100644
index 00000000..fc59950b
--- /dev/null
+++ b/puppet/modules/backupninja/manifests/rsync.pp
@@ -0,0 +1,128 @@
+# Run rsync as part of a backupninja run.
+# Based on backupninja::rdiff
+
+define backupninja::rsync( $order = 90,
+ $ensure = present,
+ # [general]
+ $log = false,
+ $partition = false,
+ $fscheck = false,
+ $read_only = false,
+ $mountpoint = false,
+ $format = false,
+ $days = false,
+ $keepdaily = false,
+ $keepweekly = false,
+ $keepmonthly = false,
+ $lockfile = false,
+ $nicelevel = 0,
+ $tmp = false,
+ $multiconnection = false,
+ $enable_mv_timestamp_bug = false,
+ # [source]
+ $include = [ "/var/spool/cron/crontabs",
+ "/var/backups",
+ "/etc",
+ "/root",
+ "/home",
+ "/usr/local/*bin",
+ "/var/lib/dpkg/status*"
+ ],
+ $exclude = [ "/home/*/.gnupg",
+ "/home/*/.local/share/Trash",
+ "/home/*/.Trash",
+ "/home/*/.thumbnails",
+ "/home/*/.beagle",
+ "/home/*/.aMule",
+ "/home/*/gtk-gnutella-downloads"
+ ],
+ # [dest]
+ $host = false,
+ $user = false,
+ $home = "/home/${user}-${name}",
+ $subfolder = 'rsync',
+ $testconnect = false,
+ $ssh = false,
+ $protocol = false,
+ $numericids = false,
+ $compress = false,
+ $port = false,
+ $bandwidthlimit = false,
+ $remote_rsync = false,
+ $batch = false,
+ $batchbase = false,
+ $fakesuper = false,
+ $id_file = false,
+ # [services]
+ $initscripts = false,
+ $service = false,
+ # [system]
+ $rm = false,
+ $cp = false,
+ $touch = false,
+ $mv = false,
+ $fsck = false,
+ # ssh keypair config
+ $key = false,
+ $keymanage = $backupninja::keymanage,
+ $backupkeystore = $backupninja::keystore,
+ $backupkeytype = $backupninja::keytype,
+ $ssh_dir_manage = true,
+ $ssh_dir = "${home}/.ssh",
+ $authorized_keys_file = 'authorized_keys',
+ # sandbox config
+ $installuser = true,
+ $backuptag = "backupninja-${::fqdn}",
+ # monitoring
+ $nagios_description = "backups-${name}" ) {
+
+ # install client dependencies
+ ensure_resource('package', 'rsync', {'ensure' => $backupninja::ensure_rsync_version})
+
+ # Right now just local origin with remote destination is supported.
+ $from = 'local'
+ $dest = 'remote'
+
+ case $dest {
+ 'remote': {
+ case $host { false: { err("need to define a host for remote backups!") } }
+
+ $directory = "${home}/${subfolder}/"
+
+ backupninja::server::sandbox { "${user}-${name}":
+ user => $user,
+ host => $host,
+ dir => $home,
+ manage_ssh_dir => $ssh_dir_manage,
+ ssh_dir => $ssh_dir,
+ key => $key,
+ authorized_keys_file => $authorized_keys_file,
+ installuser => $installuser,
+ backuptag => $backuptag,
+ keytype => $backupkeytype,
+ backupkeys => $backupkeystore,
+ }
+
+ backupninja::key { "${user}-${name}":
+ user => $user,
+ keymanage => $keymanage,
+ keytype => $backupkeytype,
+ keystore => $backupkeystore,
+ }
+ }
+ }
+
+ file { "${backupninja::configdir}/${order}_${name}.rsync":
+ ensure => $ensure,
+ content => template('backupninja/rsync.conf.erb'),
+ owner => root,
+ group => root,
+ mode => 0600,
+ require => File["${backupninja::configdir}"]
+ }
+
+ if $backupninja::manage_nagios {
+ nagios::service::passive { $nagios_description: }
+ }
+
+}
diff --git a/puppet/modules/backupninja/manifests/server.pp b/puppet/modules/backupninja/manifests/server.pp
new file mode 100644
index 00000000..49e42a0f
--- /dev/null
+++ b/puppet/modules/backupninja/manifests/server.pp
@@ -0,0 +1,147 @@
+# this define realizes all needed resources for a hosted backup
+define backupninja_server_realize($host) {
+ User <<| tag == "backupninja-$host" |>>
+ File <<| tag == "backupninja-$host" |>>
+ Ssh_authorized_key <<| tag == "backupninja-$host" |>>
+}
+
+class backupninja::server (
+ $backupdir = '/backup',
+ $backupdir_ensure = 'directory',
+ $manage_nagios = false,
+ $nagios_server = undef,
+ $nagios_warn_level = 129600,
+ $nagios_crit_level = 216000,
+) {
+
+ group { "backupninjas":
+ ensure => "present",
+ gid => 700
+ }
+
+ file { $backupdir:
+ ensure => $backupdir_ensure,
+ mode => 0710, owner => root, group => "backupninjas",
+ require => $backupdir_ensure ? {
+ 'directory' => undef,
+ default => File["$backupdir_ensure"],
+ }
+ }
+
+ if $manage_nagios {
+
+ case $nagios_server { undef: { err('Cannot manage nagios without nagios_server parameter!') } }
+
+ include nagios::nsca::client
+
+ file { "/usr/local/bin/checkbackups":
+ ensure => "present",
+ source => "puppet:///modules/backupninja/checkbackups.pl",
+ mode => 0755, owner => root, group => root,
+ }
+
+ cron { checkbackups:
+ command => "/usr/local/bin/checkbackups -d ${backupdir} -s ${nagios_server} -w ${nagios_warn_level} -c ${nagios_crit_level} | grep -v 'sent to host successfully'",
+ user => "root",
+ hour => "8-23",
+ minute => 59,
+ require => [ File["/usr/local/bin/checkbackups"], Package['nsca'] ]
+ }
+ }
+
+ # collect all resources from hosted backups
+ Backupninja_server_realize <<| tag == $::fqdn |>>
+
+ # this define allows nodes to declare a remote backup sandbox, that have to
+ # get created on the server
+ define sandbox (
+ $user = $name,
+ $host = $::fqdn,
+ $installuser = true,
+ $dir,
+ $manage_ssh_dir = true,
+ $ssh_dir = "${dir}/.ssh",
+ $authorized_keys_file = 'authorized_keys',
+ $key = false,
+ $keytype = 'dss',
+ $backupkeys = "${fileserver}/keys/backupkeys",
+ $uid = false,
+ $gid = "backupninjas",
+ $backuptag = "backupninja-${::fqdn}",
+ ) {
+
+ if !defined(Backupninja_server_realize["${::fqdn}@${host}"]) {
+ @@backupninja_server_realize { "${::fqdn}@${host}":
+ host => $::fqdn,
+ tag => $host,
+ }
+ }
+
+ if !defined(File["$dir"]) {
+ @@file { "$dir":
+ ensure => directory,
+ mode => 0750, owner => $user, group => 0,
+ tag => "$backuptag",
+ }
+ }
+
+ if $installuser {
+
+ if $manage_ssh_dir {
+ if !defined(File["$ssh_dir"]) {
+ @@file { "${ssh_dir}":
+ ensure => directory,
+ mode => 0700, owner => $user, group => 0,
+ require => [User[$user], File["$dir"]],
+ tag => "$backuptag",
+ }
+ }
+ }
+
+ if $key {
+ # $key contais ssh public key
+ if !defined(Ssh_autorized_key["$user"]) {
+ @@ssh_authorized_key{ "$user":
+ type => $keytype,
+ key => $key,
+ user => $user,
+ target => "${ssh_dir}/${authorized_keys_file}",
+ tag => "$backuptag",
+ require => User[$user],
+ }
+ }
+ }
+ else {
+ # get ssh public key exists from server
+ if !defined(File["${ssh_dir}/${authorized_keys_file}"]) {
+ @@file { "${ssh_dir}/${authorized_keys_file}":
+ ensure => present,
+ mode => 0644, owner => 0, group => 0,
+ source => "${backupkeys}/${user}_id_${keytype}.pub",
+ require => File["${ssh_dir}"],
+ tag => "$backuptag",
+ }
+ }
+ }
+
+ if !defined(User["$user"]) {
+ @@user { "$user":
+ ensure => "present",
+ uid => $uid ? {
+ false => undef,
+ default => $uid
+ },
+ gid => "$gid",
+ comment => "$user backup sandbox",
+ home => "$dir",
+ managehome => true,
+ shell => "/bin/bash",
+ password => '*',
+ require => Group['backupninjas'],
+ tag => "$backuptag"
+ }
+ }
+ }
+ }
+}
+
diff --git a/puppet/modules/backupninja/manifests/sh.pp b/puppet/modules/backupninja/manifests/sh.pp
new file mode 100644
index 00000000..4a60e5fa
--- /dev/null
+++ b/puppet/modules/backupninja/manifests/sh.pp
@@ -0,0 +1,25 @@
+# sh handler, as part of a backupninja run.
+#
+# Valid attributes for this type are:
+#
+# order: The prefix to give to the handler config filename, to set
+# order in which the actions are executed during the backup run.
+#
+# ensure: Allows you to delete an entry if you don't want it any more
+# (but be sure to keep the configdir, name, and order the same, so
+# that we can find the correct file to remove).
+#
+#
+define backupninja::sh($order = 50,
+ $ensure = present,
+ $command_string
+ ) {
+ file { "${backupninja::configdir}/${order}_${name}.sh":
+ ensure => $ensure,
+ content => template('backupninja/sh.conf.erb'),
+ owner => root,
+ group => root,
+ mode => 0600,
+ require => File["${backupninja::configdir}"]
+ }
+}
diff --git a/puppet/modules/backupninja/manifests/svn.pp b/puppet/modules/backupninja/manifests/svn.pp
new file mode 100644
index 00000000..1ab0597f
--- /dev/null
+++ b/puppet/modules/backupninja/manifests/svn.pp
@@ -0,0 +1,28 @@
+# Subversion dumps, as part of a backupninja run.
+#
+# Valid attributes for this type are:
+#
+# order: The prefix to give to the handler config filename, to set
+# order in which the actions are executed during the backup run.
+#
+# ensure: Allows you to delete an entry if you don't want it any more
+# (but be sure to keep the configdir, name, and order the same, so
+# that we can find the correct file to remove).
+#
+#
+define backupninja::svn($order = 20,
+ $ensure = present,
+ $src = '/var/lib/svn',
+ $dest = '/var/backups/svn',
+ $tmp = '/var/backups/svn.tmp',
+ $vsname = false
+ ) {
+ file { "${backupninja::configdir}/${order}_${name}.svn":
+ ensure => $ensure,
+ content => template('backupninja/svn.conf.erb'),
+ owner => root,
+ group => root,
+ mode => 0600,
+ require => File["${backupninja::configdir}"]
+ }
+}
diff --git a/puppet/modules/backupninja/manifests/sys.pp b/puppet/modules/backupninja/manifests/sys.pp
new file mode 100644
index 00000000..946a525e
--- /dev/null
+++ b/puppet/modules/backupninja/manifests/sys.pp
@@ -0,0 +1,45 @@
+# sys handler, as part of a backupninja run.
+#
+# Valid attributes for this type are:
+#
+# order: The prefix to give to the handler config filename, to set
+# order in which the actions are executed during the backup run.
+#
+# ensure: Allows you to delete an entry if you don't want it any more
+# (but be sure to keep the configdir, name, and order the same, so
+# that we can find the correct file to remove).
+#
+#
+define backupninja::sys($order = 30,
+ $ensure = present,
+ $parentdir = '/var/backups',
+ $packages = true,
+ $packagesfile = '/var/backups/dpkg-selections.txt',
+ $partitions = true,
+ $partitionsfile = '/var/backups/partitions.__star__.txt',
+ $dosfdisk = true,
+ $hardware = true,
+ $hardwarefile = '/var/backups/hardware.txt',
+ $dohwinfo = true,
+ $doluks = false,
+ $dolvm = false
+ ) {
+
+ # install client dependencies
+ case $operatingsystem {
+ debian,ubuntu: {
+ ensure_resource('package', 'debconf-utils', {'ensure' => $backupninja::ensure_debconfutils_version})
+ ensure_resource('package', 'hwinfo', {'ensure' => $backupninja::ensure_hwinfo_version})
+ }
+ default: {}
+ }
+
+ file { "${backupninja::configdir}/${order}_${name}.sys":
+ ensure => $ensure,
+ content => template('backupninja/sys.conf.erb'),
+ owner => root,
+ group => root,
+ mode => 0600,
+ require => File["${backupninja::configdir}"]
+ }
+}
diff --git a/puppet/modules/backupninja/templates/backupninja.conf.erb b/puppet/modules/backupninja/templates/backupninja.conf.erb
new file mode 100644
index 00000000..7706a615
--- /dev/null
+++ b/puppet/modules/backupninja/templates/backupninja.conf.erb
@@ -0,0 +1,25 @@
+# This configuration file was auto-generated by the Puppet configuration
+# management system. Any changes you make to this file will be overwritten
+# the next time Puppet runs. Please make configuration changes to this
+# service in Puppet.
+
+loglevel = <%= @loglvl %>
+when = <%= send(:when) %>
+reportemail = <%= @reportemail %>
+reportsuccess = <%= @reportsuccess ? 'yes' : 'no' %>
+reportwarning = <%= @reportwarning ? 'yes' : 'no' %>
+<% if reporthost.is_a? String -%>
+<%= 'reporthost = ' + @reporthost %>
+<% end -%>
+<% if reportuser.is_a? String -%>
+<%= 'reportuser = ' + @reportuser %>
+<% end -%>
+<% if reportdirectory.is_a? String -%>
+<%= 'reportdirectory = ' + @reportdirectory %>
+<% end -%>
+logfile = <%= @logfile %>
+configdirectory = <%= @configdir %>
+scriptdirectory = <%= @scriptdir %>
+libdirectory = <%= @libdir %>
+usecolors = <%= @usecolors ? 'yes' : 'no' %>
+vservers = <%= @vservers ? 'yes' : 'no' %>
diff --git a/puppet/modules/backupninja/templates/backupninja.cron.erb b/puppet/modules/backupninja/templates/backupninja.cron.erb
new file mode 100644
index 00000000..ec392ca9
--- /dev/null
+++ b/puppet/modules/backupninja/templates/backupninja.cron.erb
@@ -0,0 +1,6 @@
+# /etc/cron.d/backupninja -- cron tab entry for package backupninja
+
+PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
+
+# # run backupninja
+<%= min %> <%= hour %> <%= dom %> <%= month %> <%= dow %> root if [ -x <%= backupninja_test_cmd %> ]; then <%= backupninja_cmd %>; fi
diff --git a/puppet/modules/backupninja/templates/dup.conf.erb b/puppet/modules/backupninja/templates/dup.conf.erb
new file mode 100644
index 00000000..4f15e789
--- /dev/null
+++ b/puppet/modules/backupninja/templates/dup.conf.erb
@@ -0,0 +1,46 @@
+# This configuration file was auto-generated by the Puppet configuration
+# management system. Any changes you make to this file will be overwritten
+# the next time Puppet runs. Please make configuration changes to this
+# service in Puppet.
+
+<%= 'options = ' + options if options %>
+<%= 'nicelevel = ' + nicelevel if nicelevel %>
+<%= 'testconnect = ' + testconnect if testconnect %>
+<%= 'tmpdir = ' + tmpdir if tmpdir %>
+
+[gpg]
+<%= 'sign = ' + sign if sign %>
+<%= 'encryptkey = ' + encryptkey if encryptkey %>
+<%= 'signkey = ' + signkey if signkey %>
+<%= 'password = ' + password if password %>
+
+[source]
+<% if include.is_a? String -%>
+<%= 'include = ' + include %>
+<% elsif include.is_a? Array -%>
+<%= include.map { |i| "include = #{i}" }.join("\n") %>
+<% end -%>
+
+<% if exclude.is_a? String -%>
+<%= 'exclude = ' + exclude %>
+<% elsif exclude.is_a? Array -%>
+<%= exclude.map { |i| "exclude = #{i}" }.join("\n") %>
+<% end -%>
+
+<% if vsinclude.is_a? String -%>
+<%= 'vsinclude = ' + vsinclude %>
+<% elsif vsinclude.is_a? Array -%>
+<%= vsinclude.map { |i| "vsinclude = #{i}" }.join("\n") %>
+<% end -%>
+
+[dest]
+<%= 'incremental = ' + incremental if incremental %>
+<%= 'increments = ' + increments if increments %>
+<%= 'keep = ' + keep if keep %>
+<%= 'keepincroffulls = ' + keepincroffulls if keepincroffulls %>
+<%= 'bandwidthlimit = ' + bandwidthlimit if bandwidthlimit %>
+<%= 'sshoptions = ' + sshoptions if sshoptions %>
+<%= 'destdir = ' + destdir if destdir %>
+<%= 'desthost = ' + desthost if desthost %>
+<%= 'destuser = ' + destuser if destuser %>
+<%= 'desturl = ' + desturl if desturl %>
diff --git a/puppet/modules/backupninja/templates/labelmount.conf.erb b/puppet/modules/backupninja/templates/labelmount.conf.erb
new file mode 100644
index 00000000..e40c49d3
--- /dev/null
+++ b/puppet/modules/backupninja/templates/labelmount.conf.erb
@@ -0,0 +1,2 @@
+label = <%= label %>
+dest = <%= dest %>
diff --git a/puppet/modules/backupninja/templates/labelmount.handler b/puppet/modules/backupninja/templates/labelmount.handler
new file mode 100644
index 00000000..22090bd4
--- /dev/null
+++ b/puppet/modules/backupninja/templates/labelmount.handler
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+# Mount a block device with the specified label ('label') onto the given
+# directory ('dest').
+
+getconf label
+getconf dest
+
+if [ ! -b "/dev/disk/by-label/$label" ]; then
+ halt "No partition labelled '$label' is available"
+fi
+
+if [ ! -d "$dest" ]; then
+ halt "Destination directory does not exist"
+fi
+
+mount -t auto /dev/disk/by-label/$label $dest || halt "Mount failed"
diff --git a/puppet/modules/backupninja/templates/maildir.conf.erb b/puppet/modules/backupninja/templates/maildir.conf.erb
new file mode 100644
index 00000000..351f3824
--- /dev/null
+++ b/puppet/modules/backupninja/templates/maildir.conf.erb
@@ -0,0 +1,14 @@
+# This configuration file was auto-generated by the Puppet configuration
+# management system. Any changes you make to this file will be overwritten
+# the next time Puppet runs. Please make configuration changes to this
+# service in Puppet.
+
+<% %w{when srcdir destdir desthost destuser destid_file keepdaily keepweekly keepmonthly}.each do |v|
+ if send(v)
+ -%><%= v + ' = ' + send(v) + "\n" %><%
+ end
+end -%>
+
+remove = <%= remove ? 'yes' : 'no' %>
+multiconnection = <%= multiconnection ? 'yes' : 'no' %>
+
diff --git a/puppet/modules/backupninja/templates/mysql.conf.erb b/puppet/modules/backupninja/templates/mysql.conf.erb
new file mode 100644
index 00000000..b7ac5e8f
--- /dev/null
+++ b/puppet/modules/backupninja/templates/mysql.conf.erb
@@ -0,0 +1,25 @@
+# This configuration file was auto-generated by the Puppet configuration
+# management system. Any changes you make to this file will be overwritten
+# the next time Puppet runs. Please make configuration changes to this
+# service in Puppet.
+
+<% %w{user dbusername dbpassword dbhost databases backupdir vsname sqldumpoptions}.each do |v|
+ if send(v)
+ -%><%= v + ' = ' + send(v) + "\n" %><%
+ end
+end -%>
+
+hotcopy = <%= hotcopy ? 'yes' : 'no' %>
+sqldump = <%= sqldump ? 'yes' : 'no' %>
+compress = <%= compress ? 'yes' : 'no' %>
+
+<% if real_configfile %>
+configfile = <%= real_configfile %>
+<% end %>
+
+<% if nodata.is_a? String -%>
+<%= 'nodata = ' + nodata %>
+<% elsif nodata.is_a? Array -%>
+<%= "nodata = " + nodata.map { |i| "#{i}" }.join(" ") %>
+<% end -%>
+
diff --git a/puppet/modules/backupninja/templates/pgsql.conf.erb b/puppet/modules/backupninja/templates/pgsql.conf.erb
new file mode 100644
index 00000000..5ffa89c0
--- /dev/null
+++ b/puppet/modules/backupninja/templates/pgsql.conf.erb
@@ -0,0 +1,13 @@
+<% if vsname %>
+vsname = <%= vsname %>
+<% end %>
+<% if backupdir %>
+backupdir = <%= backupdir %>
+<% end %>
+<% if databases.is_a? String -%>
+<%= 'databases = ' + databases %>
+<% elsif databases.is_a? Array -%>
+<%= "databases = " + databases.map { |i| "#{i}" }.join(" ") %>
+<% end -%>
+compress = <%= compress ? 'yes' : 'no' %>
+
diff --git a/puppet/modules/backupninja/templates/rdiff.conf.erb b/puppet/modules/backupninja/templates/rdiff.conf.erb
new file mode 100644
index 00000000..23c336fc
--- /dev/null
+++ b/puppet/modules/backupninja/templates/rdiff.conf.erb
@@ -0,0 +1,38 @@
+# This configuration file was auto-generated by the Puppet configuration
+# management system. Any changes you make to this file will be overwritten
+# the next time Puppet runs. Please make configuration changes to this
+# service in Puppet.
+
+<%= 'options = ' + options if options %>
+
+<%= extras if extras %>
+
+[source]
+type = local
+<%= 'keep = ' + keep if keep %>
+
+<% if include.is_a? String -%>
+<%= 'include = ' + include %>
+<% elsif include.is_a? Array -%>
+<%= include.map { |i| "include = #{i}" }.join("\n") %>
+<% end -%>
+
+<% if exclude.is_a? String -%>
+<%= 'exclude = ' + exclude %>
+<% elsif exclude.is_a? Array -%>
+<%= exclude.map { |i| "exclude = #{i}" }.join("\n") %>
+<% end -%>
+
+<% if vsinclude.is_a? String -%>
+<%= 'vsinclude = ' + vsinclude %>
+<% elsif vsinclude.is_a? Array -%>
+<%= vsinclude.map { |i| "vsinclude = #{i}" }.join("\n") %>
+<% end -%>
+
+[dest]
+<%- %w{type host directory user sshoptions}.each do |v|
+ if has_variable?(v) and instance_variable_get("@#{v}").to_s != "false" -%>
+<%= v + ' = ' + instance_variable_get("@#{v}").to_s %>
+<%-
+ end
+end -%>
diff --git a/puppet/modules/backupninja/templates/rsync.conf.erb b/puppet/modules/backupninja/templates/rsync.conf.erb
new file mode 100644
index 00000000..778676fc
--- /dev/null
+++ b/puppet/modules/backupninja/templates/rsync.conf.erb
@@ -0,0 +1,49 @@
+# This configuration file was auto-generated by the Puppet configuration
+# management system. Any changes you make to this file will be overwritten
+# the next time Puppet runs. Please make configuration changes to this
+# service in Puppet.
+
+[general]
+<%- %w{log partition fscheck read_only mountpoint backupdir format days keepdaily keepweekly keepmonthly lockfile nicelevel enable_mv_timestamp_bug, tmp, multiconnection}.each do |v|
+ if has_variable?(v) and instance_variable_get("@#{v}").to_s != "false" -%>
+<%= v + ' = ' + instance_variable_get("@#{v}").to_s %>
+<%-
+ end
+end -%>
+
+[source]
+<% unless from.empty? and from.to_s != "false" -%>
+from = <%= from %>
+<% end -%>
+<%- %w{include exclude}.each do |v|
+ if has_variable?(v)
+ instance_variable_get("@#{v}").to_a.each do |parameter| -%>
+<%= v + ' = ' + parameter %>
+<%-
+ end
+ end
+end -%>
+
+[dest]
+<%- %w{dest testconnect ssh protocol numericids compress host port user id_file bandwidthlimit remote_rsync batch batchbase fakesuper}.each do |v|
+ if has_variable?(v) and instance_variable_get("@#{v}").to_s != "false" -%>
+<%= v + ' = ' + instance_variable_get("@#{v}").to_s %>
+<%-
+ end
+end -%>
+
+[services]
+<%- %w{initscripts service}.each do |v|
+ if has_variable?(v) and instance_variable_get("@#{v}").to_s != "false" -%>
+<%= v + ' = ' + instance_variable_get("@#{v}").to_s %>
+<%-
+ end
+end -%>
+
+[system]
+<%- %w{rm cp touch mv fsck}.each do |v|
+ if has_variable?(v) and instance_variable_get("@#{v}").to_s != "false" -%>
+<%= v + ' = ' + instance_variable_get("@#{v}").to_s %>
+<%-
+ end
+end -%>
diff --git a/puppet/modules/backupninja/templates/sh.conf.erb b/puppet/modules/backupninja/templates/sh.conf.erb
new file mode 100644
index 00000000..f1b4161a
--- /dev/null
+++ b/puppet/modules/backupninja/templates/sh.conf.erb
@@ -0,0 +1,10 @@
+#!/bin/sh
+#
+# This configuration file was auto-generated by the Puppet configuration
+# management system. Any changes you make to this file will be overwritten
+# the next time Puppet runs. Please make configuration changes to this
+# service in Puppet.
+
+<% @command_string.each_line do |line| -%>
+<%= line %>
+<% end -%>
diff --git a/puppet/modules/backupninja/templates/svn.conf.erb b/puppet/modules/backupninja/templates/svn.conf.erb
new file mode 100644
index 00000000..465cc673
--- /dev/null
+++ b/puppet/modules/backupninja/templates/svn.conf.erb
@@ -0,0 +1,10 @@
+# This configuration file was auto-generated by the Puppet configuration
+# management system. Any changes you make to this file will be overwritten
+# the next time Puppet runs. Please make configuration changes to this
+# service in Puppet.
+
+<% %w{src dest tmp vsname}.each do |v|
+ if send(v)
+ -%><%= v + ' = ' + send(v) + "\n" %><%
+ end
+end -%> \ No newline at end of file
diff --git a/puppet/modules/backupninja/templates/sys.conf.erb b/puppet/modules/backupninja/templates/sys.conf.erb
new file mode 100644
index 00000000..a684e8b7
--- /dev/null
+++ b/puppet/modules/backupninja/templates/sys.conf.erb
@@ -0,0 +1,18 @@
+# This configuration file was auto-generated by the Puppet configuration
+# management system. Any changes you make to this file will be overwritten
+# the next time Puppet runs. Please make configuration changes to this
+# service in Puppet.
+
+<% %w{parentdir packagesfile partitionsfile hardwarefile}.each do |v|
+ if send(v)
+ -%><%= v + ' = ' + send(v) + "\n" %><%
+ end
+end -%>
+
+packages = <%= packages ? 'yes' : 'no' %>
+partitions = <%= partitions ? 'yes' : 'no' %>
+dosfdisk = <%= dosfdisk ? 'yes' : 'no' %>
+hardware = <%= hardware ? 'yes' : 'no' %>
+dohwinfo = <%= dohwinfo ? 'yes' : 'no' %>
+luksheaders = <%= doluks ? 'yes' : 'no' %>
+lvm = <%= dolvm ? 'yes' : 'no' %>
diff --git a/puppet/modules/backupninja/templates/umount.conf.erb b/puppet/modules/backupninja/templates/umount.conf.erb
new file mode 100644
index 00000000..59bfaec8
--- /dev/null
+++ b/puppet/modules/backupninja/templates/umount.conf.erb
@@ -0,0 +1 @@
+dir = <%= dest %>
diff --git a/puppet/modules/backupninja/templates/umount.handler b/puppet/modules/backupninja/templates/umount.handler
new file mode 100644
index 00000000..4fea195a
--- /dev/null
+++ b/puppet/modules/backupninja/templates/umount.handler
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+# Unmount the specified directory ('dir'), forcefully if necessary.
+
+getconf dir
+
+if ! umount $dir; then
+ warning "Simple unmount failed for $dir; being forceful"
+ if ! umount -f $dir; then
+ warning "Forceful unmount failed for $dir; being lazy"
+ if ! umount -l $dir; then
+ warning "Lazy unmount failed for $dir; you're on your own"
+ fi
+ fi
+fi
diff --git a/puppet/modules/bundler/.gitignore b/puppet/modules/bundler/.gitignore
new file mode 100644
index 00000000..1377554e
--- /dev/null
+++ b/puppet/modules/bundler/.gitignore
@@ -0,0 +1 @@
+*.swp
diff --git a/puppet/modules/bundler/LICENSE b/puppet/modules/bundler/LICENSE
new file mode 100644
index 00000000..9cef3784
--- /dev/null
+++ b/puppet/modules/bundler/LICENSE
@@ -0,0 +1,13 @@
+Copyright (c) 2012 Evan Stachowiak
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/puppet/modules/bundler/README.md b/puppet/modules/bundler/README.md
new file mode 100644
index 00000000..2abb1cfc
--- /dev/null
+++ b/puppet/modules/bundler/README.md
@@ -0,0 +1,63 @@
+puppet-bundler - Bundler gem manager for Ruby
+==========================================
+
+This puppet module will install bundler and set config
+variables.
+
+This module supports Ubuntu 10.04 and Debian
+
+Installation
+------------
+
+1. Copy this directory to your puppet master module path $(git clone
+https://github.com/evanstachowiak/puppet-bundler bundler)
+
+2. Apply the `bundler` class to any nodes you want bundler installed on:
+
+ class { 'bundler::install': }
+
+ By default this will install bundler with RVM, if you wish to use another
+ method, you can pass any puppet package provider to the class as
+ 'install_method', or just use 'package' if you wish the puppet parser to
+ automatically chose the best method for your platform.
+
+ Examples: class { 'bundler::install': install_method => 'fink' }
+ class { 'bundler::install': install_method => 'gem' }
+ class { 'bundler::install': install_method => 'package' }
+
+3. Set whatever config variables are necessary:
+ bundler::config { 'linecache19':
+ user => ubuntu,
+ config_flag => "--with-ruby-include=/usr/local/rvm/src/ruby-1.9.2-p290",
+ app_dir => your_app_dir,
+ }
+
+
+Contributing
+------------
+
+- fork on github (https://github.com/evanstachowiak/puppet-bundler)
+- send a pull request
+
+Author
+------
+Evan Stachowiak (https://github.com/evanstachowiak)
+
+LICENSE
+-------
+
+ Author:: Evan Stachowiak
+ Copyright:: Copyright (c) 2012 Evan Stachowiak
+ License:: Apache License, Version 2.0
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/puppet/modules/bundler/manifests/config.pp b/puppet/modules/bundler/manifests/config.pp
new file mode 100644
index 00000000..5937a228
--- /dev/null
+++ b/puppet/modules/bundler/manifests/config.pp
@@ -0,0 +1,74 @@
+# Define bundler::config
+#
+# All config settings for candiapp class
+#
+# == Parameters
+#
+# [*user*]
+# App directory owner
+# [*config_flag*]
+# config flag for specific gem compile settings
+# [*app_dir*]
+# App directory where Gemfile is located
+# [*home_dir_base_path*]
+# Home directory of the specified user
+# [*use_rvm*]
+# Sets whether rvm is used. Defaults to true
+# [*rvm_bin*]
+# RVM install location. Defaults to /usr/local/rvm/bin/rvm
+# [*rvm_gem_path*]
+# RVM gem directory. Defaults to /usr/local/rvm/gems
+# [*rvm_gemset*]
+# RVM gemset to use. Defaults to global.
+# [*ruby_version*]
+# Ruby version for RVM purposes.
+# [*bundler_path*]
+# Bundler install directory
+#
+# == Examples
+#
+#
+# == Requires:
+#
+# class { bundler::install: }
+#
+define bundler::config (
+ $user,
+ $config_flag,
+ $app_dir,
+ $home_dir_base_path = $bundler::params::home_dir_base_path,
+ $use_rvm = $bundler::params::use_rvm,
+ $rvm_bin = $bundler::params::rvm_bin,
+ $rvm_gem_path = $bundler::params::rvm_gem_path,
+ $rvm_gemset = $bundler::params::rvm_gemset,
+ $ruby_version = $bundler::ruby_version,
+ $bundler_path = $bundler::params::bundler_path
+) {
+
+ Class['bundler::install'] -> Bundler::Config[$name]
+
+ if $user == 'root' {
+ $home_dir = '/root'
+ }
+ else {
+ $home_dir = "${home_dir_base_path}/${user}"
+ }
+
+ # Must use $bundler_path_real, otherwise cannot reassign variable error is thrown
+ if $use_rvm == 'true' {
+ $bundler_path_rvm = "${rvm_gem_path}/${ruby_version}@${rvm_gemset}/bin"
+ $bundler_bin = "${rvm_bin} ${ruby_version} exec ${bundler_path_rvm}/bundle"
+ }
+ else {
+ $bundler_bin = "${bundler_path}/bundle"
+ }
+
+ # Bundler doesn't respect uid. Use /bin/su to override this behavior for users
+ # other than root.
+ exec { "bundler_config_${name}":
+ cwd => $app_dir,
+ command => "/bin/su -c '${bundler_bin} config build.${name} ${config_flag} --gemfile=${app_dir}/Gemfile' ${user}",
+ unless => "/bin/grep -i \"BUNDLE_BUILD__${name}: ${config_flag}\" ${home_dir}/.bundle/config",
+ }
+
+}
diff --git a/puppet/modules/bundler/manifests/install.pp b/puppet/modules/bundler/manifests/install.pp
new file mode 100644
index 00000000..1524de31
--- /dev/null
+++ b/puppet/modules/bundler/manifests/install.pp
@@ -0,0 +1,64 @@
+# Class bundler::install
+#
+# Installs bundler Ruby gem manager
+#
+# == Parameters
+#
+# [*install_method*]
+# How to install bundler, 'rvm' is the default
+# [*ruby_version*]
+# Ruby version that bundler will use.
+#
+# == Examples
+#
+#
+# == Requires:
+#
+# If use_rvm = 'true':
+# include rvm
+#
+class bundler::install (
+ $ruby_version = undef,
+ $ensure = 'present',
+ $install_method = 'rvm',
+ $use_rvm = '',
+ ) inherits bundler::params {
+
+ # deprecation warning
+ if $use_rvm != '' {
+ warning('$use_rvm is deprecated, please use $install_method instead')
+ }
+
+ if ( $install_method == undef ) or ( $install_method == 'package' ) {
+ $provider_method = undef
+ }
+ else {
+ # backwards compatibility
+ if $use_rvm == false {
+ $provider_method = gem
+ }
+ else {
+ $provider_method = $bundler::params::install_method
+ }
+ }
+
+ if $provider_method == 'rvm' {
+ if $ruby_version == undef {
+ fail('When using rvm, you must pass a ruby_version')
+ }
+ else {
+ #Install bundler with correct RVM
+ rvm_gem { 'bundler':
+ ensure => $ensure,
+ ruby_version => $ruby_version,
+ }
+ }
+ }
+ else {
+ package { 'bundler':
+ ensure => $ensure,
+ provider => $provider_method,
+ }
+ }
+
+}
diff --git a/puppet/modules/bundler/manifests/params.pp b/puppet/modules/bundler/manifests/params.pp
new file mode 100644
index 00000000..53ca86e4
--- /dev/null
+++ b/puppet/modules/bundler/manifests/params.pp
@@ -0,0 +1,31 @@
+# Class bundler::params
+#
+# All config settings for candiapp class
+#
+# == Parameters
+#
+#
+#
+# == Examples
+#
+#
+# == Requires:
+#
+class bundler::params {
+
+ case $::operatingsystem {
+ ubuntu, debian: {
+ $user = 'root'
+ $home_dir_base_path = '/home'
+ $install_method = 'rvm'
+ $rvm_bin = '/usr/local/rvm/bin/rvm'
+ $rvm_gem_path = '/usr/local/rvm/gems'
+ $rvm_gemset = 'global'
+ $bundler_path = '/usr/bin'
+ }
+ default: {
+ fail("Unsupported platform: ${::operatingsystem}")
+ }
+ }
+
+}
diff --git a/puppet/modules/check_mk/.gitignore b/puppet/modules/check_mk/.gitignore
new file mode 100644
index 00000000..f6dc3f68
--- /dev/null
+++ b/puppet/modules/check_mk/.gitignore
@@ -0,0 +1,3 @@
+pkg/
+metadata.json
+*.swp
diff --git a/puppet/modules/check_mk/Changelog b/puppet/modules/check_mk/Changelog
new file mode 100644
index 00000000..0b2f8a15
--- /dev/null
+++ b/puppet/modules/check_mk/Changelog
@@ -0,0 +1,27 @@
+0.3.0:
+
+* Added host tags to agent config so that host groups can be auto-populated
+
+* Fixed incorrect package name when using a file store that was causing the
+package existence check to fail always causing an often failing reinstall
+
+* Enable a static list of hosts to be specified for those without the Puppet
+check_mk module installed
+
+0.2.0:
+
+* Switched to using OMD rather than manually compiling check_mk
+
+* Added support for host tags and creating host groups based on these tags
+
+* Allow local check_mk configuration to be specified in
+/etc/check_mk/main.mk.local that is appended to /etc/check_mk/main.mk as
+check_mk can do a lot more than is covered by this module
+
+0.1.1:
+
+* Brown paper bag release to fix a silly typo
+
+0.1:
+
+* Initial release
diff --git a/puppet/modules/check_mk/LICENSE b/puppet/modules/check_mk/LICENSE
new file mode 100644
index 00000000..94a9ed02
--- /dev/null
+++ b/puppet/modules/check_mk/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/puppet/modules/check_mk/Modulefile b/puppet/modules/check_mk/Modulefile
new file mode 100644
index 00000000..60c355a3
--- /dev/null
+++ b/puppet/modules/check_mk/Modulefile
@@ -0,0 +1,10 @@
+name 'erwbgy-check_mk'
+version '0.3.0'
+source 'https://github.com/erwbgy/puppet-check_mk.git'
+author 'erwbgy'
+license 'Apache License, Version 2.0'
+summary 'install and configure check_mk'
+description 'Install and configure check_mk agent and Nagios plugin'
+project_page 'https://github.com/erwbgy/puppet-check_mk'
+dependency 'puppetlabs/stdlib', '>= 2.6.0'
+dependency 'ripienaar/concat', '>= 0.2.0'
diff --git a/puppet/modules/check_mk/README.md b/puppet/modules/check_mk/README.md
new file mode 100644
index 00000000..81e1bc87
--- /dev/null
+++ b/puppet/modules/check_mk/README.md
@@ -0,0 +1,268 @@
+# check_mk
+
+Puppet module for:
+
+* Installing and configuring the Open Monitoring Distribution (OMD) which
+ includes Nagios, check_mk and lots of other tools
+
+* Installing and configuring check_mk agents
+
+Agent hostnames are automatically added to the server all_hosts configuration
+using stored configs.
+
+Currently only tested on Redhat-like systems and on Debian.
+
+For examples how to use this class on a debian wheezy system, check out following
+snippets: https://git.codecoop.org/snippets/1, https://git.codecoop.org/snippets/2
+
+## Server
+
+* Installs omd package either using the system repository (eg. yum, apt) or
+ from a package file retrieved from the Puppet file store
+
+* Use check_mk::omd_repo to enable a debian repository for omd
+ (requires apt module from i.e. https://labs.riseup.net/code/projects/shared-apt).
+ For now, you need to fetch the omd apt-key manually from
+ http://labs.consol.de/nagios/omd-repository/, put it into your site_apt/files/keys
+ directory and pass the custom_key_dir parameter to the apt class, like
+
+
+ class { 'apt':
+ custom_key_dir => 'puppet:///modules/site-apt/keys'
+ }
+
+* Populates the all_hosts array in /etc/check_mk/main.mk with hostnames
+ exported by check::agent classes on agent hosts
+
+### Example 1
+
+ include check_mk
+
+Installs the 'monitoring' package from the system repository. The default 'monitoring' site is used.
+
+### Example 2
+
+ class { 'check_mk':
+ filestore => 'puppet:///files/check_mk',
+ package => 'omd-0.56-rh60-29.x86_64.rpm'
+ }
+
+Installs the specified omd package after retrieving it from the Puppet file store.
+
+### Example 3
+
+ class { 'check_mk':
+ site => 'acme',
+ }
+
+Installs the omd package from the system repository. A site called 'acme' is
+created making the URL http://hostname/acme/check_mk/ running as the 'acme' user.
+
+### check_mk parameters
+
+*package*: The omd package (rpm or deb) to install. Optional.
+
+*filestore*: The Puppet file store location where the package can be found (eg. 'puppet:///files/check_mk'). Optional.
+
+*host_groups*: A hash with the host group names as the keys with a list of host tags to match as values. (See 'Host groups and tags' below). Optional.
+
+*site*: The name of the omd site (and the user/group it runs as). Default: 'monitoring'
+
+*workspace*: The directory to use to store files used during installation. Default: '/root/check_mk'
+
+*omdadmin_htpasswd*: changes the htpasswd of the amdadmin user (requires apache module from i.e.
+ https://labs.riseup.net/code/projects/shared-apache)
+
+*use_ssh*: Configures ssh to agents that use the same parameter.
+ Default: false.
+
+*inventory_only_on_changes*: By default (parameter set to `true`) these two execs are called
+ only when config files changes:
+ - Exec['check_mk-refresh'] (which runs a check inventory by calling `check_mk -II`)
+ - Exec['check_mk-reload'] (which generates the nagios config and reloads nagios by calling `check_mk -O`)
+ By setting this parameter to `false` these execs will be called on each puppetrun.
+
+### Notes
+
+* A user and group with the same value as the site parameter is created. By default this is 'monitoring'.
+
+* The URL is http://yourhostname/sitename/check_mk/ - for example http://monhost.domain/monitoring/check_mk/
+
+* The default username/password is omdadmin/omd. To change this or add additional users log in as the site user and run htpasswd - for example:
+
+ monitoring$ htpasswd -b ~/etc/htpasswd guest guest
+
+* A user called 'guest' is configured as a guest user but is not enabled unless a password is set (as above).
+
+* RedHat-like RPM downloads from http://files.omdistro.org/releases/centos_rhel/
+
+## Agent
+
+* Installs the check_mk-agent and check_mk-agent-logwatch packages
+
+* Configures the /etc/xinetd.d/check_mk configuration file
+
+### Example 1
+
+ include check_mk::agent
+
+Installs the check_mk and check_mk_logwatch packages from the system repository
+and configures /etc/xinetd.d/check_mk with no IP whitelist restrictions.
+
+### Example 2
+
+ class { 'check_mk::agent':
+ version => '1.2.0p3-1',
+ ip_whitelist => [ '10.7.96.21', '10.7.96.22' ],
+ }
+
+Installs the specified versions of the check_mk and check_mk_logwatch packages
+after retrieving them from the Puppet file store. Configures
+/etc/xinetd.d/check_mk so that only the specified IPs (and localhost/127.0.0.1)
+are allowed to connect.
+
+### check_mk::agent parameters
+
+*filestore*: The Puppet file store location where the packages can be found (eg. 'puppet:///files/check_mk'). Optional.
+
+*ip_whitelist*: The list of IP addresses that are allowed to retrieve check_mk
+data. (Note that localhost is always allowed to connect.) By default any IP can
+connect.
+
+*port*: The port the check_mk agent listens on. Default: '6556'
+
+*server_dir*: The directory in which the check_mk_agent executable is located.
+Default: '/usr/bin'
+
+*use_cache*: Whether or not to cache the results - useful with redundant
+monitoring server setups. Default: 'false'
+
+*user*: The user that the agent runs as. Default: 'root'
+
+*version*: The version in the check_mk packages - for example if the RPM is
+'check_mk-agent-1.2.0p3-1.noarch.rpm' then the version is '1.2.0p3-1'.
+Only required if a filestore is used.
+
+*workspace*: The directory to use to store files used during installation.
+Default: '/root/check_mk'
+
+*method*: "xinetd" (default) or "ssh"
+ "ssh": Use ssh instead of the tcp wrapper in order to allows the server to
+ execute the agent on the client.
+
+*generate_sshkey*: true or false (default)
+
+ * Deploys ssh keypair on server (in /opt/omd/sites/monitoring/.ssh)
+ * Saves keypair on puppetmaster (/etc/puppet/modules/keys/files/check_mk_keys by default)
+ * Deploys public key on client in /root/.ssh/authorized_keys (restricting allows command to "/usr/bin/check_mk_agent")
+
+## Host groups and tags
+
+By default check_mk puts all hosts into a group called 'check_mk' but where you
+have more than a few you will often want your own groups. We can do this by
+setting host tags on the agents and then configuring host groups on the server
+side to match hosts with these tags.
+
+For example in the hiera config for your agent hosts you could have:
+
+ check_mk::agent::host_tags:
+ - '%{osfamily}'
+
+and on the monitoring host you could have:
+
+ check_mk::host_groups:
+ RedHat:
+ description: 'RedHat or_CentOS hosts'
+ host_tags:
+ - RedHat
+ Debian:
+ description: 'Debian or Ubuntu_hosts'
+ host_tags:
+ - Debian
+ SuSE:
+ description: 'SuSE hosts'
+ host_tags:
+ - Suse
+
+You can of course have as many host tags as you like. I have custom facts for
+the server role and the environment type (dev, qa, stage, prod) and define
+groups based on the role and envtype host tags.
+
+Remember to run the Puppet agent on your agent hosts to export any host tags
+and run the Puppet agent on the monitoring host to pick up any changes to the
+host groups.
+
+## Static host config
+
+Hosts that do not run Puppet with the check_mk module are not automatically
+added to the all_hosts list in main.mk. To manually include these hosts you can
+add them to '/omd/sites/monitoring/etc/check_mk/all_hosts_static' (replacing
+'monitoring' with your site name). Use the quoted fully qualified domain name
+with a two-space prefix and a comma suffix - for example:
+
+ 'host1.domain',
+ 'host2.domain',
+
+You can also include host tags - for example:
+
+ 'host1.domain|windows|dev',
+ 'host2.domain|windows|prod',
+
+Remember to run the Puppet agent on your monitoring host to pick up any changes.
+
+## Migrating from nagios-statd
+
+nagios-statd provides several features that can be replaced with check_mk
+plugins.
+
+*nagios-stat-proc*: checks processes on the agent system
+If you previously used the nagios puppet module to do something like:
+
+ check_command => 'nagios-stat-proc!/usr/sbin/foo!1!1!proc'
+
+you can now use the check_mk ps check:
+
+ check_mk::agent::ps {
+ 'foo':
+ procname => '/usr/local/weirdpath/foo',
+ levels => '1, 2, 2, 3',
+ owner => 'alice'
+ }
+
+defaults:
+ procname: "/usr/sbin/${name}"
+ levels: '1, 1, 1, 1'
+ owner: not required
+
+Run check_mk with '-M ps' for the manpage explaining the parameters.
+
+*swap*: check_mk has a 'mem.used' check which is enabled by default. But
+ as it's manpage explains if you want to measure swappiness you are
+ better off using the 'kernel' check and measuring 'Major Page Faults'
+ (pgmajfault).
+
+*disk*: check_mk has a 'df' check which is enabled by default.
+
+## Migrating from nrpe to mrpe
+
+If you were using nrpe to run a nagios plugin locally, first check if a
+native check_mk check exists with the same functionality, if not consider
+writing one. But if continuing to use the nagios plugin makes sense you
+can switch to mrpe.
+
+* Continue to deliver the plugin to the agent system
+* include check_mk::agent::mrpe
+* add a line to the mrpe.cfg file using augeas
+
+ augeas {
+ "Foo":
+ incl => '/etc/check_mk/mrpe.cfg',
+ lens => 'Spacevars.lns',
+ changes => 'set FOO /usr/local/lib/nagios/plugins/check_foo',
+ require => [ File['/usr/local/lib/nagios/plugins' ], Package['check-mk-agent'] ];
+ }
+
+
+This is the riseup clone, available at:
+
+git://labs.riseup.net/module_check_mk
diff --git a/puppet/modules/check_mk/Rakefile b/puppet/modules/check_mk/Rakefile
new file mode 100644
index 00000000..14f1c246
--- /dev/null
+++ b/puppet/modules/check_mk/Rakefile
@@ -0,0 +1,2 @@
+require 'rubygems'
+require 'puppetlabs_spec_helper/rake_tasks'
diff --git a/puppet/modules/check_mk/TODO b/puppet/modules/check_mk/TODO
new file mode 100644
index 00000000..1697f34b
--- /dev/null
+++ b/puppet/modules/check_mk/TODO
@@ -0,0 +1,5 @@
+Use nagios_hostgroup type rather than clumsily creating our own.
+Add support for ignored_services to eliminate false alerts.
+Implement support for choosing either upstream install or distro supplied
+ packages. If using distro packages, detect distro and set package names
+ to reasonable default (currently requires overriding).
diff --git a/puppet/modules/check_mk/debian.md b/puppet/modules/check_mk/debian.md
new file mode 100644
index 00000000..96d32a4e
--- /dev/null
+++ b/puppet/modules/check_mk/debian.md
@@ -0,0 +1,35 @@
+Examples for using this check_mk repository on debian
+=====================================================
+
+What it does
+============
+
+* ssh authentication is configured to allow the server to execute check_mk on the client
+* omd is installed on the server
+* check_mk is installed as package on the client
+
+On the client
+=============
+
+ class site_check_mk::client {
+ class { 'check_mk::agent':
+ agent_package_name => 'check-mk-agent',
+ agent_logwatch_package_name => 'check-mk-agent-logwatch',
+ use_ssh => true,
+ register_agent => false
+ }
+ }
+
+
+On the server
+=============
+
+ include check_mk::omd_repo
+ class { 'check_mk':
+ package => 'omd',
+ omd_service_name => 'omd-1.00',
+ http_service_name => 'apache2',
+ omdadmin_htpasswd => trocla("${::fqdn}_omdadmin"),
+ use_ssh => true;
+ }
+
diff --git a/puppet/modules/check_mk/example.yaml b/puppet/modules/check_mk/example.yaml
new file mode 100644
index 00000000..de82ecc5
--- /dev/null
+++ b/puppet/modules/check_mk/example.yaml
@@ -0,0 +1,93 @@
+# Monitoring Server
+check_mk::filestore: 'puppet:///files/check_mk'
+check_mk::package: 'omd-0.56-rh60-29.x86_64.rpm'
+
+#check_parameters = [
+# ( (95, 99), ALL_HOSTS, [ "fs_/boot" ]),
+# ( (3192, 3584), ALL_HOSTS, [ "JVM PODDSv3 Memory" ]),
+# ( (150, 200), ALL_HOSTS, [ "JVM PODDSv3 Threads" ]),
+# ( (4000, 6000), [ 'coherence' ], ALL_HOSTS, [ "Number of threads" ]),
+#]
+# Defaults:
+# hosts: ALL_HOSTS
+# tags: undef
+check_mk::check_parameters:
+ 'fs_/boot':
+ warning: '95'
+ critical: '99'
+ 'JVM MyApp Memory':
+ warning: '3192'
+ critical: '3584'
+ 'JVM MyApp Threads':
+ warning: '150'
+ critical: '200'
+ 'Number of threads':
+ tags: [ 'coherence' ]
+ warning: '4000'
+ critical: '6000'
+ 'fs_/':
+ hosts: [ 'myhost1.domain.com', 'myhost2.domain.com' ]
+ warning: '60'
+ critical: '70'
+
+check_mk::host_groups:
+ 'Puppet_Masters':
+ host_tags:
+ - 'puppet-master'
+
+ 'My_App':
+ description: 'My Application'
+ host_tags:
+ - 'my-app'
+
+ 'My_DB':
+ description: 'My Database'
+ host_tags:
+ - 'my-db'
+
+#ignored_services = [
+# ( [ "windows" ], ALL_HOSTS, [ "LOG Security" ] ),
+# ( ALL_HOSTS, [ "NFS mount /home/" ] )
+#]
+
+check_mk::ignored_services:
+ 'LOG security':
+ tags:
+ 'windows'
+ 'NFS mount /home/':
+ hosts:
+ - 'lnxuser1.domain.com'
+ - 'lnxuser2.domain.com'
+
+# Monitoring Agent
+check_mk::agent::filestore: 'puppet:///files/check_mk'
+check_mk::agent::version: '1.2.0p3-1'
+
+# Set host tags based on built-in and custom facts
+check_mk::agent::host_tags:
+ - '%{envtype}'
+ - '%{kernel}'
+ - '%{role}'
+ - '%{location}'
+
+check_mk::agent::jolokia::server: '127.0.0.1'
+check_mk::agent::jolokia::port: '8080'
+check_mk::agent::jolokia::user: 'monitoring'
+check_mk::agent::jolokia::password: 'tinstaafl'
+check_mk::agent::jolokia::suburi: 'jolokia'
+
+check_mk::agent::jolokia::instances:
+ 'My-App':
+ server: '10.0.0.1'
+ port: '8190'
+
+check_mk::agent::logwatch::keep_defaults: 'true'
+check_mk::agent::logwatch::logfiles:
+ '/apps/tomcat1/logs/tomcat/catalina.*.log':
+ critical:
+ - '^SERVERE:'
+ - '^ERROR:'
+ warning:
+ - '^WARNING:'
+ ignore:
+ - '^INFO'
diff --git a/puppet/modules/check_mk/files/agent/local_checks/all_hosts/README.md b/puppet/modules/check_mk/files/agent/local_checks/all_hosts/README.md
new file mode 100644
index 00000000..f5234cbf
--- /dev/null
+++ b/puppet/modules/check_mk/files/agent/local_checks/all_hosts/README.md
@@ -0,0 +1,2 @@
+Place local checks in this directory,
+see http://mathias-kettner.de/checkmk_localchecks.html
diff --git a/puppet/modules/check_mk/files/use_ssh.mk b/puppet/modules/check_mk/files/use_ssh.mk
new file mode 100644
index 00000000..b5d77c62
--- /dev/null
+++ b/puppet/modules/check_mk/files/use_ssh.mk
@@ -0,0 +1,5 @@
+# http://mathias-kettner.de/checkmk_datasource_programs.html
+datasource_programs = [
+ ( "ssh -l root -i /omd/sites/monitoring/.ssh/monitoring_<HOST>_id_rsa <IP> check_mk_agent", ['ssh'], ALL_HOSTS ),
+]
+
diff --git a/puppet/modules/check_mk/manifests/agent.pp b/puppet/modules/check_mk/manifests/agent.pp
new file mode 100644
index 00000000..64109ae9
--- /dev/null
+++ b/puppet/modules/check_mk/manifests/agent.pp
@@ -0,0 +1,70 @@
+class check_mk::agent (
+ $filestore = undef,
+ $host_tags = undef,
+ $ip_whitelist = undef,
+ $port = '6556',
+ $server_dir = '/usr/bin',
+ $keydir = '/omd/sites/monitoring',
+ $authdir = '/omd/sites/monitoring',
+ $authfile = undef,
+ $use_cache = false,
+ $user = 'root',
+ $version = undef,
+ $workspace = '/root/check_mk',
+ $agent_package_name = 'check_mk-agent',
+ $agent_logwatch_package_name = 'check_mk-agent-logwatch',
+ $method = 'xinetd',
+ $generate_sshkey = false,
+ $sshuser = undef,
+ $use_ssh_tag = 'ssh',
+ $hostname = $::fqdn,
+ $register_agent = true
+) {
+
+ case $method {
+ 'xinetd': {
+ $tags = $host_tags
+ include check_mk::agent::service
+ }
+ 'ssh': {
+ if ( $host_tags == undef ) or ( $host_tags == '' ) {
+ $tags = $use_ssh_tag
+ } else {
+ $tags = "${host_tags}|${use_ssh_tag}"
+ }
+ }
+ default: {}
+ }
+
+ class { 'check_mk::agent::install':
+ version => $version,
+ filestore => $filestore,
+ workspace => $workspace,
+ agent_package_name => $agent_package_name,
+ agent_logwatch_package_name => $agent_logwatch_package_name,
+ method => $method
+ }
+
+ class { 'check_mk::agent::config':
+ ip_whitelist => $ip_whitelist,
+ port => $port,
+ server_dir => $server_dir,
+ keydir => $keydir,
+ authdir => $authdir,
+ authfile => $authfile,
+ use_cache => $use_cache,
+ user => $user,
+ method => $method,
+ generate_sshkey => $generate_sshkey,
+ sshuser => $sshuser,
+ hostname => $hostname,
+ require => Class['check_mk::agent::install'],
+ }
+
+ if ( $register_agent ) {
+ class { 'check_mk::agent::register':
+ host_tags => $tags,
+ hostname => $hostname,
+ }
+ }
+}
diff --git a/puppet/modules/check_mk/manifests/agent/config.pp b/puppet/modules/check_mk/manifests/agent/config.pp
new file mode 100644
index 00000000..8ee5f185
--- /dev/null
+++ b/puppet/modules/check_mk/manifests/agent/config.pp
@@ -0,0 +1,59 @@
+class check_mk::agent::config (
+ $ip_whitelist = '',
+ $port,
+ $server_dir,
+ $keydir,
+ $authdir,
+ $authfile = undef,
+ $use_cache,
+ $user,
+ $method = 'xinetd',
+ $generate_sshkey = false,
+ $sshuser = undef,
+ $hostname = $::fqdn
+) {
+ if $use_cache {
+ $server = "${server_dir}/check_mk_caching_agent"
+ } else {
+ $server = "${server_dir}/check_mk_agent"
+ }
+
+ case $method {
+ 'xinetd': {
+ if $ip_whitelist {
+ $only_from = join($ip_whitelist, ' ')
+ } else {
+ $only_from = undef
+ }
+
+ file { '/etc/xinetd.d/check_mk':
+ ensure => present,
+ owner => 'root',
+ group => 'root',
+ mode => '0444',
+ content => template('check_mk/agent/check_mk.erb'),
+ require => Package['check_mk-agent','check_mk-agent-logwatch'],
+ notify => Class['check_mk::agent::service'],
+ }
+ }
+
+ 'ssh': {
+ if $generate_sshkey {
+ check_mk::agent::generate_sshkey { "check_mk_key_${hostname}":
+ keydir => $keydir,
+ authdir => $authdir,
+ authfile => $authfile,
+ sshuser => $sshuser,
+ hostname => $hostname
+ }
+ }
+
+ # make sure the xinetd method is not configured
+ file { '/etc/xinetd.d/check_mk':
+ ensure => absent;
+ }
+ }
+
+ default : {}
+ }
+}
diff --git a/puppet/modules/check_mk/manifests/agent/generate_sshkey.pp b/puppet/modules/check_mk/manifests/agent/generate_sshkey.pp
new file mode 100644
index 00000000..b00271f5
--- /dev/null
+++ b/puppet/modules/check_mk/manifests/agent/generate_sshkey.pp
@@ -0,0 +1,70 @@
+define check_mk::agent::generate_sshkey (
+ # dir on the check-mk-server where the collected key pairs are stored
+ $keydir,
+ # user/group the key should be owned by on the check-mk-server
+ $keyuser = 'nagios',
+ $keygroup = 'nagios',
+ # dir on the check-mk-agent where the authorized_keys file is stored
+ $authdir,
+ # name of the authorized_keys file
+ $authfile = undef,
+ # dir on the puppetmaster where keys are stored
+ # FIXME: need a way to ensure this dir is setup on the puppetmaster correctly
+ #$ssh_key_basepath = "${common::moduledir::module_dir_path}/check_mk/keys",
+ # for now use a dir we know works
+ $ssh_key_basepath = '/etc/puppet/modules/check_mk/keys',
+ # user on the client the check_mk server will ssh to, to run the agent
+ $sshuser = 'root',
+ $hostname = $::fqdn,
+ $check_mk_tag = 'check_mk_sshkey'
+){
+
+ # generate check-mk ssh keypair, stored on puppetmaster
+ $ssh_key_name = "${hostname}_id_rsa"
+ $ssh_keys = ssh_keygen("${ssh_key_basepath}/${ssh_key_name}")
+ $public = split($ssh_keys[1],' ')
+ $public_type = $public[0]
+ $public_key = $public[1]
+ $secret_key = $ssh_keys[0]
+
+ # if we're not root we need to use sudo
+ if $sshuser != 'root' {
+ $command = 'sudo /usr/bin/check_mk_agent'
+ } else {
+ $command = '/usr/bin/check_mk_agent'
+ }
+
+ # setup the public half of the key in authorized_keys on the agent
+ # and restrict it to running only the agent
+ if $authdir or $authfile {
+ # if $authkey or $authdir are set, override authorized_keys path and file
+ # and also override using the built-in ssh_authorized_key since it may
+ # not be able to write to $authdir
+ sshd::ssh_authorized_key { $ssh_key_name:
+ type => 'ssh-rsa',
+ key => $public_key,
+ user => $sshuser,
+ target => "${authdir}/${authfile}",
+ override_builtin => true,
+ options => "command=\"${command}\"";
+ }
+ } else {
+ # otherwise use the defaults
+ sshd::ssh_authorized_key { $ssh_key_name:
+ type => 'ssh-rsa',
+ key => $public_key,
+ user => $sshuser,
+ options => "command=\"${command}\"";
+ }
+ }
+
+ # resource collector for the private half of the keys, these end up on
+ # the check-mk-server host, and the user running check-mk needs access
+ @@file { "${keydir}/${ssh_key_name}":
+ content => $secret_key,
+ owner => $keyuser,
+ group => $keygroup,
+ mode => '0600',
+ tag => $check_mk_tag;
+ }
+}
diff --git a/puppet/modules/check_mk/manifests/agent/install.pp b/puppet/modules/check_mk/manifests/agent/install.pp
new file mode 100644
index 00000000..5c0b56ef
--- /dev/null
+++ b/puppet/modules/check_mk/manifests/agent/install.pp
@@ -0,0 +1,70 @@
+class check_mk::agent::install (
+ $version = '',
+ $filestore = '',
+ $workspace,
+ $agent_package_name,
+ $agent_logwatch_package_name,
+ $method = 'xinetd',
+) {
+ if $method == 'xinetd' {
+ if ! defined($require_method) {
+ package { 'xinetd':
+ ensure => latest,
+ }
+ }
+ $require_method = 'Package[\'xinetd\']'
+ } else {
+ $require_method = undef
+ }
+
+ if $filestore {
+ if ! defined(File[$workspace]) {
+ file { $workspace:
+ ensure => directory,
+ }
+ }
+ file { "${workspace}/check_mk-agent-${version}.noarch.rpm":
+ ensure => latest,
+ source => "${filestore}/check_mk-agent-${version}.noarch.rpm",
+ require => $require_method,
+ }
+ file { "${workspace}/check_mk-agent-logwatch-${version}.noarch.rpm":
+ ensure => latest,
+ source => "${filestore}/check_mk-agent-logwatch-${version}.noarch.rpm",
+ require => $require_method,
+ }
+ package { 'check_mk-agent':
+ ensure => latest,
+ provider => 'rpm',
+ source => "${workspace}/check_mk-agent-${version}.noarch.rpm",
+ require => File["${workspace}/check_mk-agent-${version}.noarch.rpm"],
+ }
+ package { 'check_mk-agent-logwatch':
+ ensure => latest,
+ provider => 'rpm',
+ source => "${workspace}/check_mk-agent-logwatch-${version}.noarch.rpm",
+ require => [
+ File["${workspace}/check_mk-agent-logwatch-${version}.noarch.rpm"],
+ Package['check_mk-agent'],
+ ],
+ }
+ }
+ else {
+ if $version {
+ $agent_package_version = $version
+ } else {
+ $agent_package_version = latest
+ }
+
+ package { 'check_mk-agent':
+ ensure => $agent_package_version,
+ name => $agent_package_name,
+ require => $require_method,
+ }
+ package { 'check_mk-agent-logwatch':
+ ensure => $agent_package_version,
+ name => $agent_logwatch_package_name,
+ require => Package['check_mk-agent'],
+ }
+ }
+}
diff --git a/puppet/modules/check_mk/manifests/agent/install_local.pp b/puppet/modules/check_mk/manifests/agent/install_local.pp
new file mode 100644
index 00000000..7238440f
--- /dev/null
+++ b/puppet/modules/check_mk/manifests/agent/install_local.pp
@@ -0,0 +1,12 @@
+define check_mk::agent::install_local($source=undef, $content=undef, $ensure='present') {
+ @file { "/usr/lib/check_mk_agent/local/${name}" :
+ ensure => $ensure,
+ owner => 'root',
+ group => 'root',
+ mode => '0755',
+ content => $content,
+ source => $source,
+ tag => 'check_mk::local',
+ require => Package['check-mk-agent'],
+ }
+}
diff --git a/puppet/modules/check_mk/manifests/agent/local_checks.pp b/puppet/modules/check_mk/manifests/agent/local_checks.pp
new file mode 100644
index 00000000..04896b0a
--- /dev/null
+++ b/puppet/modules/check_mk/manifests/agent/local_checks.pp
@@ -0,0 +1,11 @@
+class check_mk::agent::local_checks{
+ file { '/usr/lib/check_mk_agent/local':
+ ensure => directory,
+ source => [
+ 'puppet:///modules/site_check_mk/agent/local_checks/all_hosts',
+ 'puppet:///modules/check_mk/agent/local_checks/all_hosts' ],
+ recurse => true,
+ require => Package['check_mk-agent'],
+ }
+
+}
diff --git a/puppet/modules/check_mk/manifests/agent/mrpe.pp b/puppet/modules/check_mk/manifests/agent/mrpe.pp
new file mode 100644
index 00000000..5bc5f331
--- /dev/null
+++ b/puppet/modules/check_mk/manifests/agent/mrpe.pp
@@ -0,0 +1,19 @@
+class check_mk::agent::mrpe {
+ # check_mk can use standard nagios plugins using
+ # a wrapper called mrpe
+ # see http://mathias-kettner.de/checkmk_mrpe.html
+ # this subclass is provided to be included by checks that use mrpe
+
+ # FIXME: this is Debian specific and should be made more generic
+ if !defined(Package['nagios-plugins-basic']) {
+ package { 'nagios-plugins-basic':
+ ensure => latest,
+ }
+ }
+
+ # ensure the config file exists, individual checks will add lines to it
+ file { '/etc/check_mk/mrpe.cfg':
+ ensure => present,
+ require => Package['check-mk-agent']
+ }
+}
diff --git a/puppet/modules/check_mk/manifests/agent/ps.pp b/puppet/modules/check_mk/manifests/agent/ps.pp
new file mode 100644
index 00000000..67a999f5
--- /dev/null
+++ b/puppet/modules/check_mk/manifests/agent/ps.pp
@@ -0,0 +1,17 @@
+define check_mk::agent::ps (
+ # procname and levels have defaults in check_mk::ps
+ $procname = undef,
+ $levels = undef,
+ # user is optional
+ $user = undef
+) {
+
+ @@check_mk::ps { "${::fqdn}_${name}":
+ desc => $name,
+ host => $::fqdn,
+ procname => $procname,
+ user => $user,
+ levels => $levels,
+ tag => 'check_mk_ps';
+ }
+}
diff --git a/puppet/modules/check_mk/manifests/agent/register.pp b/puppet/modules/check_mk/manifests/agent/register.pp
new file mode 100644
index 00000000..46cdeaee
--- /dev/null
+++ b/puppet/modules/check_mk/manifests/agent/register.pp
@@ -0,0 +1,8 @@
+class check_mk::agent::register (
+ $host_tags = '',
+ $hostname = $::fqdn
+) {
+ @@check_mk::host { $hostname:
+ host_tags => $host_tags,
+ }
+}
diff --git a/puppet/modules/check_mk/manifests/agent/service.pp b/puppet/modules/check_mk/manifests/agent/service.pp
new file mode 100644
index 00000000..0f707082
--- /dev/null
+++ b/puppet/modules/check_mk/manifests/agent/service.pp
@@ -0,0 +1,8 @@
+class check_mk::agent::service {
+ if ! defined(Service['xinetd']) {
+ service { 'xinetd':
+ ensure => 'running',
+ enable => true,
+ }
+ }
+}
diff --git a/puppet/modules/check_mk/manifests/config.pp b/puppet/modules/check_mk/manifests/config.pp
new file mode 100644
index 00000000..fba68361
--- /dev/null
+++ b/puppet/modules/check_mk/manifests/config.pp
@@ -0,0 +1,109 @@
+# Deploy check_mk config
+class check_mk::config (
+ $site,
+ $host_groups = undef,
+ $etc_dir = "/omd/sites/${site}/etc",
+ $nagios_subdir = 'nagios',
+ $bin_dir = "/omd/sites/${site}/bin",
+ $use_storedconfigs = true,
+ $inventory_only_on_changes = true
+) {
+ file {
+ # for local check_mk checks
+ "${etc_dir}/${nagios_subdir}/local":
+ ensure => directory;
+
+ # package provided and check_mk generated files, defined so the nagios
+ # module doesn't purge them
+ "${etc_dir}/${nagios_subdir}/conf.d":
+ ensure => directory;
+ "${etc_dir}/${nagios_subdir}/conf.d/check_mk":
+ ensure => directory;
+ }
+ file_line { 'nagios-add-check_mk-cfg_dir':
+ ensure => present,
+ line => "cfg_dir=${etc_dir}/${nagios_subdir}/local",
+ path => "${etc_dir}/${nagios_subdir}/nagios.cfg",
+ require => File["${etc_dir}/${nagios_subdir}/local"],
+ #notify => Class['check_mk::service'],
+ }
+ file_line { 'add-guest-users':
+ ensure => present,
+ line => 'guest_users = [ "guest" ]',
+ path => "${etc_dir}/check_mk/multisite.mk",
+ #notify => Class['check_mk::service'],
+ }
+ concat { "${etc_dir}/check_mk/main.mk":
+ owner => 'root',
+ group => 'root',
+ mode => '0644',
+ notify => Exec['check_mk-refresh'],
+ }
+ # all_hosts
+ concat::fragment { 'all_hosts-header':
+ target => "${etc_dir}/check_mk/main.mk",
+ content => "all_hosts = [\n",
+ order => 10,
+ }
+ concat::fragment { 'all_hosts-footer':
+ target => "${etc_dir}/check_mk/main.mk",
+ content => "]\n",
+ order => 19,
+ }
+ if ( $use_storedconfigs ) {
+ class { 'check_mk::server::collect_hosts': }
+ class { 'check_mk::server::collect_ps': }
+ }
+
+
+ # local list of hosts is in /omd/sites/${site}/etc/check_mk/all_hosts_static and is appended
+ concat::fragment { 'all-hosts-static':
+ ensure => "${etc_dir}/check_mk/all_hosts_static",
+ target => "${etc_dir}/check_mk/main.mk",
+ order => 18,
+ }
+ # host_groups
+ if $host_groups {
+ file { "${etc_dir}/nagios/local/hostgroups":
+ ensure => directory,
+ }
+ concat::fragment { 'host_groups-header':
+ target => "${etc_dir}/check_mk/main.mk",
+ content => "host_groups = [\n",
+ order => 20,
+ }
+ concat::fragment { 'host_groups-footer':
+ target => "${etc_dir}/check_mk/main.mk",
+ content => "]\n",
+ order => 29,
+ }
+ $groups = keys($host_groups)
+ check_mk::hostgroup { $groups:
+ dir => "${etc_dir}/nagios/local/hostgroups",
+ hostgroups => $host_groups,
+ target => "${etc_dir}/check_mk/main.mk",
+ notify => Exec['check_mk-refresh']
+ }
+ }
+ # local config is in /omd/sites/${site}/etc/check_mk/main.mk.local and is appended
+ concat::fragment { 'check_mk-local-config':
+ ensure => "${etc_dir}/check_mk/main.mk.local",
+ target => "${etc_dir}/check_mk/main.mk",
+ order => 99,
+ }
+ # re-read config if it changes
+ exec { 'check_mk-refresh':
+ command => "/bin/su -l -c '${bin_dir}/check_mk -II' ${site}",
+ refreshonly => $inventory_only_on_changes,
+ notify => Exec['check_mk-reload'],
+ }
+ exec { 'check_mk-reload':
+ command => "/bin/su -l -c '${bin_dir}/check_mk -O' ${site}",
+ refreshonly => $inventory_only_on_changes,
+ }
+ # re-read inventory at least daily
+ exec { 'check_mk-refresh-inventory-daily':
+ command => "/bin/su -l -c '${bin_dir}/check_mk -O' ${site}",
+ schedule => 'daily',
+ }
+}
diff --git a/puppet/modules/check_mk/manifests/host.pp b/puppet/modules/check_mk/manifests/host.pp
new file mode 100644
index 00000000..49f038b5
--- /dev/null
+++ b/puppet/modules/check_mk/manifests/host.pp
@@ -0,0 +1,18 @@
+define check_mk::host (
+ $target,
+ $host_tags = [],
+) {
+ $host = $title
+ if size($host_tags) > 0 {
+ $taglist = join(any2array($host_tags),'|')
+ $entry = "${host}|${taglist}"
+ }
+ else {
+ $entry = $host
+ }
+ concat::fragment { "check_mk-${host}":
+ target => $target,
+ content => " '${entry}',\n",
+ order => 11,
+ }
+}
diff --git a/puppet/modules/check_mk/manifests/hostgroup.pp b/puppet/modules/check_mk/manifests/hostgroup.pp
new file mode 100644
index 00000000..baec45f9
--- /dev/null
+++ b/puppet/modules/check_mk/manifests/hostgroup.pp
@@ -0,0 +1,24 @@
+define check_mk::hostgroup (
+ $dir,
+ $hostgroups,
+ $target,
+) {
+ $group = $title
+ $group_tags = sprintf("'%s'", join($hostgroups[$group]['host_tags'], "', '"))
+ concat::fragment { "check_mk-hostgroup-${group}":
+ target => $target,
+ content => " ( '${group}', [ ${group_tags} ], ALL_HOSTS ),\n",
+ order => 21,
+ }
+ if $hostgroups[$group]['description'] {
+ $description = $hostgroups[$group]['description']
+ }
+ else {
+ $description = regsubst($group, '_', ' ')
+ }
+ file { "${dir}/${group}.cfg":
+ ensure => present,
+ content => "define hostgroup {\n hostgroup_name ${group}\n alias ${description}\n}\n",
+ require => File[$dir],
+ }
+}
diff --git a/puppet/modules/check_mk/manifests/htpasswd.pp b/puppet/modules/check_mk/manifests/htpasswd.pp
new file mode 100644
index 00000000..2bd24cc5
--- /dev/null
+++ b/puppet/modules/check_mk/manifests/htpasswd.pp
@@ -0,0 +1,12 @@
+class check_mk::htpasswd (
+ $password,
+ $username = 'omdadmin',
+ $path = '/opt/omd/sites/monitoring/etc/htpasswd' ) {
+
+ apache::htpasswd_user { $username:
+ ensure => present,
+ username => $username,
+ password => $password,
+ path => $path
+ }
+}
diff --git a/puppet/modules/check_mk/manifests/init.pp b/puppet/modules/check_mk/manifests/init.pp
new file mode 100644
index 00000000..4aab837d
--- /dev/null
+++ b/puppet/modules/check_mk/manifests/init.pp
@@ -0,0 +1,44 @@
+# configure check_mk server
+class check_mk (
+ $filestore = undef,
+ $host_groups = undef,
+ $package = 'omd-0.56',
+ $site = 'monitoring',
+ $workspace = '/root/check_mk',
+ $omd_service_name = 'omd',
+ $http_service_name = 'httpd',
+ $xinitd_service_name = 'xinetd',
+ $omdadmin_htpasswd = undef,
+ $use_ssh = false,
+ $shelluser = 'monitoring',
+ $shellgroup = 'monitoring',
+ $use_storedconfigs = true,
+ $inventory_only_on_changes = true) {
+
+ class { 'check_mk::install':
+ filestore => $filestore,
+ package => $package,
+ site => $site,
+ workspace => $workspace,
+ }
+ class { 'check_mk::config':
+ host_groups => $host_groups,
+ site => $site,
+ use_storedconfigs => $use_storedconfigs,
+ inventory_only_on_changes => $inventory_only_on_changes,
+ require => Class['check_mk::install'],
+ }
+ class { 'check_mk::service':
+ require => Class['check_mk::config'],
+ }
+ if $omdadmin_htpasswd {
+ class { 'check_mk::htpasswd':
+ password => $omdadmin_htpasswd
+ }
+ }
+
+ if ( $use_ssh == true ) {
+ class { 'check_mk::server::configure_ssh': }
+ }
+
+}
diff --git a/puppet/modules/check_mk/manifests/install.pp b/puppet/modules/check_mk/manifests/install.pp
new file mode 100644
index 00000000..5f8a4a0d
--- /dev/null
+++ b/puppet/modules/check_mk/manifests/install.pp
@@ -0,0 +1,50 @@
+class check_mk::install (
+ $filestore = '',
+ $version = '',
+ $package,
+ $site,
+ $workspace,
+) {
+ if $filestore {
+ if ! defined(File[$workspace]) {
+ file { $workspace:
+ ensure => directory,
+ }
+ }
+ file { "${workspace}/${package}":
+ ensure => latest,
+ source => "${filestore}/${package}",
+ require => File[$workspace],
+ }
+ # omd-0.56-rh60-29.x86_64.rpm
+ if $package =~ /^(omd-\d+\.\d+)-(.*?)\.(rpm|deb)$/ {
+ $package_name = $1
+ $type = $3
+ package { $package_name:
+ ensure => installed,
+ provider => $type,
+ source => "${workspace}/${package}",
+ require => File["${workspace}/${package}"],
+ }
+ }
+ }
+ else {
+ $package_name = $package
+
+ if $version {
+ $server_package_version = $version
+ } else {
+ $server_package_version = latest
+ }
+
+ package { $package_name:
+ ensure => $server_package_version,
+ }
+ }
+ $etc_dir = "/omd/sites/${site}/etc"
+ exec { 'omd-create-site':
+ command => "/usr/bin/omd create ${site}",
+ creates => $etc_dir,
+ require => Package[$package_name],
+ }
+}
diff --git a/puppet/modules/check_mk/manifests/install_tarball.pp b/puppet/modules/check_mk/manifests/install_tarball.pp
new file mode 100644
index 00000000..af40a267
--- /dev/null
+++ b/puppet/modules/check_mk/manifests/install_tarball.pp
@@ -0,0 +1,92 @@
+class check_mk::install_tarball (
+ $filestore,
+ $version,
+ $workspace,
+) {
+ package { 'nagios':
+ ensure => present,
+ notify => Exec['set-nagiosadmin-password', 'set-guest-password', 'add-apache-to-nagios-group'],
+ }
+ file { '/etc/nagios/passwd':
+ ensure => present,
+ owner => 'root',
+ group => 'apache',
+ mode => '0640',
+ }
+ exec { 'set-nagiosadmin-password':
+ command => '/usr/bin/htpasswd -b /etc/nagios/passwd nagiosadmin letmein',
+ refreshonly => true,
+ require => File['/etc/nagios/passwd'],
+ }
+ exec { 'set-guest-password':
+ command => '/usr/bin/htpasswd -b /etc/nagios/passwd guest guest',
+ refreshonly => true,
+ require => File['/etc/nagios/passwd'],
+ }
+ exec { 'add-apache-to-nagios-group':
+ command => '/usr/sbin/usermod -a -G nagios apache',
+ refreshonly => true,
+ }
+ package { 'nagios-plugins-all':
+ ensure => present,
+ require => Package['nagios'],
+ }
+ # FIXME: this should get and check $use_ssh before requiring xinetd
+ package { [ 'xinetd', 'mod_python', 'make', 'gcc-c++', 'tar', 'gzip' ]:
+ ensure => present,
+ }
+ file { "${workspace}/check_mk-${version}.tar.gz":
+ ensure => present,
+ source => "${filestore}/check_mk-${version}.tar.gz",
+ }
+ exec { 'unpack-check_mk-tarball':
+ command => "/bin/tar -zxf ${workspace}/check_mk-${version}.tar.gz",
+ cwd => $workspace,
+ creates => "${workspace}/check_mk-${version}",
+ require => File["${workspace}/check_mk-${version}.tar.gz"],
+ }
+ exec { 'change-setup-config-location':
+ command => "/usr/bin/perl -pi -e 's#^SETUPCONF=.*?$#SETUPCONF=${workspace}/check_mk_setup.conf#' ${workspace}/check_mk-${version}/setup.sh",
+ unless => "/bin/egrep '^SETUPCONF=${workspace}/check_mk_setup.conf$' ${workspace}/check_mk-${version}/setup.sh",
+ require => Exec['unpack-check_mk-tarball'],
+ }
+ # Avoid header like 'Written by setup of check_mk 1.2.0p3 at Thu Feb 7 12:26:17 GMT 2013'
+ # that changes every time the setup script is run
+ exec { 'remove-setup-header':
+ command => "/usr/bin/perl -pi -e 's#^DIRINFO=.*?$#DIRINFO=#' ${workspace}/check_mk-${version}/setup.sh",
+ unless => "/bin/egrep '^DIRINFO=$' ${workspace}/check_mk-${version}/setup.sh",
+ require => Exec['unpack-check_mk-tarball'],
+ }
+ file { "${workspace}/check_mk_setup.conf":
+ ensure => present,
+ content => template('check_mk/setup.conf.erb'),
+ notify => Exec['check_mk-setup'],
+ }
+ file { '/etc/nagios/check_mk':
+ ensure => directory,
+ owner => 'nagios',
+ group => 'nagios',
+ recurse => true,
+ require => Package['nagios'],
+ }
+ file { '/etc/nagios/check_mk/hostgroups':
+ ensure => directory,
+ owner => 'nagios',
+ group => 'nagios',
+ require => File['/etc/nagios/check_mk'],
+ }
+ exec { 'check_mk-setup':
+ command => "${workspace}/check_mk-${version}/setup.sh --yes",
+ cwd => "${workspace}/check_mk-${version}",
+ refreshonly => true,
+ require => [
+ Exec['change-setup-config-location'],
+ Exec['remove-setup-header'],
+ Exec['unpack-check_mk-tarball'],
+ File["${workspace}/check_mk_setup.conf"],
+ File['/etc/nagios/check_mk'],
+ Package['nagios'],
+ ],
+ notify => Class['check_mk::service'],
+ }
+}
diff --git a/puppet/modules/check_mk/manifests/omd_repo.pp b/puppet/modules/check_mk/manifests/omd_repo.pp
new file mode 100644
index 00000000..2100f378
--- /dev/null
+++ b/puppet/modules/check_mk/manifests/omd_repo.pp
@@ -0,0 +1,6 @@
+class check_mk::omd_repo {
+ apt::sources_list { 'omd.list':
+ content => "deb http://labs.consol.de/OMD/debian ${::lsbdistcodename} main",
+ before => Package['omd']
+ }
+}
diff --git a/puppet/modules/check_mk/manifests/ps.pp b/puppet/modules/check_mk/manifests/ps.pp
new file mode 100644
index 00000000..1171a135
--- /dev/null
+++ b/puppet/modules/check_mk/manifests/ps.pp
@@ -0,0 +1,34 @@
+define check_mk::ps (
+ $target,
+ $host,
+ $desc,
+ $procname = "/usr/sbin/${desc}",
+ $levels = '1, 1, 1, 1',
+ $user = undef
+) {
+ # This class is called on check-mk agent machines in order to create
+ # checks using the built-in ps check type. They create stored configs
+ # and then the check_mk::server::collect_ps class on the server
+ # generates the config file to set them up
+
+ # lines in the ps.mk config file look like
+ # ( "foo.example.com", "ps", "NAME", ( "/usr/sbin/foo", 1, 1, 1, 1 ) )
+ # or with a user
+ # ( "foo.example.com", "ps", "NAME", ( "/usr/sbin/foo", "user", 1, 1, 1, 1 ) )
+ if $user {
+ $check = " ( \"${host}\", \"ps\", \"${desc}\", ( \"${procname}\", ${user}, ${levels} ) ),\n"
+ } else {
+ $check = " ( \"${host}\", \"ps\", \"${desc}\", ( \"${procname}\", ${levels} ) ),\n"
+ }
+
+ # FIXME: we could be smarter about this and consolidate host checks
+ # that have identical settings and that would make the config file
+ # make more sense for humans. but for now we'll just do separate
+ # lines (which may result in a very large file, but check-mk is fine)
+ concat::fragment { "check_mk_ps-${host}_${desc}":
+ target => $target,
+ content => $check,
+ order => 20
+ }
+}
+
diff --git a/puppet/modules/check_mk/manifests/server/collect_hosts.pp b/puppet/modules/check_mk/manifests/server/collect_hosts.pp
new file mode 100644
index 00000000..6d07897b
--- /dev/null
+++ b/puppet/modules/check_mk/manifests/server/collect_hosts.pp
@@ -0,0 +1,6 @@
+class check_mk::server::collect_hosts {
+ Check_mk::Host <<| |>> {
+ target => "${::check_mk::config::etc_dir}/check_mk/main.mk",
+ notify => Exec['check_mk-refresh']
+ }
+}
diff --git a/puppet/modules/check_mk/manifests/server/collect_ps.pp b/puppet/modules/check_mk/manifests/server/collect_ps.pp
new file mode 100644
index 00000000..067a25c9
--- /dev/null
+++ b/puppet/modules/check_mk/manifests/server/collect_ps.pp
@@ -0,0 +1,30 @@
+class check_mk::server::collect_ps (
+ $config = "${::check_mk::config::etc_dir}/check_mk/conf.d/ps.mk"
+) {
+
+ # this class gets run on the check-mk server in order to collect the
+ # stored configs created on clients and assemble the ps.mk config file
+ concat { $config:
+ owner => 'root',
+ group => 'root',
+ mode => '0644',
+ notify => Exec['check_mk-refresh'],
+ }
+
+ concat::fragment{'check_mk_ps_header':
+ target => $config,
+ content => "checks += [\n",
+ order => 10,
+ }
+
+ Check_mk::Ps <<| tag == 'check_mk_ps' |>> {
+ target => $config,
+ notify => Exec['check_mk-refresh']
+ }
+
+ concat::fragment{'check_mk_ps_footer':
+ target => $config,
+ content => "]\n",
+ order => 90,
+ }
+}
diff --git a/puppet/modules/check_mk/manifests/server/configure_ssh.pp b/puppet/modules/check_mk/manifests/server/configure_ssh.pp
new file mode 100644
index 00000000..987cc7af
--- /dev/null
+++ b/puppet/modules/check_mk/manifests/server/configure_ssh.pp
@@ -0,0 +1,16 @@
+class check_mk::server::configure_ssh (
+ $check_mk_tag = 'check_mk_sshkey'
+) {
+ # collect exported files from client::generate_sshkey
+ File <<| tag == $check_mk_tag |>>
+
+ # configure ssh access to agents which have 'ssh' tags
+ file { "${check_mk::config::etc_dir}/check_mk/conf.d/use_ssh.mk":
+ source => [ 'puppet:///modules/site_check_mk/use_ssh.mk',
+ 'puppet:///modules/check_mk/use_ssh.mk' ],
+ owner => $::check_mk::shelluser,
+ group => $::check_mk::shellgroup,
+ mode => '0644',
+ notify => Exec['check_mk-refresh']
+ }
+}
diff --git a/puppet/modules/check_mk/manifests/service.pp b/puppet/modules/check_mk/manifests/service.pp
new file mode 100644
index 00000000..36fb2d16
--- /dev/null
+++ b/puppet/modules/check_mk/manifests/service.pp
@@ -0,0 +1,23 @@
+class check_mk::service {
+
+ if ! defined(Service[$check_mk::http_service_name]) {
+ service { $check_mk::http_service_name:
+ ensure => 'running',
+ enable => true,
+ }
+ }
+ # FIXME: this should get and check $use_ssh before doing this
+ if ! defined(Service[xinetd]) {
+ service { 'xinetd':
+ ensure => 'running',
+ name => $check_mk::xinitd_service_name,
+ hasstatus => false,
+ enable => true,
+ }
+ }
+ service { 'omd':
+ ensure => 'running',
+ name => $check_mk::omd_service_name,
+ enable => true,
+ }
+}
diff --git a/puppet/modules/check_mk/templates/agent/check_mk.erb b/puppet/modules/check_mk/templates/agent/check_mk.erb
new file mode 100644
index 00000000..47824a9f
--- /dev/null
+++ b/puppet/modules/check_mk/templates/agent/check_mk.erb
@@ -0,0 +1,39 @@
+# +------------------------------------------------------------------+
+# | ____ _ _ __ __ _ __ |
+# | / ___| |__ ___ ___| | __ | \/ | |/ / |
+# | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
+# | | |___| | | | __/ (__| < | | | | . \ |
+# | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
+# | |
+# | Copyright Mathias Kettner 2012 mk@mathias-kettner.de |
+# +------------------------------------------------------------------+
+#
+# This file is part of Check_MK.
+# The official homepage is at http://mathias-kettner.de/check_mk.
+#
+# check_mk is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation in version 2. check_mk is distributed
+# in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
+# out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. See the GNU General Public License for more de-
+# ails. You should have received a copy of the GNU General Public
+# License along with GNU Make; see the file COPYING. If not, write
+# to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+# Boston, MA 02110-1301 USA.
+
+service check_mk
+{
+ type = UNLISTED
+ port = <%= @port %>
+ socket_type = stream
+ protocol = tcp
+ wait = no
+ user = <%= @user %>
+ server = <%= @server %>
+<% if @only_from -%>
+ only_from = 127.0.0.1 <%= @only_from %>
+<% end -%>
+ log_on_success =
+ disable = no
+}
diff --git a/puppet/modules/check_mk/templates/main.mk.erb b/puppet/modules/check_mk/templates/main.mk.erb
new file mode 100644
index 00000000..e1fbe81c
--- /dev/null
+++ b/puppet/modules/check_mk/templates/main.mk.erb
@@ -0,0 +1,4 @@
+all_hosts = [
+ 'lnxmgt-01.sbetenv.ads',
+ 'lnxmgt-02.sbetenv.ads',
+]
diff --git a/puppet/modules/check_mk/templates/setup.conf.erb b/puppet/modules/check_mk/templates/setup.conf.erb
new file mode 100644
index 00000000..41e1143b
--- /dev/null
+++ b/puppet/modules/check_mk/templates/setup.conf.erb
@@ -0,0 +1,29 @@
+
+bindir='/usr/bin'
+confdir='/etc/check_mk'
+sharedir='/usr/share/check_mk'
+docdir='/usr/share/doc/check_mk'
+checkmandir='/usr/share/doc/check_mk/checks'
+vardir='/var/lib/check_mk'
+agentslibdir='/usr/lib/check_mk_agent'
+agentsconfdir='/etc/check_mk'
+nagiosuser='nagios'
+wwwuser='apache'
+wwwgroup='nagios'
+nagios_binary='/usr/sbin/nagios'
+nagios_config_file='/etc/nagios/nagios.cfg'
+nagconfdir='/etc/nagios/check_mk'
+nagios_startscript='/etc/init.d/nagios'
+nagpipe='/var/spool/nagios/cmd/nagios.cmd'
+check_result_path='/var/log/nagios/spool/checkresults'
+nagios_status_file='/var/log/nagios/status.dat'
+check_icmp_path='/usr/lib64/nagios/plugins/check_icmp'
+url_prefix='/'
+apache_config_dir='/etc/httpd/conf.d'
+htpasswd_file='/etc/nagios/passwd'
+nagios_auth_name='Nagios Access'
+pnptemplates='/usr/share/nagios/html/pnp4nagios/templates'
+enable_livestatus='yes'
+libdir='/usr/lib/check_mk'
+livesock='/var/spool/nagios/cmd/live'
+livebackendsdir='/usr/share/check_mk/livestatus'
diff --git a/puppet/modules/clamav/files/01-leap.conf b/puppet/modules/clamav/files/01-leap.conf
new file mode 100644
index 00000000..a7e49d17
--- /dev/null
+++ b/puppet/modules/clamav/files/01-leap.conf
@@ -0,0 +1,58 @@
+# If running clamd in "LocalSocket" mode (*NOT* in TCP/IP mode), and
+# either "SOcket Cat" (socat) or the "IO::Socket::UNIX" perl module
+# are installed on the system, and you want to report whether clamd
+# is running or not, uncomment the "clamd_socket" variable below (you
+# will be warned if neither socat nor IO::Socket::UNIX are found, but
+# the script will still run). You will also need to set the correct
+# path to your clamd socket file (if unsure of the path, check the
+# "LocalSocket" setting in your clamd.conf file for socket location).
+clamd_socket="/run/clamav/clamd.ctl"
+
+# If you would like to attempt to restart ClamD if detected not running,
+# uncomment the next 2 lines. Confirm the path to the "clamd_lock" file
+# (usually can be found in the clamd init script) and also enter the clamd
+# start command for your particular distro for the "start_clamd" variable
+# (the sample start command shown below should work for most linux distros).
+# NOTE: these 2 variables are dependant on the "clamd_socket" variable
+# shown above - if not enabled, then the following 2 variables will be
+# ignored, whether enabled or not.
+clamd_lock="/run/clamav/clamd.pid"
+start_clamd="clamdscan --reload"
+
+ss_dbs="
+ junk.ndb
+ phish.ndb
+ rogue.hdb
+ sanesecurity.ftm
+ scam.ndb
+ sigwhitelist.ign2
+ spamattach.hdb
+ spamimg.hdb
+ winnow.attachments.hdb
+ winnow_bad_cw.hdb
+ winnow_extended_malware.hdb
+ winnow_malware.hdb
+ winnow_malware_links.ndb
+ malwarehash.hsb
+ doppelstern.hdb
+ bofhland_cracked_URL.ndb
+ bofhland_malware_attach.hdb
+ bofhland_malware_URL.ndb
+ bofhland_phishing_URL.ndb
+ crdfam.clamav.hdb
+ phishtank.ndb
+ porcupine.ndb
+ spear.ndb
+ spearl.ndb
+"
+
+# ========================
+# SecuriteInfo Database(s)
+# ========================
+# Add or remove database file names between quote marks as needed. To
+# disable any SecuriteInfo database downloads, remove the appropriate
+# lines below. To disable all SecuriteInfo database file downloads,
+# comment all of the following lines.
+si_dbs=""
+
+mbl_dbs="" \ No newline at end of file
diff --git a/puppet/modules/clamav/files/clamav-daemon_default b/puppet/modules/clamav/files/clamav-daemon_default
new file mode 100644
index 00000000..b4cd6a4f
--- /dev/null
+++ b/puppet/modules/clamav/files/clamav-daemon_default
@@ -0,0 +1,8 @@
+# This is a file designed only t0 set special environment variables
+# eg TMP or TMPDIR. It is sourced from a shell script, so anything
+# put in here must be in variable=value format, suitable for sourcing
+# from a shell script.
+# Examples:
+# export TMPDIR=/dev/shm
+export TMP=/var/tmp
+export TMPDIR=/var/tmp
diff --git a/puppet/modules/clamav/files/clamav-milter_default b/puppet/modules/clamav/files/clamav-milter_default
new file mode 100644
index 00000000..5e33e822
--- /dev/null
+++ b/puppet/modules/clamav/files/clamav-milter_default
@@ -0,0 +1,14 @@
+#
+# clamav-milter init options
+#
+
+## SOCKET_RWGROUP
+# by default, the socket created by the milter has permissions
+# clamav:clamav:755. SOCKET_RWGROUP changes the group and changes the
+# permissions to 775 to give read-write access to that group.
+#
+# If you are using postfix to speak to the milter, you have to give permission
+# to the postfix group to write
+#
+SOCKET_RWGROUP=postfix
+export TMPDIR=/var/tmp
diff --git a/puppet/modules/clamav/manifests/daemon.pp b/puppet/modules/clamav/manifests/daemon.pp
new file mode 100644
index 00000000..2e13a8fb
--- /dev/null
+++ b/puppet/modules/clamav/manifests/daemon.pp
@@ -0,0 +1,91 @@
+# deploy clamav daemon
+class clamav::daemon {
+
+ $domain_hash = hiera('domain')
+ $domain = $domain_hash['full_suffix']
+
+ package { [ 'clamav-daemon', 'arj' ]:
+ ensure => installed;
+ }
+
+ service {
+ 'clamav-daemon':
+ ensure => running,
+ name => clamav-daemon,
+ pattern => '/usr/sbin/clamd',
+ enable => true,
+ hasrestart => true,
+ subscribe => File['/etc/default/clamav-daemon'],
+ require => Package['clamav-daemon'];
+ }
+
+ file {
+ '/var/run/clamav':
+ ensure => directory,
+ mode => '0750',
+ owner => clamav,
+ group => postfix,
+ require => [Package['postfix'], Package['clamav-daemon']];
+
+ '/var/lib/clamav':
+ mode => '0755',
+ owner => clamav,
+ group => clamav,
+ require => Package['clamav-daemon'];
+
+ '/etc/default/clamav-daemon':
+ source => 'puppet:///modules/clamav/clamav-daemon_default',
+ mode => '0644',
+ owner => root,
+ group => root;
+
+ # this file contains additional domains that we want the clamav
+ # phishing process to look for (our domain)
+ '/var/lib/clamav/local.pdb':
+ content => template('clamav/local.pdb.erb'),
+ mode => '0644',
+ owner => clamav,
+ group => clamav,
+ require => Package['clamav-daemon'];
+ }
+
+ file_line {
+ 'clamav_daemon_tmp':
+ path => '/etc/clamav/clamd.conf',
+ line => 'TemporaryDirectory /var/tmp',
+ require => Package['clamav-daemon'],
+ notify => Service['clamav-daemon'];
+
+ 'enable_phishscanurls':
+ path => '/etc/clamav/clamd.conf',
+ match => 'PhishingScanURLs no',
+ line => 'PhishingScanURLs yes',
+ require => Package['clamav-daemon'],
+ notify => Service['clamav-daemon'];
+
+ 'clamav_LogSyslog_true':
+ path => '/etc/clamav/clamd.conf',
+ match => '^LogSyslog false',
+ line => 'LogSyslog true',
+ require => Package['clamav-daemon'],
+ notify => Service['clamav-daemon'];
+
+ 'clamav_MaxThreads':
+ path => '/etc/clamav/clamd.conf',
+ match => 'MaxThreads 20',
+ line => 'MaxThreads 100',
+ require => Package['clamav-daemon'],
+ notify => Service['clamav-daemon'];
+ }
+
+ # remove LogFile line
+ file_line {
+ 'clamav_LogFile':
+ path => '/etc/clamav/clamd.conf',
+ match => '^LogFile .*',
+ line => '',
+ require => Package['clamav-daemon'],
+ notify => Service['clamav-daemon'];
+ }
+
+}
diff --git a/puppet/modules/clamav/manifests/freshclam.pp b/puppet/modules/clamav/manifests/freshclam.pp
new file mode 100644
index 00000000..80c822a4
--- /dev/null
+++ b/puppet/modules/clamav/manifests/freshclam.pp
@@ -0,0 +1,23 @@
+class clamav::freshclam {
+
+ package { 'clamav-freshclam': ensure => installed }
+
+ service {
+ 'freshclam':
+ ensure => running,
+ enable => true,
+ name => clamav-freshclam,
+ pattern => '/usr/bin/freshclam',
+ hasrestart => true,
+ require => Package['clamav-freshclam'];
+ }
+
+ file_line {
+ 'freshclam_notify':
+ path => '/etc/clamav/freshclam.conf',
+ line => 'NotifyClamd /etc/clamav/clamd.conf',
+ require => Package['clamav-freshclam'],
+ notify => Service['freshclam'];
+ }
+
+}
diff --git a/puppet/modules/clamav/manifests/init.pp b/puppet/modules/clamav/manifests/init.pp
new file mode 100644
index 00000000..de8fb4dc
--- /dev/null
+++ b/puppet/modules/clamav/manifests/init.pp
@@ -0,0 +1,8 @@
+class clamav {
+
+ include clamav::daemon
+ include clamav::milter
+ include clamav::unofficial_sigs
+ include clamav::freshclam
+
+}
diff --git a/puppet/modules/clamav/manifests/milter.pp b/puppet/modules/clamav/manifests/milter.pp
new file mode 100644
index 00000000..e8a85e3f
--- /dev/null
+++ b/puppet/modules/clamav/manifests/milter.pp
@@ -0,0 +1,50 @@
+class clamav::milter {
+
+ $clamav = hiera('clamav')
+ $whitelisted_addresses = $clamav['whitelisted_addresses']
+ $domain_hash = hiera('domain')
+ $domain = $domain_hash['full_suffix']
+
+ package { 'clamav-milter': ensure => installed }
+
+ service {
+ 'clamav-milter':
+ ensure => running,
+ enable => true,
+ name => clamav-milter,
+ pattern => '/usr/sbin/clamav-milter',
+ hasrestart => true,
+ require => Package['clamav-milter'],
+ subscribe => File['/etc/default/clamav-milter'];
+ }
+
+ file {
+ '/run/clamav/milter.ctl':
+ mode => '0666',
+ owner => clamav,
+ group => postfix,
+ require => Class['clamav::daemon'];
+
+ '/etc/clamav/clamav-milter.conf':
+ content => template('clamav/clamav-milter.conf.erb'),
+ mode => '0644',
+ owner => root,
+ group => root,
+ require => Package['clamav-milter'],
+ subscribe => Service['clamav-milter'];
+
+ '/etc/default/clamav-milter':
+ source => 'puppet:///modules/clamav/clamav-milter_default',
+ mode => '0644',
+ owner => root,
+ group => root;
+
+ '/etc/clamav/whitelisted_addresses':
+ content => template('clamav/whitelisted_addresses.erb'),
+ mode => '0644',
+ owner => root,
+ group => root,
+ require => Package['clamav-milter'];
+ }
+
+}
diff --git a/puppet/modules/clamav/manifests/unofficial_sigs.pp b/puppet/modules/clamav/manifests/unofficial_sigs.pp
new file mode 100644
index 00000000..2d849585
--- /dev/null
+++ b/puppet/modules/clamav/manifests/unofficial_sigs.pp
@@ -0,0 +1,23 @@
+class clamav::unofficial_sigs {
+
+ package { 'clamav-unofficial-sigs':
+ ensure => installed
+ }
+
+ ensure_packages(['wget', 'gnupg', 'socat', 'rsync', 'curl'])
+
+ file {
+ '/var/log/clamav-unofficial-sigs.log':
+ ensure => file,
+ owner => clamav,
+ group => clamav,
+ require => Package['clamav-unofficial-sigs'];
+
+ '/etc/clamav-unofficial-sigs.conf.d/01-leap.conf':
+ source => 'puppet:///modules/clamav/01-leap.conf',
+ mode => '0755',
+ owner => root,
+ group => root,
+ require => Package['clamav-unofficial-sigs'];
+ }
+}
diff --git a/puppet/modules/clamav/templates/clamav-milter.conf.erb b/puppet/modules/clamav/templates/clamav-milter.conf.erb
new file mode 100644
index 00000000..9bf7099e
--- /dev/null
+++ b/puppet/modules/clamav/templates/clamav-milter.conf.erb
@@ -0,0 +1,28 @@
+# THIS FILE MANAGED BY PUPPET
+MilterSocket /var/run/clamav/milter.ctl
+FixStaleSocket true
+User clamav
+MilterSocketGroup clamav
+MilterSocketMode 666
+AllowSupplementaryGroups true
+ReadTimeout 120
+Foreground false
+PidFile /var/run/clamav/clamav-milter.pid
+ClamdSocket unix:/var/run/clamav/clamd.ctl
+OnClean Accept
+OnInfected Reject
+OnFail Defer
+AddHeader Replace
+LogSyslog true
+LogFacility LOG_LOCAL6
+LogVerbose yes
+LogInfected Basic
+LogTime true
+LogFileUnlock false
+LogClean Off
+LogRotate true
+SupportMultipleRecipients false
+MaxFileSize 10M
+TemporaryDirectory /var/tmp
+RejectMsg "Message refused due to content violation: %v - contact https://<%= @domain %>/tickets/new if this is in error"
+Whitelist /etc/clamav/whitelisted_addresses
diff --git a/puppet/modules/clamav/templates/local.pdb.erb b/puppet/modules/clamav/templates/local.pdb.erb
new file mode 100644
index 00000000..9ea0584a
--- /dev/null
+++ b/puppet/modules/clamav/templates/local.pdb.erb
@@ -0,0 +1 @@
+H:<%= @domain %>
diff --git a/puppet/modules/clamav/templates/whitelisted_addresses.erb b/puppet/modules/clamav/templates/whitelisted_addresses.erb
new file mode 100644
index 00000000..9e068ec5
--- /dev/null
+++ b/puppet/modules/clamav/templates/whitelisted_addresses.erb
@@ -0,0 +1,5 @@
+<%- if @whitelisted_addresses then -%>
+<% @whitelisted_addresses.each do |name| -%>
+From::<%= name %>
+<% end -%>
+<% end -%>
diff --git a/puppet/modules/common/LICENSE b/puppet/modules/common/LICENSE
new file mode 100644
index 00000000..94a9ed02
--- /dev/null
+++ b/puppet/modules/common/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/puppet/modules/common/README b/puppet/modules/common/README
new file mode 100644
index 00000000..e6df7663
--- /dev/null
+++ b/puppet/modules/common/README
@@ -0,0 +1,44 @@
+Common Module
+-------------
+
+The common module installs various functions that are required by other
+modules. This module should be installed before any of the other module.
+
+! Upgrade Notice !
+
+The 'append_if_no_such_line' define has been replaced with the 'line' define. If
+you are using 'append_if_no_such_line' anywhere in your manifests, you will need
+to transition to 'line' before upgrading to this version of the common
+module. The 'line' define is a drop-in replacement and essentially equivalent,
+so the transition is quite easy, you should only simply need to change the name
+in your manifests.
+
+To use this module, follow these directions:
+
+1. Your modules directory will need all the files included in this
+ repository placed under a directory called "common"
+
+2. Add the following line to manifests/site.pp:
+
+ import "modules.pp"
+
+3. Add the following line to manifests/modules.pp:
+
+ import "common"
+
+
+Original author: David Schmitt (mailto:david@dasz.at)
+Copyright:: Copyright (c) 2007-2009 dasz.at OG
+License:: 3-clause BSD
+
+Additional authors:
+Copyright (C) 2007 David Schmitt <david@schmitt.edv-bus.at>
+Copyright 2008-2011, admin(at)immerda.ch
+Copyright 2008, Puzzle ITC GmbH
+ Marcel Härry haerry+puppet(at)puzzle.ch
+ Simon Josi josi+puppet(at)puzzle.ch
+Copyright 2009-2011, Riseup Labs <http://riseuplabs.org>
+ Pietro Ferrari <pietro@riseup.net>
+ Micah Anderson <micah@riseup.net>
+Copyright (C) 2007 Antoine Beaupre <anarcat@koumbit.org>
+Copyright (c) 2011 intrigeri - intrigeri(at)boum.org \ No newline at end of file
diff --git a/puppet/modules/common/lib/puppet/parser/functions/basename.rb b/puppet/modules/common/lib/puppet/parser/functions/basename.rb
new file mode 100644
index 00000000..dc725375
--- /dev/null
+++ b/puppet/modules/common/lib/puppet/parser/functions/basename.rb
@@ -0,0 +1,22 @@
+# This function has two modes of operation:
+#
+# basename(string) : string
+#
+# Returns the last component of the filename given as argument, which must be
+# formed using forward slashes ("/") regardless of the separator used on the
+# local file system.
+#
+# basename(string[]) : string[]
+#
+# Returns an array of strings with the basename of each item from the argument.
+#
+module Puppet::Parser::Functions
+ newfunction(:basename, :type => :rvalue) do |args|
+ if args[0].is_a?(Array)
+ args.collect do |a| File.basename(a) end
+ else
+ File.basename(args[0])
+ end
+ end
+end
+
diff --git a/puppet/modules/common/lib/puppet/parser/functions/dirname.rb b/puppet/modules/common/lib/puppet/parser/functions/dirname.rb
new file mode 100644
index 00000000..ea0d50b4
--- /dev/null
+++ b/puppet/modules/common/lib/puppet/parser/functions/dirname.rb
@@ -0,0 +1,22 @@
+# This function has two modes of operation:
+#
+# dirname(string) : string
+#
+# Returns all components of the filename given as argument except the last
+# one. The filename must be formed using forward slashes (``/..) regardless of
+# the separator used on the local file system.
+#
+# dirname(string[]) : string[]
+#
+# Returns an array of strings with the basename of each item from the argument.
+#
+module Puppet::Parser::Functions
+ newfunction(:dirname, :type => :rvalue) do |args|
+ if args[0].is_a?(Array)
+ args.collect do |a| File.dirname(a) end
+ else
+ File.dirname(args[0])
+ end
+ end
+end
+
diff --git a/puppet/modules/common/lib/puppet/parser/functions/get_default.rb b/puppet/modules/common/lib/puppet/parser/functions/get_default.rb
new file mode 100644
index 00000000..3f4359bd
--- /dev/null
+++ b/puppet/modules/common/lib/puppet/parser/functions/get_default.rb
@@ -0,0 +1,15 @@
+# get_default($value, $default) : $value
+#
+# return $value || $default.
+module Puppet::Parser::Functions
+ newfunction(:get_default, :type => :rvalue) do |args|
+ value = nil
+ args.each { |x|
+ if ! x.nil? and x.length > 0
+ value = x
+ break
+ end
+ }
+ return value
+ end
+end
diff --git a/puppet/modules/common/lib/puppet/parser/functions/hostname.rb b/puppet/modules/common/lib/puppet/parser/functions/hostname.rb
new file mode 100644
index 00000000..7bc477f2
--- /dev/null
+++ b/puppet/modules/common/lib/puppet/parser/functions/hostname.rb
@@ -0,0 +1,13 @@
+# get an uniq array of ipaddresses for a hostname
+require 'resolv'
+
+module Puppet::Parser::Functions
+ newfunction(:hostname, :type => :rvalue) do |args|
+ res = Array.new
+ Resolv::DNS.new.each_address(args[0]){ |addr|
+ res << addr
+ }
+ res.uniq
+ end
+end
+
diff --git a/puppet/modules/common/lib/puppet/parser/functions/multi_source_template.rb b/puppet/modules/common/lib/puppet/parser/functions/multi_source_template.rb
new file mode 100644
index 00000000..e0753205
--- /dev/null
+++ b/puppet/modules/common/lib/puppet/parser/functions/multi_source_template.rb
@@ -0,0 +1,29 @@
+module Puppet::Parser::Functions
+ require 'erb'
+
+ newfunction(:multi_source_template, :type => :rvalue) do |args|
+ contents = nil
+ environment = compiler.environment
+ sources = args
+
+ sources.each do |file|
+ Puppet.debug("Looking for #{file} in #{environment}")
+ if filename = Puppet::Parser::Files.find_template(file, environment.to_s)
+ wrapper = Puppet::Parser::TemplateWrapper.new(self)
+ wrapper.file = file
+
+ begin
+ contents = wrapper.result
+ rescue => detail
+ raise Puppet::ParseError, "Failed to parse template %s: %s" % [file, detail]
+ end
+
+ break
+ end
+ end
+
+ raise Puppet::ParseError, "multi_source_template: No match found for files: #{sources.join(', ')}" if contents == nil
+
+ contents
+ end
+end
diff --git a/puppet/modules/common/lib/puppet/parser/functions/prefix_with.rb b/puppet/modules/common/lib/puppet/parser/functions/prefix_with.rb
new file mode 100644
index 00000000..6e64a4a8
--- /dev/null
+++ b/puppet/modules/common/lib/puppet/parser/functions/prefix_with.rb
@@ -0,0 +1,9 @@
+# prefix arguments 2..n with first argument
+
+module Puppet::Parser::Functions
+ newfunction(:prefix_with, :type => :rvalue) do |args|
+ prefix = args.shift
+ args.collect {|v| "%s%s" % [prefix, v] }
+ end
+end
+
diff --git a/puppet/modules/common/lib/puppet/parser/functions/re_escape.rb b/puppet/modules/common/lib/puppet/parser/functions/re_escape.rb
new file mode 100644
index 00000000..7bee90a8
--- /dev/null
+++ b/puppet/modules/common/lib/puppet/parser/functions/re_escape.rb
@@ -0,0 +1,7 @@
+# apply ruby regexp escaping to a string
+module Puppet::Parser::Functions
+ newfunction(:re_escape, :type => :rvalue) do |args|
+ Regexp.escape(args[0])
+ end
+end
+
diff --git a/puppet/modules/common/lib/puppet/parser/functions/slash_escape.rb b/puppet/modules/common/lib/puppet/parser/functions/slash_escape.rb
new file mode 100644
index 00000000..04d3b95e
--- /dev/null
+++ b/puppet/modules/common/lib/puppet/parser/functions/slash_escape.rb
@@ -0,0 +1,7 @@
+# escape slashes in a String
+module Puppet::Parser::Functions
+ newfunction(:slash_escape, :type => :rvalue) do |args|
+ args[0].gsub(/\//, '\\/')
+ end
+end
+
diff --git a/puppet/modules/common/lib/puppet/parser/functions/substitute.rb b/puppet/modules/common/lib/puppet/parser/functions/substitute.rb
new file mode 100644
index 00000000..4c97def3
--- /dev/null
+++ b/puppet/modules/common/lib/puppet/parser/functions/substitute.rb
@@ -0,0 +1,20 @@
+# subsititute($string, $regex, $replacement) : $string
+# subsititute($string[], $regex, $replacement) : $string[]
+#
+# Replace all ocurrences of $regex in $string by $replacement.
+# $regex is interpreted as Ruby regular expression.
+#
+# For long-term portability it is recommended to refrain from using Ruby's
+# extended RE features.
+module Puppet::Parser::Functions
+ newfunction(:substitute, :type => :rvalue) do |args|
+ if args[0].is_a?(Array)
+ args[0].collect do |val|
+ val.gsub(/#{args[1]}/, args[2])
+ end
+ else
+ args[0].gsub(/#{args[1]}/, args[2])
+ end
+ end
+end
+
diff --git a/puppet/modules/common/lib/puppet/parser/functions/tfile.rb b/puppet/modules/common/lib/puppet/parser/functions/tfile.rb
new file mode 100644
index 00000000..acb6609b
--- /dev/null
+++ b/puppet/modules/common/lib/puppet/parser/functions/tfile.rb
@@ -0,0 +1,19 @@
+Puppet::Parser::Functions::newfunction(
+ :tfile,
+ :type => :rvalue,
+ :doc => "Returns the content of a file. If the file or the path does not
+ yet exist, it will create the path and touch the file."
+) do |args|
+ raise Puppet::ParseError, 'tfile() needs one argument' if args.length != 1
+ path = args.to_a.first
+ unless File.exists?(path)
+ dir = File.dirname(path)
+ unless File.directory?(dir)
+ require 'fileutils'
+ FileUtils.mkdir_p(dir, :mode => 0700)
+ end
+ require 'fileutils'
+ FileUtils.touch(path)
+ end
+ File.read(path)
+end
diff --git a/puppet/modules/common/manifests/module_dir.pp b/puppet/modules/common/manifests/module_dir.pp
new file mode 100644
index 00000000..2420da94
--- /dev/null
+++ b/puppet/modules/common/manifests/module_dir.pp
@@ -0,0 +1,34 @@
+# common/manifests/modules_dir.pp -- create a default directory
+# for storing module specific information
+#
+# Copyright (C) 2007 David Schmitt <david@schmitt.edv-bus.at>
+# See LICENSE for the full license granted to you.
+
+# A module_dir is a storage place for all the stuff a module might want to
+# store. According to the FHS, this should go to /var/lib. Since this is a part
+# of puppet, the full path is /var/lib/puppet/modules/${name}. Every module
+# should # prefix its module_dirs with its name.
+#
+# Usage:
+# include common::moduledir
+# module_dir { ["common", "common/dir1", "common/dir2" ]: }
+#
+# You may refer to a file in module_dir by using :
+# file { "${common::moduledir::module_dir_path}/somedir/somefile": }
+define common::module_dir(
+ $owner = root,
+ $group = 0,
+ $mode = 0644
+) {
+ include common::moduledir
+ file {
+ "${common::moduledir::module_dir_path}/${name}":
+ ensure => directory,
+ recurse => true,
+ purge => true,
+ force => true,
+ owner => $owner,
+ group => $group,
+ mode => $mode;
+ }
+}
diff --git a/puppet/modules/common/manifests/module_file.pp b/puppet/modules/common/manifests/module_file.pp
new file mode 100644
index 00000000..c1070bcf
--- /dev/null
+++ b/puppet/modules/common/manifests/module_file.pp
@@ -0,0 +1,37 @@
+# common/manifests/module_file.pp -- use a modules_dir to store module
+# specific files
+#
+# Copyright (C) 2007 David Schmitt <david@schmitt.edv-bus.at>
+# See LICENSE for the full license granted to you.
+
+# Put a file into module-local storage.
+#
+# Usage:
+# common::module_file { "module/file":
+# source => "puppet:///...",
+# mode => 644, # default
+# owner => root, # default
+# group => 0, # default
+# }
+define common::module_file (
+ $ensure = present,
+ $source = undef,
+ $owner = root,
+ $group = 0,
+ $mode = 0644
+){
+ include common::moduledir
+ file {
+ "${common::moduledir::module_dir_path}/${name}":
+ ensure => $ensure,
+ }
+
+ if $ensure != 'absent' {
+ File["${common::moduledir::module_dir_path}/${name}"]{
+ source => $source,
+ owner => $owner,
+ group => $group,
+ mode => $mode,
+ }
+ }
+}
diff --git a/puppet/modules/common/manifests/moduledir.pp b/puppet/modules/common/manifests/moduledir.pp
new file mode 100644
index 00000000..f779085b
--- /dev/null
+++ b/puppet/modules/common/manifests/moduledir.pp
@@ -0,0 +1,18 @@
+# setup root for module_dirs
+class common::moduledir {
+ # Use this variable to reference the base path. Thus you are safe from any
+ # changes.
+ $module_dir_path = '/var/lib/puppet/modules'
+
+ # Module programmers can use /var/lib/puppet/modules/$modulename to save
+ # module-local data, e.g. for constructing config files
+ file{$module_dir_path:
+ ensure => directory,
+ recurse => true,
+ purge => true,
+ force => true,
+ owner => root,
+ group => 0,
+ mode => '0755';
+ }
+}
diff --git a/puppet/modules/common/manifests/moduledir/common.pp b/puppet/modules/common/manifests/moduledir/common.pp
new file mode 100644
index 00000000..e74c601e
--- /dev/null
+++ b/puppet/modules/common/manifests/moduledir/common.pp
@@ -0,0 +1,4 @@
+# setup a common dir
+class common::moduledir::common{
+ common::module_dir{'common': }
+}
diff --git a/puppet/modules/common/spec/spec.opts b/puppet/modules/common/spec/spec.opts
new file mode 100644
index 00000000..91cd6427
--- /dev/null
+++ b/puppet/modules/common/spec/spec.opts
@@ -0,0 +1,6 @@
+--format
+s
+--colour
+--loadby
+mtime
+--backtrace
diff --git a/puppet/modules/common/spec/spec_helper.rb b/puppet/modules/common/spec/spec_helper.rb
new file mode 100644
index 00000000..6ba62e11
--- /dev/null
+++ b/puppet/modules/common/spec/spec_helper.rb
@@ -0,0 +1,16 @@
+require 'pathname'
+dir = Pathname.new(__FILE__).parent
+$LOAD_PATH.unshift(dir, dir + 'lib', dir + '../lib')
+require 'puppet'
+gem 'rspec', '>= 1.2.9'
+require 'spec/autorun'
+
+Dir[File.join(File.dirname(__FILE__), 'support', '*.rb')].each do |support_file|
+ require support_file
+end
+
+# We need this because the RAL uses 'should' as a method. This
+# allows us the same behaviour but with a different method name.
+class Object
+ alias :must :should
+end
diff --git a/puppet/modules/common/spec/unit/parser/functions/tfile.rb b/puppet/modules/common/spec/unit/parser/functions/tfile.rb
new file mode 100644
index 00000000..5c8f636e
--- /dev/null
+++ b/puppet/modules/common/spec/unit/parser/functions/tfile.rb
@@ -0,0 +1,54 @@
+#! /usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../../spec_helper'
+require 'mocha'
+
+describe "the tfile function" do
+
+ before :each do
+ @scope = Puppet::Parser::Scope.new
+ end
+
+ it "should exist" do
+ Puppet::Parser::Functions.function("tfile").should == "function_tfile"
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ lambda { @scope.function_tfile([]) }.should( raise_error(Puppet::ParseError))
+ end
+
+ it "should raise a ParseError if there is more than 1 arguments" do
+ lambda { @scope.function_tfile(["bar", "gazonk"]) }.should( raise_error(Puppet::ParseError))
+ end
+
+ describe "when executed properly" do
+
+ before :each do
+ File.stubs(:read).with('/some_path/aa').returns("foo1\nfoo2\n")
+ end
+
+ it "should return the content of the file" do
+ File.stubs(:exists?).with('/some_path/aa').returns(true)
+ result = @scope.function_tfile(['/some_path/aa'])
+ result.should == "foo1\nfoo2\n"
+ end
+
+ it "should touch a file if it does not exist" do
+ File.stubs(:exists?).with('/some_path/aa').returns(false)
+ File.stubs(:directory?).with('/some_path').returns(true)
+ FileUtils.expects(:touch).with('/some_path/aa')
+ result = @scope.function_tfile(['/some_path/aa'])
+ result.should == "foo1\nfoo2\n"
+ end
+
+ it "should create the path if it does not exist" do
+ File.stubs(:exists?).with('/some_path/aa').returns(false)
+ File.stubs(:directory?).with('/some_path').returns(false)
+ FileUtils.expects(:mkdir_p).with("/some_path",:mode => 0700)
+ FileUtils.expects(:touch).with('/some_path/aa')
+ result = @scope.function_tfile(['/some_path/aa'])
+ result.should == "foo1\nfoo2\n"
+ end
+ end
+
+end
diff --git a/puppet/modules/concat/CHANGELOG b/puppet/modules/concat/CHANGELOG
new file mode 100644
index 00000000..c506cf1a
--- /dev/null
+++ b/puppet/modules/concat/CHANGELOG
@@ -0,0 +1,29 @@
+KNOWN ISSUES:
+- In 0.24.8 you will see inintended notifies, if you build a file
+ in a run, the next run will also see it as changed. This is due
+ to how 0.24.8 does the purging of unhandled files, this is improved
+ in 0.25.x and we cannot work around it in our code.
+
+CHANGELOG:
+- 2010/02/19 - initial release
+- 2010/03/12 - add support for 0.24.8 and newer
+ - make the location of sort configurable
+ - add the ability to add shell comment based warnings to
+ top of files
+ - add the ablity to create empty files
+- 2010/04/05 - fix parsing of WARN and change code style to match rest
+ of the code
+ - Better and safer boolean handling for warn and force
+ - Don't use hard coded paths in the shell script, set PATH
+ top of the script
+ - Use file{} to copy the result and make all fragments owned
+ by root. This means we can chnage the ownership/group of the
+ resulting file at any time.
+ - You can specify ensure => "/some/other/file" in concat::fragment
+ to include the contents of a symlink into the final file.
+- 2010/04/16 - Add more cleaning of the fragment name - removing / from the $name
+- 2010/05/22 - Improve documentation and show the use of ensure =>
+- 2010/07/14 - Add support for setting the filebucket behavior of files
+- 2010/10/04 - Make the warning message configurable
+- 2010/12/03 - Add flags to make concat work better on Solaris - thanks Jonathan Boyett
+- 2011/02/03 - Make the shell script more portable and add a config option for root group
diff --git a/puppet/modules/concat/LICENSE b/puppet/modules/concat/LICENSE
new file mode 100644
index 00000000..6a9e9a19
--- /dev/null
+++ b/puppet/modules/concat/LICENSE
@@ -0,0 +1,14 @@
+ Copyright 2012 R.I.Pienaar
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
diff --git a/puppet/modules/concat/Modulefile b/puppet/modules/concat/Modulefile
new file mode 100644
index 00000000..d6ab2bb0
--- /dev/null
+++ b/puppet/modules/concat/Modulefile
@@ -0,0 +1,8 @@
+name 'puppet-concat'
+version '0.1.0'
+source 'git://github.com/ripienaar/puppet-concat.git'
+author 'R.I.Pienaar'
+license 'Apache'
+summary 'Concat module'
+description 'Concat module'
+project_page 'http://github.com/ripienaar/puppet-concat'
diff --git a/puppet/modules/concat/README.markdown b/puppet/modules/concat/README.markdown
new file mode 100644
index 00000000..8736d57a
--- /dev/null
+++ b/puppet/modules/concat/README.markdown
@@ -0,0 +1,112 @@
+What is it?
+===========
+
+A Puppet module that can construct files from fragments.
+
+Please see the comments in the various .pp files for details
+as well as posts on my blog at http://www.devco.net/
+
+Released under the Apache 2.0 licence
+
+Usage:
+------
+
+Before you can use any of the concat features you should include the class
+concat::setup somewhere on your node first.
+
+If you wanted a /etc/motd file that listed all the major modules
+on the machine. And that would be maintained automatically even
+if you just remove the include lines for other modules you could
+use code like below, a sample /etc/motd would be:
+
+<pre>
+Puppet modules on this server:
+
+ -- Apache
+ -- MySQL
+</pre>
+
+Local sysadmins can also append to the file by just editing /etc/motd.local
+their changes will be incorporated into the puppet managed motd.
+
+<pre>
+# class to setup basic motd, include on all nodes
+class motd {
+ include concat::setup
+ $motd = "/etc/motd"
+
+ concat{$motd:
+ owner => root,
+ group => root,
+ mode => 644
+ }
+
+ concat::fragment{"motd_header":
+ target => $motd,
+ content => "\nPuppet modules on this server:\n\n",
+ order => 01,
+ }
+
+ # local users on the machine can append to motd by just creating
+ # /etc/motd.local
+ concat::fragment{"motd_local":
+ target => $motd,
+ ensure => "/etc/motd.local",
+ order => 15
+ }
+}
+
+# used by other modules to register themselves in the motd
+define motd::register($content="", $order=10) {
+ if $content == "" {
+ $body = $name
+ } else {
+ $body = $content
+ }
+
+ concat::fragment{"motd_fragment_$name":
+ target => "/etc/motd",
+ content => " -- $body\n"
+ }
+}
+
+# a sample apache module
+class apache {
+ include apache::install, apache::config, apache::service
+
+ motd::register{"Apache": }
+}
+</pre>
+
+Known Issues:
+-------------
+* In 0.24.8 you will see inintended notifies, if you build a file
+ in a run, the next run will also see it as changed. This is due
+ to how 0.24.8 does the purging of unhandled files, this is improved
+ in 0.25.x and we cannot work around it in our code.
+* Since puppet-concat now relies on a fact for the concat directory,
+ you will need to set up pluginsync = true for at least the first run.
+ You have this issue if puppet fails to run on the client and you have
+ a message similar to
+ "err: Failed to apply catalog: Parameter path failed: File
+ paths must be fully qualified, not 'undef' at [...]/concat/manifests/setup.pp:44".
+
+Contributors:
+-------------
+**Paul Elliot**
+
+ * Provided 0.24.8 support, shell warnings and empty file creation support.
+
+**Chad Netzer**
+
+ * Various patches to improve safety of file operations
+ * Symlink support
+
+**David Schmitt**
+
+ * Patch to remove hard coded paths relying on OS path
+ * Patch to use file{} to copy the resulting file to the final destination. This means Puppet client will show diffs and that hopefully we can change file ownerships now
+
+Contact:
+--------
+You can contact me on rip@devco.net or follow my blog at http://www.devco.net I am also on twitter as ripienaar
diff --git a/puppet/modules/concat/Rakefile b/puppet/modules/concat/Rakefile
new file mode 100644
index 00000000..764aebd2
--- /dev/null
+++ b/puppet/modules/concat/Rakefile
@@ -0,0 +1,13 @@
+require 'rake'
+require 'rspec/core/rake_task'
+
+task :default => [:spec]
+
+desc "Run all module spec tests (Requires rspec-puppet gem)"
+RSpec::Core::RakeTask.new(:spec)
+
+desc "Build package"
+task :build do
+ system("puppet-module build")
+end
+
diff --git a/puppet/modules/concat/files/concatfragments.sh b/puppet/modules/concat/files/concatfragments.sh
new file mode 100755
index 00000000..c9397975
--- /dev/null
+++ b/puppet/modules/concat/files/concatfragments.sh
@@ -0,0 +1,129 @@
+#!/bin/sh
+
+# Script to concat files to a config file.
+#
+# Given a directory like this:
+# /path/to/conf.d
+# |-- fragments
+# | |-- 00_named.conf
+# | |-- 10_domain.net
+# | `-- zz_footer
+#
+# The script supports a test option that will build the concat file to a temp location and
+# use /usr/bin/cmp to verify if it should be run or not. This would result in the concat happening
+# twice on each run but gives you the option to have an unless option in your execs to inhibit rebuilds.
+#
+# Without the test option and the unless combo your services that depend on the final file would end up
+# restarting on each run, or in other manifest models some changes might get missed.
+#
+# OPTIONS:
+# -o The file to create from the sources
+# -d The directory where the fragments are kept
+# -t Test to find out if a build is needed, basically concats the files to a temp
+# location and compare with what's in the final location, return codes are designed
+# for use with unless on an exec resource
+# -w Add a shell style comment at the top of the created file to warn users that it
+# is generated by puppet
+# -f Enables the creation of empty output files when no fragments are found
+# -n Sort the output numerically rather than the default alpha sort
+#
+# the command:
+#
+# concatfragments.sh -o /path/to/conffile.cfg -d /path/to/conf.d
+#
+# creates /path/to/conf.d/fragments.concat and copies the resulting
+# file to /path/to/conffile.cfg. The files will be sorted alphabetically
+# pass the -n switch to sort numerically.
+#
+# The script does error checking on the various dirs and files to make
+# sure things don't fail.
+
+OUTFILE=""
+WORKDIR=""
+TEST=""
+FORCE=""
+WARN=""
+SORTARG=""
+
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+
+## Well, if there's ever a bad way to do things, Nexenta has it.
+## http://nexenta.org/projects/site/wiki/Personalities
+unset SUN_PERSONALITY
+
+while getopts "o:s:d:tnw:f" options; do
+ case $options in
+ o ) OUTFILE=$OPTARG;;
+ d ) WORKDIR=$OPTARG;;
+ n ) SORTARG="-n";;
+ w ) WARNMSG="$OPTARG";;
+ f ) FORCE="true";;
+ t ) TEST="true";;
+ * ) echo "Specify output file with -o and fragments directory with -d"
+ exit 1;;
+ esac
+done
+
+# do we have -o?
+if [ x${OUTFILE} = "x" ]; then
+ echo "Please specify an output file with -o"
+ exit 1
+fi
+
+# do we have -d?
+if [ x${WORKDIR} = "x" ]; then
+ echo "Please fragments directory with -d"
+ exit 1
+fi
+
+# can we write to -o?
+if [ -f ${OUTFILE} ]; then
+ if [ ! -w ${OUTFILE} ]; then
+ echo "Cannot write to ${OUTFILE}"
+ exit 1
+ fi
+else
+ if [ ! -w `dirname ${OUTFILE}` ]; then
+ echo "Cannot write to `dirname ${OUTFILE}` to create ${OUTFILE}"
+ exit 1
+ fi
+fi
+
+# do we have a fragments subdir inside the work dir?
+if [ ! -d "${WORKDIR}/fragments" ] && [ ! -x "${WORKDIR}/fragments" ]; then
+ echo "Cannot access the fragments directory"
+ exit 1
+fi
+
+# are there actually any fragments?
+if [ ! "$(ls -A ${WORKDIR}/fragments)" ]; then
+ if [ x${FORCE} = "x" ]; then
+ echo "The fragments directory is empty, cowardly refusing to make empty config files"
+ exit 1
+ fi
+fi
+
+cd ${WORKDIR}
+
+if [ x${WARNMSG} = "x" ]; then
+ : > "fragments.concat"
+else
+ printf '%s\n' "$WARNMSG" > "fragments.concat"
+fi
+
+# find all the files in the fragments directory, sort them numerically and concat to fragments.concat in the working dir
+find fragments/ -type f -follow | sort ${SORTARG} | while read fragfile; do
+ cat "$fragfile" >> "fragments.concat"
+done
+
+if [ x${TEST} = "x" ]; then
+ # This is a real run, copy the file to outfile
+ cp fragments.concat ${OUTFILE}
+ RETVAL=$?
+else
+ # Just compare the result to outfile to help the exec decide
+ cmp ${OUTFILE} fragments.concat
+ RETVAL=$?
+fi
+
+exit $RETVAL
diff --git a/puppet/modules/concat/files/null/.gitignore b/puppet/modules/concat/files/null/.gitignore
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/puppet/modules/concat/files/null/.gitignore
diff --git a/puppet/modules/concat/lib/facter/concat_basedir.rb b/puppet/modules/concat/lib/facter/concat_basedir.rb
new file mode 100644
index 00000000..02e9c5bf
--- /dev/null
+++ b/puppet/modules/concat/lib/facter/concat_basedir.rb
@@ -0,0 +1,5 @@
+Facter.add("concat_basedir") do
+ setcode do
+ File.join(Puppet[:vardir],"concat")
+ end
+end
diff --git a/puppet/modules/concat/manifests/fragment.pp b/puppet/modules/concat/manifests/fragment.pp
new file mode 100644
index 00000000..943bf671
--- /dev/null
+++ b/puppet/modules/concat/manifests/fragment.pp
@@ -0,0 +1,49 @@
+# Puts a file fragment into a directory previous setup using concat
+#
+# OPTIONS:
+# - target The file that these fragments belong to
+# - content If present puts the content into the file
+# - source If content was not specified, use the source
+# - order By default all files gets a 10_ prefix in the directory
+# you can set it to anything else using this to influence the
+# order of the content in the file
+# - ensure Present/Absent or destination to a file to include another file
+# - mode Mode for the file
+# - owner Owner of the file
+# - group Owner of the file
+# - backup Controls the filebucketing behavior of the final file and
+# see File type reference for its use. Defaults to 'puppet'
+define concat::fragment($target, $content='', $source='', $order=10, $ensure = 'present', $mode = '0644', $owner = $::id, $group = $concat::setup::root_group, $backup = 'puppet') {
+ $safe_name = regsubst($name, '/', '_', 'G')
+ $safe_target_name = regsubst($target, '/', '_', 'G')
+ $concatdir = $concat::setup::concatdir
+ $fragdir = "${concatdir}/${safe_target_name}"
+
+ # if content is passed, use that, else if source is passed use that
+ # if neither passed, but $ensure is in symlink form, make a symlink
+ case $content {
+ '': {
+ case $source {
+ '': {
+ case $ensure {
+ '', 'absent', 'present', 'file', 'directory': {
+ crit('No content, source or symlink specified')
+ }
+ }
+ }
+ default: { File{ source => $source } }
+ }
+ }
+ default: { File{ content => $content } }
+ }
+
+ file{"${fragdir}/fragments/${order}_${safe_name}":
+ ensure => $ensure,
+ mode => $mode,
+ owner => $owner,
+ group => $group,
+ backup => $backup,
+ alias => "concat_fragment_${name}",
+ notify => Exec["concat_${target}"]
+ }
+}
diff --git a/puppet/modules/concat/manifests/init.pp b/puppet/modules/concat/manifests/init.pp
new file mode 100644
index 00000000..0b3ed564
--- /dev/null
+++ b/puppet/modules/concat/manifests/init.pp
@@ -0,0 +1,178 @@
+# A system to construct files using fragments from other files or templates.
+#
+# This requires at least puppet 0.25 to work correctly as we use some
+# enhancements in recursive directory management and regular expressions
+# to do the work here.
+#
+# USAGE:
+# The basic use case is as below:
+#
+# concat{"/etc/named.conf":
+# notify => Service["named"]
+# }
+#
+# concat::fragment{"foo.com_config":
+# target => "/etc/named.conf",
+# order => 10,
+# content => template("named_conf_zone.erb")
+# }
+#
+# # add a fragment not managed by puppet so local users
+# # can add content to managed file
+# concat::fragment{"foo.com_user_config":
+# target => "/etc/named.conf",
+# order => 12,
+# ensure => "/etc/named.conf.local"
+# }
+#
+# This will use the template named_conf_zone.erb to build a single
+# bit of config up and put it into the fragments dir. The file
+# will have an number prefix of 10, you can use the order option
+# to control that and thus control the order the final file gets built in.
+#
+# SETUP:
+# The class concat::setup uses the fact concat_basedir to define the variable
+# $concatdir, where all the temporary files and fragments will be
+# durably stored. The fact concat_basedir will be set up on the client to
+# <Puppet[:vardir]>/concat, so you will be able to run different setup/flavours
+# of puppet clients.
+# However, since this requires the file lib/facter/concat_basedir.rb to be
+# deployed on the clients, so you will have to set "pluginsync = true" on
+# both the master and client, at least for the first run.
+#
+# There's some regular expression magic to figure out the puppet version but
+# if you're on an older 0.24 version just set $puppetversion = 24
+#
+# Before you can use any of the concat features you should include the
+# class concat::setup somewhere on your node first.
+#
+# DETAIL:
+# We use a helper shell script called concatfragments.sh that gets placed
+# in <Puppet[:vardir]>/concat/bin to do the concatenation. While this might
+# seem more complex than some of the one-liner alternatives you might find on
+# the net we do a lot of error checking and safety checks in the script to avoid
+# problems that might be caused by complex escaping errors etc.
+#
+# LICENSE:
+# Apache Version 2
+#
+# LATEST:
+# http://github.com/ripienaar/puppet-concat/
+#
+# CONTACT:
+# R.I.Pienaar <rip@devco.net>
+# Volcane on freenode
+# @ripienaar on twitter
+# www.devco.net
+
+
+# Sets up so that you can use fragments to build a final config file,
+#
+# OPTIONS:
+# - mode The mode of the final file
+# - owner Who will own the file
+# - group Who will own the file
+# - force Enables creating empty files if no fragments are present
+# - warn Adds a normal shell style comment top of the file indicating
+# that it is built by puppet
+# - backup Controls the filebucketing behavior of the final file and
+# see File type reference for its use. Defaults to 'puppet'
+#
+# ACTIONS:
+# - Creates fragment directories if it didn't exist already
+# - Executes the concatfragments.sh script to build the final file, this script will create
+# directory/fragments.concat. Execution happens only when:
+# * The directory changes
+# * fragments.concat != final destination, this means rebuilds will happen whenever
+# someone changes or deletes the final file. Checking is done using /usr/bin/cmp.
+# * The Exec gets notified by something else - like the concat::fragment define
+# - Copies the file over to the final destination using a file resource
+#
+# ALIASES:
+# - The exec can notified using Exec["concat_/path/to/file"] or Exec["concat_/path/to/directory"]
+# - The final file can be referened as File["/path/to/file"] or File["concat_/path/to/file"]
+define concat($mode = '0644', $owner = $::id, $group = $concat::setup::root_group, $warn = false, $force = false, $backup = 'puppet', $gnu = undef, $order='alpha') {
+ $safe_name = regsubst($name, '/', '_', 'G')
+ $concatdir = $concat::setup::concatdir
+ $version = $concat::setup::majorversion
+ $fragdir = "${concatdir}/${safe_name}"
+ $concat_name = 'fragments.concat.out'
+ $default_warn_message = '# This file is managed by Puppet. DO NOT EDIT.'
+
+ case $warn {
+ 'true',true,yes,on: { $warnmsg = $default_warn_message }
+ 'false',false,no,off: { $warnmsg = '' }
+ default: { $warnmsg = $warn }
+ }
+
+ $warnmsg_escaped = regsubst($warnmsg, "'", "'\\\\''", 'G')
+ $warnflag = $warnmsg_escaped ? {
+ '' => '',
+ default => "-w '${warnmsg_escaped}'"
+ }
+
+ case $force {
+ 'true',true,yes,on: { $forceflag = '-f' }
+ 'false',false,no,off: { $forceflag = '' }
+ default: { fail("Improper 'force' value given to concat: ${force}") }
+ }
+
+ case $order {
+ numeric: { $orderflag = '-n' }
+ alpha: { $orderflag = '' }
+ default: { fail("Improper 'order' value given to concat: ${order}") }
+ }
+
+ File{
+ owner => $::id,
+ group => $group,
+ mode => $mode,
+ backup => $backup
+ }
+
+ file{$fragdir:
+ ensure => directory;
+
+ "${fragdir}/fragments":
+ ensure => directory,
+ recurse => true,
+ purge => true,
+ force => true,
+ ignore => ['.svn', '.git', '.gitignore'],
+ source => $version ? {
+ 24 => 'puppet:///concat/null',
+ default => undef,
+ },
+ notify => Exec["concat_${name}"];
+
+ "${fragdir}/fragments.concat":
+ ensure => present;
+
+ "${fragdir}/${concat_name}":
+ ensure => present;
+
+ $name:
+ ensure => present,
+ source => "${fragdir}/${concat_name}",
+ owner => $owner,
+ group => $group,
+ checksum => md5,
+ mode => $mode,
+ alias => "concat_${name}";
+ }
+
+ exec{"concat_${name}":
+ notify => File[$name],
+ subscribe => File[$fragdir],
+ alias => "concat_${fragdir}",
+ require => [ File[$fragdir], File["${fragdir}/fragments"], File["${fragdir}/fragments.concat"] ],
+ unless => "${concat::setup::concatdir}/bin/concatfragments.sh -o ${fragdir}/${concat_name} -d ${fragdir} -t ${warnflag} ${forceflag} ${orderflag}",
+ command => "${concat::setup::concatdir}/bin/concatfragments.sh -o ${fragdir}/${concat_name} -d ${fragdir} ${warnflag} ${forceflag} ${orderflag}",
+ }
+ if $::id == 'root' {
+ Exec["concat_${name}"]{
+ user => root,
+ group => $group,
+ }
+ }
+}
diff --git a/puppet/modules/concat/manifests/setup.pp b/puppet/modules/concat/manifests/setup.pp
new file mode 100644
index 00000000..38aeb964
--- /dev/null
+++ b/puppet/modules/concat/manifests/setup.pp
@@ -0,0 +1,49 @@
+# Sets up the concat system.
+#
+# $concatdir is where the fragments live and is set on the fact concat_basedir.
+# Since puppet should always manage files in $concatdir and they should
+# not be deleted ever, /tmp is not an option.
+#
+# $puppetversion should be either 24 or 25 to enable a 24 compatible
+# mode, in 24 mode you might see phantom notifies this is a side effect
+# of the method we use to clear the fragments directory.
+#
+# The regular expression below will try to figure out your puppet version
+# but this code will only work in 0.24.8 and newer.
+#
+# It also copies out the concatfragments.sh file to ${concatdir}/bin
+class concat::setup {
+ $id = $::id
+ $root_group = $id ? {
+ root => 0,
+ default => $id
+ }
+
+ if $::concat_basedir {
+ $concatdir = $::concat_basedir
+ } else {
+ fail ("\$concat_basedir not defined. Try running again with pluginsync enabled")
+ }
+
+ $majorversion = regsubst($::puppetversion, '^[0-9]+[.]([0-9]+)[.][0-9]+$', '\1')
+
+ file{"${concatdir}/bin/concatfragments.sh":
+ owner => $id,
+ group => $root_group,
+ mode => '0755',
+ source => $majorversion ? {
+ 24 => 'puppet:///concat/concatfragments.sh',
+ default => 'puppet:///modules/concat/concatfragments.sh'
+ };
+
+ [ $concatdir, "${concatdir}/bin" ]:
+ ensure => directory,
+ owner => $id,
+ group => $root_group,
+ mode => '0750';
+
+ ## Old versions of this module used a different path.
+ '/usr/local/bin/concatfragments.sh':
+ ensure => absent;
+ }
+}
diff --git a/puppet/modules/concat/spec/defines/init_spec.rb b/puppet/modules/concat/spec/defines/init_spec.rb
new file mode 100644
index 00000000..d968a26c
--- /dev/null
+++ b/puppet/modules/concat/spec/defines/init_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe 'concat' do
+ basedir = '/var/lib/puppet/concat'
+ let(:title) { '/etc/foo.bar' }
+ let(:facts) { { :concat_basedir => '/var/lib/puppet/concat' } }
+ let :pre_condition do
+ 'include concat::setup'
+ end
+ it { should contain_file("#{basedir}/_etc_foo.bar").with('ensure' => 'directory') }
+ it { should contain_file("#{basedir}/_etc_foo.bar/fragments").with('ensure' => 'directory') }
+
+ it { should contain_file("#{basedir}/_etc_foo.bar/fragments.concat").with('ensure' => 'present') }
+ it { should contain_file("/etc/foo.bar").with('ensure' => 'present') }
+ it { should contain_exec("concat_/etc/foo.bar").with_command(
+ "#{basedir}/bin/concatfragments.sh "+
+ "-o #{basedir}/_etc_foo.bar/fragments.concat.out "+
+ "-d #{basedir}/_etc_foo.bar ")
+ }
+end
diff --git a/puppet/modules/concat/spec/spec_helper.rb b/puppet/modules/concat/spec/spec_helper.rb
new file mode 100644
index 00000000..e6e9309b
--- /dev/null
+++ b/puppet/modules/concat/spec/spec_helper.rb
@@ -0,0 +1,9 @@
+require 'puppet'
+require 'rspec'
+require 'rspec-puppet'
+
+RSpec.configure do |c|
+ c.module_path = File.expand_path(File.join(File.dirname(__FILE__), 'fixtures/modules/'))
+ # Using an empty site.pp file to avoid: https://github.com/rodjek/rspec-puppet/issues/15
+ c.manifest_dir = File.expand_path(File.join(File.dirname(__FILE__), 'fixtures/manifests'))
+end
diff --git a/puppet/modules/couchdb/.fixtures.yml b/puppet/modules/couchdb/.fixtures.yml
new file mode 100644
index 00000000..50c6c9ac
--- /dev/null
+++ b/puppet/modules/couchdb/.fixtures.yml
@@ -0,0 +1,6 @@
+fixtures:
+ symlinks:
+ couchdb: "#{source_dir}"
+ repositories:
+ stdlib: " https://leap.se/git/puppet_stdlib"
+
diff --git a/puppet/modules/couchdb/Gemfile b/puppet/modules/couchdb/Gemfile
new file mode 100644
index 00000000..1c86e980
--- /dev/null
+++ b/puppet/modules/couchdb/Gemfile
@@ -0,0 +1,11 @@
+source "https://rubygems.org"
+
+group :test do
+ gem "rake"
+ gem "puppet", ENV['PUPPET_VERSION'] || '~> 3.7.0'
+ gem "rspec", '< 3.2.0'
+ gem "rspec-puppet"
+ gem "puppetlabs_spec_helper"
+ gem "metadata-json-lint"
+ gem "rspec-puppet-facts"
+end
diff --git a/puppet/modules/couchdb/README.md b/puppet/modules/couchdb/README.md
new file mode 100644
index 00000000..096221a4
--- /dev/null
+++ b/puppet/modules/couchdb/README.md
@@ -0,0 +1,32 @@
+# Couchdb Puppet module
+
+This module is based on the one from Camptocamp_.
+
+.. _Camptocamp: http://www.camptocamp.com/
+
+For more information about couchdb see http://couchdb.apache.org/
+
+# Dependencies
+
+- ruby module from the shared-modules group
+
+# Couchdb debian packages
+
+## Jessie
+
+There are no couchdb packages for jessie, so the only way is to
+to configure apt to install couchdb from unstable by adding a
+sources list file to `/etc/apt/sources.list.d`.
+
+## Example usage
+
+This will setup couchdb:
+
+ # needed for wget call, which is unqualified by purpose so we don't force
+ # a location for the wget binary
+ Exec { path => '/usr/bin:/usr/sbin/:/bin:/sbin:/usr/local/bin:/usr/local/sbin' }
+
+ class { 'couchdb':
+ admin_pw => '123'
+ }
+
diff --git a/puppet/modules/couchdb/Rakefile b/puppet/modules/couchdb/Rakefile
new file mode 100644
index 00000000..85326bb4
--- /dev/null
+++ b/puppet/modules/couchdb/Rakefile
@@ -0,0 +1,19 @@
+require 'puppetlabs_spec_helper/rake_tasks'
+require 'puppet-lint/tasks/puppet-lint'
+PuppetLint.configuration.send('disable_80chars')
+PuppetLint.configuration.ignore_paths = ["spec/**/*.pp", "pkg/**/*.pp"]
+
+desc "Validate manifests, templates, and ruby files"
+task :validate do
+ Dir['manifests/**/*.pp'].each do |manifest|
+ sh "puppet parser validate --noop #{manifest}"
+ end
+ Dir['spec/**/*.rb','lib/**/*.rb'].each do |ruby_file|
+ sh "ruby -c #{ruby_file}" unless ruby_file =~ /spec\/fixtures/
+ end
+ Dir['templates/**/*.erb'].each do |template|
+ sh "erb -P -x -T '-' #{template} | ruby -c"
+ end
+end
+
+task :test => [:lint, :syntax , :validate, :spec]
diff --git a/puppet/modules/couchdb/files/Debian/couchdb b/puppet/modules/couchdb/files/Debian/couchdb
new file mode 100755
index 00000000..ccdfe716
--- /dev/null
+++ b/puppet/modules/couchdb/files/Debian/couchdb
@@ -0,0 +1,160 @@
+#!/bin/sh -e
+
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+### BEGIN INIT INFO
+# Provides: couchdb
+# Required-Start: $local_fs $remote_fs
+# Required-Stop: $local_fs $remote_fs
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: Apache CouchDB init script
+# Description: Apache CouchDB init script for the database server.
+### END INIT INFO
+
+SCRIPT_OK=0
+SCRIPT_ERROR=1
+
+DESCRIPTION="database server"
+NAME=couchdb
+SCRIPT_NAME=`basename $0`
+COUCHDB=/usr/bin/couchdb
+CONFIGURATION_FILE=/etc/default/couchdb
+RUN_DIR=/var/run/couchdb
+LSB_LIBRARY=/lib/lsb/init-functions
+
+if test ! -x $COUCHDB; then
+ exit $SCRIPT_ERROR
+fi
+
+if test -r $CONFIGURATION_FILE; then
+ . $CONFIGURATION_FILE
+fi
+
+log_daemon_msg () {
+ # Dummy function to be replaced by LSB library.
+
+ echo $@
+}
+
+log_end_msg () {
+ # Dummy function to be replaced by LSB library.
+
+ if test "$1" != "0"; then
+ echo "Error with $DESCRIPTION: $NAME"
+ fi
+ return $1
+}
+
+if test -r $LSB_LIBRARY; then
+ . $LSB_LIBRARY
+fi
+
+run_command () {
+ command="$1"
+ if test -n "$COUCHDB_OPTIONS"; then
+ command="$command $COUCHDB_OPTIONS"
+ fi
+ if test -n "$COUCHDB_USER"; then
+ if su $COUCHDB_USER -c "$command"; then
+ return $SCRIPT_OK
+ else
+ return $SCRIPT_ERROR
+ fi
+ else
+ if $command; then
+ return $SCRIPT_OK
+ else
+ return $SCRIPT_ERROR
+ fi
+ fi
+}
+
+start_couchdb () {
+ # Start Apache CouchDB as a background process.
+
+ mkdir -p "$RUN_DIR"
+ chown -R "$COUCHDB_USER" "$RUN_DIR"
+ command="$COUCHDB -b"
+ if test -n "$COUCHDB_STDOUT_FILE"; then
+ command="$command -o $COUCHDB_STDOUT_FILE"
+ fi
+ if test -n "$COUCHDB_STDERR_FILE"; then
+ command="$command -e $COUCHDB_STDERR_FILE"
+ fi
+ if test -n "$COUCHDB_RESPAWN_TIMEOUT"; then
+ command="$command -r $COUCHDB_RESPAWN_TIMEOUT"
+ fi
+ run_command "$command" > /dev/null
+}
+
+stop_couchdb () {
+ # Stop the running Apache CouchDB process.
+
+ run_command "$COUCHDB -d" > /dev/null
+ pkill -u couchdb
+ # always return true even if no remaining couchdb procs got killed
+ /bin/true
+}
+
+display_status () {
+ # Display the status of the running Apache CouchDB process.
+
+ run_command "$COUCHDB -s"
+}
+
+parse_script_option_list () {
+ # Parse arguments passed to the script and take appropriate action.
+
+ case "$1" in
+ start)
+ log_daemon_msg "Starting $DESCRIPTION" $NAME
+ if start_couchdb; then
+ log_end_msg $SCRIPT_OK
+ else
+ log_end_msg $SCRIPT_ERROR
+ fi
+ ;;
+ stop)
+ log_daemon_msg "Stopping $DESCRIPTION" $NAME
+ if stop_couchdb; then
+ log_end_msg $SCRIPT_OK
+ else
+ log_end_msg $SCRIPT_ERROR
+ fi
+ ;;
+ restart|force-reload)
+ log_daemon_msg "Restarting $DESCRIPTION" $NAME
+ if stop_couchdb; then
+ if start_couchdb; then
+ log_end_msg $SCRIPT_OK
+ else
+ log_end_msg $SCRIPT_ERROR
+ fi
+ else
+ log_end_msg $SCRIPT_ERROR
+ fi
+ ;;
+ status)
+ display_status
+ ;;
+ *)
+ cat << EOF >&2
+Usage: $SCRIPT_NAME {start|stop|restart|force-reload|status}
+EOF
+ exit $SCRIPT_ERROR
+ ;;
+ esac
+}
+
+parse_script_option_list $@
diff --git a/puppet/modules/couchdb/files/couch-doc-diff b/puppet/modules/couchdb/files/couch-doc-diff
new file mode 100644
index 00000000..a5907d5e
--- /dev/null
+++ b/puppet/modules/couchdb/files/couch-doc-diff
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+# Run a diff between a couch document specified as the first parameter
+# and the second parameter.
+# Diff returns 0 if there is no difference. This way you can tell the data
+# is already on the couch.
+# Both the couch document and the second paramter will be pretty printed
+# before comparison so differences in spaces etc. do not matter.
+# All keys starting with an underscore on the couch such as _id and _rev
+# will be removed before the comparison - we assume we want to compare
+# the real data, not the metadata about the document as we usually do not
+# know or care about what the id and revision will be.
+
+curl -s --netrc-file /etc/couchdb/couchdb.netrc $1 \
+ | python -mjson.tool \
+ | grep -v '^\s*"_' \
+ | diff -w - <(echo $2 | python -mjson.tool)
diff --git a/puppet/modules/couchdb/files/couch-doc-update b/puppet/modules/couchdb/files/couch-doc-update
new file mode 100644
index 00000000..a137e7ff
--- /dev/null
+++ b/puppet/modules/couchdb/files/couch-doc-update
@@ -0,0 +1,219 @@
+#!/usr/bin/ruby
+require 'syslog'
+
+#
+# This script will delete or update the values of a particular couchdb document. The benefit of this little script over
+# using a simple curl command for updating a document is this:
+#
+# * exit non-zero status if document was not updated.
+# * updates existing documents easily, taking care of the _rev id for you.
+# * if document doesn't exist, it is created
+#
+# REQUIREMENTS
+#
+# gem 'couchrest'
+#
+# USAGE
+#
+# see the ouput of
+#
+# couch-doc-update
+#
+# the content of <file> will be merged with the data provided.
+# If you only want the file content use --data '{}'
+#
+# EXAMPLE
+#
+# create a new user:
+# couch-doc-update --db _users --id org.couchdb.user:ca_daemon --data '{"type": "user", "name": "ca_daemon", "roles": ["certs"], "password": "sshhhh"}'
+#
+# update a user:
+# couch-doc-update --db _users --id org.couchdb.user:ca_daemon --data '{"password":"sssshhh"}'
+#
+# To update the _users DB on bigcouch, you must connect to port 5986 instead of the default couchdb port 5984
+#
+# delete a doc:
+# couch-doc-update --delete --db invite_codes --id dfaf0ee65670c16d5a9161dc86f3bff8
+#
+
+begin; require 'rubygems'; rescue LoadError; end # optionally load rubygems
+require 'couchrest'
+
+def main
+ db, id, data, delete = process_options
+
+ result = if delete
+ delete_document(db, id)
+ else
+ set_document(db, id, data)
+ end
+
+ exit 0 if result['ok']
+ raise StandardError.new(result.inspect)
+rescue StandardError => exc
+ db_without_password = db.to_s.sub(/:[^\/]*@/, ':PASSWORD_HIDDEN@')
+ indent = " "
+ log "ERROR: " + exc.to_s
+ log indent + $@[0..4].join("\n#{indent}")
+ log indent + "Failed writing to #{db_without_password}/#{id}"
+ exit 1
+end
+
+def log(message)
+ $stderr.puts message
+ Syslog.open('couch-doc-update') do |logger|
+ logger.log(Syslog::LOG_CRIT, message)
+ end
+end
+
+def process_options
+ #
+ # parse options
+ #
+ host = nil
+ db_name = nil
+ doc_id = nil
+ new_data = nil
+ filename = nil
+ netrc_file = nil
+ delete = false
+ loop do
+ case ARGV[0]
+ when '--host' then ARGV.shift; host = ARGV.shift
+ when '--db' then ARGV.shift; db_name = ARGV.shift
+ when '--id' then ARGV.shift; doc_id = ARGV.shift
+ when '--data' then ARGV.shift; new_data = ARGV.shift
+ when '--file' then ARGV.shift; filename = ARGV.shift
+ when '--netrc-file' then ARGV.shift; netrc_file = ARGV.shift
+ when '--delete' then ARGV.shift; delete = true
+ when /^-/ then usage("Unknown option: #{ARGV[0].inspect}")
+ else break
+ end
+ end
+ usage("Missing required option") unless db_name && doc_id && (new_data || delete)
+
+ unless delete
+ new_data = MultiJson.load(new_data)
+ new_data.merge!(read_file(filename)) if filename
+ end
+ db = CouchRest.database(connection_string(db_name, host, netrc_file))
+ return db, doc_id, new_data, delete
+end
+
+def read_file(filename)
+ data = MultiJson.load( IO.read(filename) )
+ # strip off _id and _rev to avoid conflicts
+ data.delete_if {|k,v| k.start_with? '_'}
+end
+
+ #
+ # update document
+ #
+def set_document(db, id, data)
+ attempt ||= 1
+ doc = get_document(db, id)
+ if doc
+ doc.id ||= id
+ update_document(db, doc, data)
+ else
+ create_document(db, id, data)
+ end
+rescue RestClient::Conflict
+ # retry once, reraise if that does not work
+ raise if attempt > 1
+ attempt += 1
+ retry
+end
+
+COUCH_RESPONSE_OK = { 'ok' => true }
+
+# Deletes document, if exists, with retry
+def delete_document(db, id)
+ attempts ||= 1
+ doc = get_document(db, id)
+ if doc
+ db.delete_doc(doc)
+ else
+ COUCH_RESPONSE_OK
+ end
+rescue RestClient::ExceptionWithResponse => e
+ if attempts < 6 && !e.response.nil? && RETRY_CODES.include?(e.response.code)
+ attempts += 1
+ sleep 10
+ retry
+ else
+ raise e
+ end
+end
+
+def get_document(db, doc_id)
+ begin
+ db.get(doc_id)
+ rescue RestClient::ResourceNotFound
+ nil
+ end
+end
+
+# if the response status code is one of these
+# then retry instead of failing.
+RETRY_CODES = [500, 422].freeze
+
+def update_document(db, doc, data)
+ attempts ||= 1
+ doc.reject! {|k,v| !["_id", "_rev"].include? k}
+ doc.merge! data
+ db.save_doc(doc)
+rescue RestClient::ExceptionWithResponse => e
+ if attempts < 6 && !e.response.nil? && RETRY_CODES.include?(e.response.code)
+ attempts += 1
+ sleep 10
+ retry
+ else
+ raise e
+ end
+end
+
+def create_document(db, doc_id, data)
+ attempts ||= 1
+ data["_id"] = doc_id
+ db.save_doc(data)
+rescue RestClient::ExceptionWithResponse => e
+ if attempts < 6 && !e.response.nil? && RETRY_CODES.include?(e.response.code)
+ attempts += 1
+ sleep 10
+ retry
+ else
+ raise e
+ end
+end
+
+def connection_string(database, host, netrc_file = nil)
+ protocol = "http"
+ #hostname = "127.0.0.1"
+ port = "5984"
+ username = "admin"
+ password = ""
+
+ netrc = File.read(netrc_file || '/etc/couchdb/couchdb.netrc')
+ netrc.scan(/\w+ [\w\.]+/).each do |key_value|
+ key, value = key_value.split ' '
+ case key
+ when "machine" then host ||= value + ':' + port
+ when "login" then username = value
+ when "password" then password = value
+ end
+ end
+
+ host ||= '127.0.0.1:5984'
+
+ "%s://%s:%s@%s/%s" % [protocol, username, password, host, database]
+end
+
+def usage(s)
+ $stderr.puts(s)
+ $stderr.puts("Usage: #{File.basename($0)} --host <host> --db <db> --id <doc_id> --data <json> [--file <file>] [--netrc-file <netrc-file>]")
+ $stderr.puts(" #{File.basename($0)} --host <host> --db <db> --id <doc_id> --delete [--netrc-file <netrc-file>]")
+ exit(2)
+end
+
+main()
diff --git a/puppet/modules/couchdb/files/local.ini b/puppet/modules/couchdb/files/local.ini
new file mode 100644
index 00000000..7365b6c6
--- /dev/null
+++ b/puppet/modules/couchdb/files/local.ini
@@ -0,0 +1,84 @@
+; CouchDB Configuration Settings
+
+; Custom settings should be made in this file. They will override settings
+; in default.ini, but unlike changes made to default.ini, this file won't be
+; overwritten on server upgrade.
+
+[couchdb]
+;max_document_size = 4294967296 ; bytes
+
+[httpd]
+;port = 5984
+;bind_address = 127.0.0.1
+; Options for the MochiWeb HTTP server.
+;server_options = [{backlog, 128}, {acceptor_pool_size, 16}]
+; For more socket options, consult Erlang's module 'inet' man page.
+;socket_options = [{recbuf, 262144}, {sndbuf, 262144}, {nodelay, true}]
+
+; Uncomment next line to trigger basic-auth popup on unauthorized requests.
+;WWW-Authenticate = Basic realm="administrator"
+
+; Uncomment next line to set the configuration modification whitelist. Only
+; whitelisted values may be changed via the /_config URLs. To allow the admin
+; to change this value over HTTP, remember to include {httpd,config_whitelist}
+; itself. Excluding it from the list would require editing this file to update
+; the whitelist.
+;config_whitelist = [{httpd,config_whitelist}, {log,level}, {etc,etc}]
+
+[httpd_global_handlers]
+;_google = {couch_httpd_proxy, handle_proxy_req, <<"http://www.google.com">>}
+
+[couch_httpd_auth]
+; If you set this to true, you should also uncomment the WWW-Authenticate line
+; above. If you don't configure a WWW-Authenticate header, CouchDB will send
+; Basic realm="server" in order to prevent you getting logged out.
+; require_valid_user = false
+
+[log]
+;level = debug
+
+[os_daemons]
+; For any commands listed here, CouchDB will attempt to ensure that
+; the process remains alive while CouchDB runs as well as shut them
+; down when CouchDB exits.
+;foo = /path/to/command -with args
+
+[daemons]
+; enable SSL support by uncommenting the following line and supply the PEM's below.
+; the default ssl port CouchDB listens on is 6984
+; httpsd = {couch_httpd, start_link, [https]}
+
+[ssl]
+;cert_file = /full/path/to/server_cert.pem
+;key_file = /full/path/to/server_key.pem
+;password = somepassword
+; set to true to validate peer certificates
+verify_ssl_certificates = false
+; Path to file containing PEM encoded CA certificates (trusted
+; certificates used for verifying a peer certificate). May be omitted if
+; you do not want to verify the peer.
+;cacert_file = /full/path/to/cacertf
+; The verification fun (optionnal) if not specidied, the default
+; verification fun will be used.
+;verify_fun = {Module, VerifyFun}
+ssl_certificate_max_depth = 1
+; To enable Virtual Hosts in CouchDB, add a vhost = path directive. All requests to
+; the Virual Host will be redirected to the path. In the example below all requests
+; to http://example.com/ are redirected to /database.
+; If you run CouchDB on a specific port, include the port number in the vhost:
+; example.com:5984 = /database
+
+[vhosts]
+;example.com = /database/
+
+[update_notification]
+;unique notifier name=/full/path/to/exe -with "cmd line arg"
+
+; To create an admin account uncomment the '[admins]' section below and add a
+; line in the format 'username = password'. When you next start CouchDB, it
+; will change the password to a hash (so that your passwords don't linger
+; around in plain-text files). You can add more admin accounts with more
+; 'username = password' lines. Don't forget to restart CouchDB after
+; changing this.
+[admins]
+;admin = mysecretpassword
diff --git a/puppet/modules/couchdb/lib/facter/couchdb_pwhash_alg.rb b/puppet/modules/couchdb/lib/facter/couchdb_pwhash_alg.rb
new file mode 100644
index 00000000..60ae701a
--- /dev/null
+++ b/puppet/modules/couchdb/lib/facter/couchdb_pwhash_alg.rb
@@ -0,0 +1,43 @@
+require 'facter'
+
+def version_parts ( version )
+ # gives back a hash containing major, minor and patch numbers
+ # of a give version string
+
+ parts = Hash.new
+ first, *rest = version.split(".")
+ parts["major"] = first
+ parts["minor"] = rest[0]
+ parts["patch"] = rest[1]
+ return parts
+end
+
+def couchdb_pwhash_alg
+ # couchdb uses sha1 as pw hash algorithm until v. 1.2,
+ # but pbkdf2 from v.1.3 on.
+ # see http://docs.couchdb.org/en/1.4.x/configuring.html for
+ # details
+
+ couchdb_version = Facter.value(:couchdb_version)
+ version = version_parts(couchdb_version)
+ major = version["major"].to_i
+ alg = case major
+ when 0 then alg = 'n/a'
+ when 1 then
+ minor = version['minor'].to_i
+ if minor < 3
+ alg = 'sha1'
+ else
+ alg = 'pbkdf2'
+ end
+ else
+ alg = 'pbkdf2'
+ end
+ return alg
+end
+
+Facter.add(:couchdb_pwhash_alg) do
+ setcode do
+ couchdb_pwhash_alg
+ end
+end
diff --git a/puppet/modules/couchdb/lib/facter/couchdb_version.rb b/puppet/modules/couchdb/lib/facter/couchdb_version.rb
new file mode 100644
index 00000000..3a721169
--- /dev/null
+++ b/puppet/modules/couchdb/lib/facter/couchdb_version.rb
@@ -0,0 +1,34 @@
+require 'facter'
+
+def deb_installed_version ( name )
+ # returns an empty string if package is not installed,
+ # otherwise the version
+
+ version = `apt-cache policy #{name} | grep Installed 2>&1`
+ version.slice! " Installed: "
+ version.slice! "(none)"
+ return version.strip.chomp
+end
+
+def couchdb_version
+ bigcouch = deb_installed_version("bigcouch")
+ if bigcouch.empty?
+ couchdb = deb_installed_version("couchdb")
+ if couchdb.empty?
+ version = 'n/a'
+ else
+ version = couchdb
+ end
+ else
+ # bigcouch is currently only available in one version (0.4.2),
+ # which includes couchdb 1.1.1
+ version = '1.1.1'
+ end
+ return version
+end
+
+Facter.add(:couchdb_version) do
+ setcode do
+ couchdb_version
+ end
+end
diff --git a/puppet/modules/couchdb/lib/puppet/parser/functions/couchdblookup.rb b/puppet/modules/couchdb/lib/puppet/parser/functions/couchdblookup.rb
new file mode 100644
index 00000000..b9067d2a
--- /dev/null
+++ b/puppet/modules/couchdb/lib/puppet/parser/functions/couchdblookup.rb
@@ -0,0 +1,55 @@
+#
+# A basic function to retrieve data in couchdb
+#
+
+
+module Puppet::Parser::Functions
+ newfunction(:couchdblookup, :type => :rvalue) do |args|
+ require 'json'
+ require 'open-uri'
+
+ raise Puppet::ParseError, ("couchdblookup(): wrong number of arguments (#{args.length}; must be 2 or 3)") unless args.length.between?(2, 3)
+
+ url = args[0]
+ key = args[1]
+ default = args[2] if args.length >= 3
+
+ begin
+ json = JSON.parse(open(URI.parse(url)).read)
+ rescue OpenURI::HTTPError => error
+ raise Puppet::ParseError, "couchdblookup(): fetching URL #{url} failed with status '#{error.message}'"
+ rescue Timeout::Error => error
+ raise Puppet::ParseError, "couchdblookup(): connection to couchdb server timed out: '#{error.message}'"
+ rescue Errno::ECONNREFUSED => error
+ raise Puppet::ParseError, "couchdblookup(): connection to couchdb server failed: '#{error.message}'"
+ rescue JSON::ParserError => error
+ raise Puppet::ParseError, "couchdblookup(): failed to parse JSON received from couchdb: '#{error.message}'"
+ rescue StandardError => error
+ raise Puppet::ParseError, "couchdblookup(): something unexpected happened: '#{error.inspect}'"
+ end
+
+ result = nil
+
+ if json.has_key?("rows")
+
+ if json['rows'].length > 1
+ arr = json['rows'].collect do |x|
+ x[key] if x.is_a?(Hash) and x.has_key?(key)
+ end
+ arr.compact!
+ result = arr unless arr.empty?
+
+ elsif json['rows'].length == 1
+ hash = json['rows'].pop
+ result = hash[key] if hash.is_a?(Hash)
+ end
+
+ elsif json.has_key?(key)
+ result = json[key]
+ end
+
+ result or default or raise Puppet::ParseError, "couchdblookup(): key '#{key}' not found in JSON object !"
+
+ end
+end
+
diff --git a/puppet/modules/couchdb/lib/puppet/parser/functions/pbkdf2.rb b/puppet/modules/couchdb/lib/puppet/parser/functions/pbkdf2.rb
new file mode 100644
index 00000000..46400c9c
--- /dev/null
+++ b/puppet/modules/couchdb/lib/puppet/parser/functions/pbkdf2.rb
@@ -0,0 +1,62 @@
+#
+# pbkdf2.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:pbkdf2, :type => :rvalue, :doc => <<-EOS
+This converts a password and a salt (and optional iterations and keylength
+parameters) to a hash containing the salted SHA1 password hash, salt,
+iterations and keylength.
+pbkdf2 is used i.e. for couchdb passwords since v1.3.
+
+Example usage:
+ $pbkdf2 = pbkdf2($::couchdb::admin_pw, $::couchdb::admin_salt)
+ $sha1 = $pbkdf2['sha1']
+EOS
+ ) do |arguments|
+ require 'openssl'
+ require 'base64'
+
+ raise(Puppet::ParseError, "pbkdf2(): Wrong number of arguments " +
+ "passed (#{arguments.size} but we require at least 2)") if arguments.size < 2
+
+ unless arguments.is_a?(Array)
+ raise(Puppet::ParseError, 'pbkdf2(): Requires a ' +
+ "Array argument, you passed: #{password.class}")
+ end
+
+ password = arguments[0]
+ salt = arguments[1]
+
+ if arguments.size > 2
+ iterations = arguments[2].to_i
+ else
+ iterations = 1000
+ end
+
+ if arguments.size > 3
+ keylength = arguments[3].to_i
+ else
+ keylength = 20
+ end
+
+ pbkdf2 = OpenSSL::PKCS5::pbkdf2_hmac_sha1(
+ password,
+ salt,
+ iterations,
+ keylength
+ )
+
+ return_hash = Hash.new()
+ # return hex encoded string
+ return_hash['sha1'] = pbkdf2.unpack('H*')[0]
+ return_hash['password'] = password
+ return_hash['salt'] = salt
+ return_hash['iterations'] = iterations
+ return_hash['keylength'] = keylength
+
+ return return_hash
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/couchdb/manifests/add_user.pp b/puppet/modules/couchdb/manifests/add_user.pp
new file mode 100644
index 00000000..29c6a8c8
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/add_user.pp
@@ -0,0 +1,39 @@
+define couchdb::add_user ( $roles, $pw, $salt = '' ) {
+ # Couchdb < 1.2 needs a pre-hashed pw and salt
+ # If you provide a salt, couchdb::add_user will assume that
+ # $pw is prehashed and pass both parameters to couchdb::update
+ # If $salt is empty, couchdb::add_user will assume that the pw
+ # is plaintext and will pass it to couchdb::update
+
+ if $::couchdb::bigcouch == true {
+ $port = 5986
+ } else {
+ $port = 5984
+ }
+
+ if $salt == '' {
+ # unhashed, plaintext pw, no salt. For couchdb >= 1.2
+ $data = "{\"type\": \"user\", \"name\": \"${name}\", \"roles\": ${roles}, \"password\": \"${pw}\"}"
+ } else {
+ # prehashed pw with salt, for couchdb < 1.2
+ # salt and encrypt pw
+ # str_and_salt2sha1 is a function from leap's stdlib module
+ $pw_and_salt = [ $pw, $salt ]
+ $sha = str_and_salt2sha1($pw_and_salt)
+ $data = "{\"type\": \"user\", \"name\": \"${name}\", \"roles\": ${roles}, \"password_sha\": \"${sha}\", \"salt\": \"${salt}\"}"
+ }
+
+ # update the user with the given password unless they already work
+ couchdb::document { "update_user_${name}":
+ host => "127.0.0.1:${port}",
+ db => '_users',
+ id => "org.couchdb.user:${name}",
+ data => $data
+ }
+
+ couchdb::query::setup { $name:
+ user => $name,
+ pw => $pw,
+ }
+
+}
diff --git a/puppet/modules/couchdb/manifests/backup.pp b/puppet/modules/couchdb/manifests/backup.pp
new file mode 100644
index 00000000..a477b5b1
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/backup.pp
@@ -0,0 +1,51 @@
+# configure backup using couchdb-backup.py
+class couchdb::backup {
+
+ include couchdb::params
+
+ # used in ERB templates
+ $bind_address = $couchdb::params::bind_address
+ $port = $couchdb::params::port
+ $backupdir = $couchdb::params::backupdir
+
+ file { $couchdb::params::backupdir:
+ ensure => directory,
+ mode => '0755',
+ require => Package['couchdb'],
+ }
+
+ file { '/usr/local/sbin/couchdb-backup.py':
+ ensure => present,
+ owner => root,
+ group => root,
+ mode => '0755',
+ content => template('couchdb/couchdb-backup.py.erb'),
+ require => File[$couchdb::params::backupdir],
+ }
+
+ cron { 'couchdb-backup':
+ command => '/usr/local/sbin/couchdb-backup.py 2> /dev/null',
+ hour => 3,
+ minute => 0,
+ require => File['/usr/local/sbin/couchdb-backup.py'],
+ }
+
+ case $::operatingsystem {
+ /Debian|Ubunu/: {
+ # note: python-couchdb >= 0.8 required, which is found in debian wheezy.
+ ensure_packages (['python-couchdb', 'python-simplejson'], {
+ before => File['/usr/local/sbin/couchdb-backup.py']
+ })
+ }
+ /RedHat|Centos/: {
+ exec {'install python-couchdb using easy_install':
+ command => 'easy_install http://pypi.python.org/packages/2.6/C/CouchDB/CouchDB-0.8-py2.6.egg',
+ creates => '/usr/lib/python2.6/site-packages/CouchDB-0.8-py2.6.egg',
+ }
+ }
+ default: {
+ err('This module has not been written to support your operating system')
+ }
+ }
+
+}
diff --git a/puppet/modules/couchdb/manifests/base.pp b/puppet/modules/couchdb/manifests/base.pp
new file mode 100644
index 00000000..6c7bf25f
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/base.pp
@@ -0,0 +1,124 @@
+# configure couchdb
+class couchdb::base {
+
+ if $::couchdb::bigcouch == true {
+ $couchdb_user = 'bigcouch'
+ include couchdb::bigcouch
+ } else {
+ $couchdb_user = 'couchdb'
+ }
+
+ # we use package{} here because bigcouch.pp overwrites it and
+ # this won't work with ensure_packages()
+ package {'couchdb':
+ ensure => installed
+ }
+
+ service { 'couchdb':
+ ensure => running,
+ hasstatus => true,
+ enable => true,
+ require => Package['couchdb']
+ }
+
+ # todo: make host/port configurable
+ exec { 'wait_for_couchdb':
+ command => 'wget --retry-connrefused --tries 10 --quiet "http://127.0.0.1:5984" -O /dev/null',
+ require => Service['couchdb']
+ }
+
+
+ # couchrest gem is required for couch-doc-update script,
+ # and it needs the ruby-dev package installed to build
+
+ if versioncmp($::operatingsystemrelease, '8') < 0 {
+ $couchrest_version = '1.2'
+ }
+ else {
+ # couchrest v1.2.1 doesn't build with default debian jessie rake version
+ # shipped as debian package (10.3.2)
+ # see https://leap.se/code/issues/7754
+ $couchrest_version = '1.2.0'
+ }
+
+ ensure_packages('ruby-dev')
+ ensure_packages('couchrest', {
+ provider => 'gem',
+ ensure => $couchrest_version,
+ require => Package['ruby-dev']
+ })
+
+ File['/usr/local/bin/couch-doc-update'] -> Couchdb::Update <| |>
+ File['/usr/local/bin/couch-doc-diff'] -> Couchdb::Update <| |>
+
+ Couchdb::Update <| |> -> Couchdb::Document <| |>
+
+ file {
+ '/usr/local/bin/couch-doc-update':
+ source => 'puppet:///modules/couchdb/couch-doc-update',
+ mode => '0755',
+ owner => 'root',
+ group => 'root',
+ require => Package['couchrest'];
+
+ '/usr/local/bin/couch-doc-diff':
+ source => 'puppet:///modules/couchdb/couch-doc-diff',
+ mode => '0755',
+ owner => 'root',
+ group => 'root',
+ require => Package['couchrest'];
+
+ '/etc/couchdb/local.ini':
+ source => [ "puppet:///modules/site_couchdb/${::fqdn}/local.ini",
+ 'puppet:///modules/site_couchdb/local.ini',
+ 'puppet:///modules/couchdb/local.ini' ],
+ notify => Service[couchdb],
+ owner => $couchdb_user,
+ group => $couchdb_user,
+ mode => '0660',
+ require => Package['couchdb'];
+
+ '/etc/couchdb/local.d':
+ ensure => directory,
+ require => Package['couchdb'];
+ }
+
+ $alg = $::couchdb::pwhash_alg
+ $salt = $::couchdb::admin_salt
+ case $alg {
+ 'sha1': {
+ # str_and_salt2sha1 is a function from leap's stdlib module
+ $pw_and_salt = [ $::couchdb::admin_pw, $salt ]
+ $sha1 = str_and_salt2sha1($pw_and_salt)
+ $admin_hash = "-hashed-${sha1},${salt}"
+ }
+ 'pbkdf2': {
+ $pbkdf2 = pbkdf2($::couchdb::admin_pw, $::couchdb::admin_salt, 10)
+ $sha1 = $pbkdf2['sha1']
+ $admin_hash = "-pbkdf2-${sha1},${salt},10"
+ }
+ default: { fail ("Unknown fact couchdb_pwhash_alg ${::couchdb_pwhash_alg} - Exiting.") }
+ }
+
+ file { '/etc/couchdb/local.d/admin.ini':
+ content => template('couchdb/admin.ini.erb'),
+ mode => '0600',
+ owner => $couchdb_user,
+ group => $couchdb_user,
+ notify => Service[couchdb],
+ require => File['/etc/couchdb/local.d'];
+ }
+
+ case $::couchdb::bigcouch {
+ true: { $restart_command = '/etc/init.d/bigcouch restart; sleep 6' }
+ default: { $restart_command = '/etc/init.d/couchdb restart; sleep 6' }
+ }
+
+ exec { 'couchdb_restart':
+ command => $restart_command,
+ path => ['/bin', '/usr/bin',],
+ subscribe => File['/etc/couchdb/local.d/admin.ini',
+ '/etc/couchdb/local.ini'],
+ refreshonly => true
+ }
+}
diff --git a/puppet/modules/couchdb/manifests/bigcouch.pp b/puppet/modules/couchdb/manifests/bigcouch.pp
new file mode 100644
index 00000000..a97411bf
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/bigcouch.pp
@@ -0,0 +1,51 @@
+class couchdb::bigcouch inherits couchdb::base {
+
+ file {
+ '/opt/bigcouch':
+ ensure => directory,
+ mode => '0755';
+
+ '/etc/couchdb':
+ ensure => directory,
+ mode => '0755',
+ before => Package['couchdb'];
+
+ '/opt/bigcouch/etc':
+ ensure => link,
+ target => '/etc/couchdb',
+ before => Package['couchdb'];
+ }
+
+ # there's no bigcouch in the official debian repo, you need
+ # to setup a repository for that. You can use class
+ # couchdb::bigcouch::package::cloudant for unauthenticated 0.4.0 packages,
+ # or site_apt::leap_repo from the leap_platfrom repository
+ # for signed 0.4.2 packages
+
+ Package['couchdb'] {
+ name => 'bigcouch'
+ }
+
+ file { '/opt/bigcouch/etc/vm.args':
+ content => template('couchdb/bigcouch/vm.args'),
+ mode => '0640',
+ owner => 'bigcouch',
+ group => 'bigcouch',
+ require => Package['couchdb'],
+ notify => Service[couchdb]
+ }
+
+ file { '/opt/bigcouch/etc/default.ini':
+ content => template('couchdb/bigcouch/default.ini'),
+ mode => '0640',
+ owner => 'bigcouch',
+ group => 'bigcouch',
+ require => Package['couchdb'],
+ notify => Service[couchdb]
+ }
+
+ Service['couchdb'] {
+ name => 'bigcouch'
+ }
+
+}
diff --git a/puppet/modules/couchdb/manifests/bigcouch/add_node.pp b/puppet/modules/couchdb/manifests/bigcouch/add_node.pp
new file mode 100644
index 00000000..ed9db94b
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/bigcouch/add_node.pp
@@ -0,0 +1,8 @@
+define couchdb::bigcouch::add_node {
+
+ couchdb::bigcouch::document { "add_${name}":
+ db => 'nodes',
+ id => "bigcouch@${name}",
+ ensure => 'present'
+ }
+}
diff --git a/puppet/modules/couchdb/manifests/bigcouch/debian.pp b/puppet/modules/couchdb/manifests/bigcouch/debian.pp
new file mode 100644
index 00000000..645c6da8
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/bigcouch/debian.pp
@@ -0,0 +1,11 @@
+class couchdb::bigcouch::debian inherits couchdb::debian {
+
+ File['/etc/init.d/couchdb'] {
+ ensure => absent
+ }
+
+ file {'/etc/init.d/bigcouch':
+ ensure => link,
+ target => '/usr/bin/sv'
+ }
+}
diff --git a/puppet/modules/couchdb/manifests/bigcouch/document.pp b/puppet/modules/couchdb/manifests/bigcouch/document.pp
new file mode 100644
index 00000000..13f4ac17
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/bigcouch/document.pp
@@ -0,0 +1,14 @@
+define couchdb::bigcouch::document (
+ $db,
+ $id,
+ $host = '127.0.0.1:5986',
+ $data ='{}',
+ $ensure ='content') {
+ couchdb::document { $name:
+ ensure => $ensure,
+ host => $host,
+ db => $db,
+ id => $id,
+ data => $data
+ }
+}
diff --git a/puppet/modules/couchdb/manifests/bigcouch/package/cloudant.pp b/puppet/modules/couchdb/manifests/bigcouch/package/cloudant.pp
new file mode 100644
index 00000000..cfdcf10c
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/bigcouch/package/cloudant.pp
@@ -0,0 +1,35 @@
+class couchdb::bigcouch::package::cloudant (
+ $ensure = 'present'
+) {
+
+ # cloudant's signing key can be fetched from
+ # http://packages.cloudant.com/KEYS, please use the apt module to
+ # distribute it on your servers after verifying its fingerprint
+
+ # cloudant's wheezy repo will fail cause in their Release file
+ # (http://packages.cloudant.com/debian/dists/wheezy/Release) they
+ # wrongly marked the packages for squeeze
+ # so we will use their squeeze repo here
+ apt::sources_list {'bigcouch-cloudant.list':
+ ensure => $ensure,
+ content => 'deb http://packages.cloudant.com/debian squeeze main'
+ }
+
+ # right now, cloudant only provides authenticated bigcouch 0.4.2 packages
+ # for squeeze, therefore we need to allow the installation of the depending
+ # packages libicu44 and libssl0.9.8 from squeeze
+
+ if $::lsbdistcodename == 'wheezy' {
+ apt::sources_list {'squeeze.list':
+ ensure => $ensure,
+ content => 'deb http://http.debian.net/debian squeeze main
+deb http://security.debian.org/ squeeze/updates main
+' }
+ apt::preferences_snippet { 'bigcouch_squeeze_deps':
+ ensure => $ensure,
+ package => 'libicu44 libssl0.9.8',
+ priority => '980',
+ pin => 'release o=Debian,n=squeeze'
+ }
+ }
+}
diff --git a/puppet/modules/couchdb/manifests/create_db.pp b/puppet/modules/couchdb/manifests/create_db.pp
new file mode 100644
index 00000000..8a8d1144
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/create_db.pp
@@ -0,0 +1,21 @@
+define couchdb::create_db (
+ $host='127.0.0.1:5984',
+ $admins="{\"names\": [], \"roles\": [] }",
+ $members="{\"names\": [], \"roles\": [] }" )
+{
+
+ couchdb::query { "create_db_${name}":
+ cmd => 'PUT',
+ host => $host,
+ path => $name,
+ unless => "/usr/bin/curl -s -f --netrc-file /etc/couchdb/couchdb.netrc ${host}/${name}"
+ }
+
+ couchdb::document { "${name}_security":
+ db => $name,
+ id => '_security',
+ host => $host,
+ data => "{ \"admins\": ${admins}, \"members\": ${members} }",
+ require => Couchdb::Query["create_db_${name}"]
+ }
+}
diff --git a/puppet/modules/couchdb/manifests/debian.pp b/puppet/modules/couchdb/manifests/debian.pp
new file mode 100644
index 00000000..b83b227a
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/debian.pp
@@ -0,0 +1,15 @@
+# installs initscript and dependent packages on debian
+class couchdb::debian inherits couchdb::base {
+
+ ensure_packages('libjs-jquery')
+
+ file { '/etc/init.d/couchdb':
+ source => [
+ 'puppet:///modules/site_couchdb/Debian/couchdb',
+ 'puppet:///modules/couchdb/Debian/couchdb' ],
+ mode => '0755',
+ owner => 'root',
+ group => 'root',
+ require => Package['couchdb']
+ }
+}
diff --git a/puppet/modules/couchdb/manifests/deploy_config.pp b/puppet/modules/couchdb/manifests/deploy_config.pp
new file mode 100644
index 00000000..2ce1fd20
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/deploy_config.pp
@@ -0,0 +1,12 @@
+class couchdb::deploy_config {
+
+ file { '/etc/couchdb/local.ini':
+ source => [ "puppet:///modules/site_couchdb/${::fqdn}/local.ini",
+ 'puppet:///modules/site_couchdb/local.ini',
+ 'puppet:///modules/couchdb/local.ini' ],
+ notify => Service[couchdb],
+ owner => couchdb,
+ group => couchdb,
+ mode => '0660'
+ }
+}
diff --git a/puppet/modules/couchdb/manifests/document.pp b/puppet/modules/couchdb/manifests/document.pp
new file mode 100644
index 00000000..6180474b
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/document.pp
@@ -0,0 +1,47 @@
+# Usage:
+# couchdb::document { id:
+# db => "database",
+# data => "content",
+# ensure => {absent,present,*content*}
+# }
+#
+define couchdb::document(
+ $db,
+ $id,
+ $host = '127.0.0.1:5984',
+ $data = '{}',
+ $netrc = '/etc/couchdb/couchdb.netrc',
+ $ensure = 'content') {
+
+ $url = "${host}/${db}/${id}"
+
+ case $ensure {
+ default: { err ( "unknown ensure value '${ensure}'" ) }
+ content: {
+ exec { "couch-doc-update --netrc-file ${netrc} --host ${host} --db ${db} --id ${id} --data \'${data}\'":
+ require => Exec['wait_for_couchdb'],
+ unless => "couch-doc-diff $url '$data'"
+ }
+ }
+
+ present: {
+ couchdb::query { "create_${db}_${id}":
+ cmd => 'PUT',
+ host => $host,
+ path => "${db}/${id}",
+ require => Exec['wait_for_couchdb'],
+ unless => "/usr/bin/curl -s -f --netrc-file ${netrc} ${url}"
+ }
+ }
+
+ absent: {
+ couchdb::query { "destroy_${db}_${id}":
+ cmd => 'DELETE',
+ host => $host,
+ path => "${db}/${id}",
+ require => Exec['wait_for_couchdb'],
+ unless => "/usr/bin/curl -s -f --netrc-file ${netrc} ${url}"
+ }
+ }
+ }
+}
diff --git a/puppet/modules/couchdb/manifests/init.pp b/puppet/modules/couchdb/manifests/init.pp
new file mode 100644
index 00000000..12598ba0
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/init.pp
@@ -0,0 +1,31 @@
+# initial couchdb class
+class couchdb (
+ $admin_pw,
+ $admin_salt = '',
+ $bigcouch = false,
+ $bigcouch_cookie = '',
+ $ednp_port = '9001',
+ $chttpd_bind_address = '0.0.0.0',
+ $pwhash_alg = 'pbkdf2' )
+{
+
+ # stdlib is needed i.e. for ensure_packages()
+ include ::stdlib
+
+ case $::operatingsystem {
+ Debian: {
+ case $::lsbdistcodename {
+ /lenny|squeeze|wheezy|jessie/: {
+ include couchdb::debian
+ if $bigcouch == true {
+ include couchdb::bigcouch::debian
+ }
+ }
+ default: { fail "couchdb not available for ${::operatingsystem}/${::lsbdistcodename}" }
+ }
+ }
+ RedHat: { include couchdb::redhat }
+ }
+
+ ensure_packages('curl')
+}
diff --git a/puppet/modules/couchdb/manifests/mirror_db.pp b/puppet/modules/couchdb/manifests/mirror_db.pp
new file mode 100644
index 00000000..b07b6749
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/mirror_db.pp
@@ -0,0 +1,21 @@
+define couchdb::mirror_db (
+ $host='127.0.0.1:5984',
+ $from='',
+ $to='',
+ $user='replication',
+ $role='replication'
+ )
+{
+ $source = "${from}/${name}"
+ if $to == '' { $target = $name }
+ else { $target = "${to}/${name}" }
+
+ couchdb::document { "${name}_replication":
+ db => "_replicator",
+ id => "${name}_replication",
+ netrc => "/etc/couchdb/couchdb-${user}.netrc",
+ host => $host,
+ data => "{ \"source\": \"${source}\", \"target\": \"${target}\", \"continuous\": true, \"user_ctx\": { \"name\": \"${user}\", \"roles\": [\"${role}\"] }, \"owner\": \"${user}\" }",
+ require => Couchdb::Query["create_db_${name}"]
+ }
+}
diff --git a/puppet/modules/couchdb/manifests/params.pp b/puppet/modules/couchdb/manifests/params.pp
new file mode 100644
index 00000000..02d5f02e
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/params.pp
@@ -0,0 +1,23 @@
+class couchdb::params {
+
+ $bind_address = $::couchdb_bind_address ? {
+ '' => '127.0.0.1',
+ default => $::couchdb_bind_address,
+ }
+
+ $port = $::couchdb_port ? {
+ '' => '5984',
+ default => $::couchdb_port,
+ }
+
+ $backupdir = $::couchdb_backupdir ? {
+ '' => '/var/backups/couchdb',
+ default => $::couchdb_backupdir,
+ }
+
+ $cert_path = $::couchdb_cert_path ? {
+ "" => '/etc/couchdb',
+ default => $::couchdb_cert_path,
+ }
+
+}
diff --git a/puppet/modules/couchdb/manifests/query.pp b/puppet/modules/couchdb/manifests/query.pp
new file mode 100644
index 00000000..9507ca1e
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/query.pp
@@ -0,0 +1,12 @@
+define couchdb::query (
+ $cmd, $path,
+ $netrc='/etc/couchdb/couchdb.netrc',
+ $host='127.0.0.1:5984',
+ $data = '{}',
+ $unless = undef) {
+
+ exec { "/usr/bin/curl -s --netrc-file ${netrc} -X ${cmd} ${host}/${path} --data \'${data}\'":
+ require => [ Package['curl'], Exec['wait_for_couchdb'] ],
+ unless => $unless
+ }
+}
diff --git a/puppet/modules/couchdb/manifests/query/setup.pp b/puppet/modules/couchdb/manifests/query/setup.pp
new file mode 100644
index 00000000..451eb536
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/query/setup.pp
@@ -0,0 +1,10 @@
+define couchdb::query::setup ($user, $pw, $host='127.0.0.1') {
+
+ file { "/etc/couchdb/couchdb-${user}.netrc":
+ content => "machine ${host} login ${user} password ${pw}",
+ mode => '0600',
+ owner => $::couchdb::base::couchdb_user,
+ group => $::couchdb::base::couchdb_user,
+ require => Package['couchdb'];
+ }
+}
diff --git a/puppet/modules/couchdb/manifests/redhat.pp b/puppet/modules/couchdb/manifests/redhat.pp
new file mode 100644
index 00000000..defa0a94
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/redhat.pp
@@ -0,0 +1 @@
+class couchdb::redhat inherits couchdb::base {}
diff --git a/puppet/modules/couchdb/manifests/ssl/deploy_cert.pp b/puppet/modules/couchdb/manifests/ssl/deploy_cert.pp
new file mode 100644
index 00000000..d3e743f1
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/ssl/deploy_cert.pp
@@ -0,0 +1,28 @@
+define couchdb::ssl::deploy_cert ($cert, $key) {
+
+ include couchdb::params
+
+ file { 'couchdb_cert_directory':
+ ensure => 'directory',
+ path => $couchdb::params::cert_path,
+ mode => '0600',
+ owner => 'couchdb',
+ group => 'couchdb';
+ }
+
+ file { 'couchdb_cert':
+ path => "${couchdb::params::cert_path}/server_cert.pem",
+ mode => '0644',
+ owner => 'couchdb',
+ group => 'couchdb',
+ content => $cert
+ }
+
+ file { 'couchdb_key':
+ path => "${couchdb::params::cert_path}/server_key.pem",
+ mode => '0600',
+ owner => 'couchdb',
+ group => 'couchdb',
+ content => $key
+ }
+}
diff --git a/puppet/modules/couchdb/manifests/ssl/generate_cert.pp b/puppet/modules/couchdb/manifests/ssl/generate_cert.pp
new file mode 100644
index 00000000..a443250e
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/ssl/generate_cert.pp
@@ -0,0 +1,25 @@
+# configures cert for ssl access
+class couchdb::ssl::generate_cert {
+
+ ensure_packages('openssl')
+
+ file { $couchdb::cert_path:
+ ensure => 'directory',
+ mode => '0600',
+ owner => 'couchdb',
+ group => 'couchdb';
+ }
+
+exec { 'generate-certs':
+ command => "/usr/bin/openssl req -new -inform PEM -x509 -nodes -days 150 -subj \
+'/C=ZZ/ST=AutoSign/O=AutoSign/localityName=AutoSign/commonName=${::hostname}/organizationalUnitName=AutoSign/emailAddress=AutoSign/' \
+-newkey rsa:2048 -out ${couchdb::cert_path}/couchdb_cert.pem -keyout ${couchdb::cert_path}/couchdb_key.pem",
+ unless => "/usr/bin/test -f ${couchdb::cert_path}/couchdb_cert.pem &&
+/usr/bin/test -f ${couchdb::params::cert_path}/couchdb_key.pem",
+ require => [
+ File[$couchdb::params::cert_path],
+ Exec['make-install']
+ ],
+ notify => Service['couchdb'],
+ }
+}
diff --git a/puppet/modules/couchdb/manifests/update.pp b/puppet/modules/couchdb/manifests/update.pp
new file mode 100644
index 00000000..b1dba84c
--- /dev/null
+++ b/puppet/modules/couchdb/manifests/update.pp
@@ -0,0 +1,12 @@
+define couchdb::update (
+ $db,
+ $id,
+ $data,
+ $host='127.0.0.1:5984',
+ $unless=undef) {
+
+ exec { "couch-doc-update --host ${host} --db ${db} --id ${id} --data \'${data}\'":
+ require => Exec['wait_for_couchdb'],
+ unless => $unless
+ }
+}
diff --git a/puppet/modules/couchdb/spec/classes/couchdb_spec.rb b/puppet/modules/couchdb/spec/classes/couchdb_spec.rb
new file mode 100644
index 00000000..e8e4174e
--- /dev/null
+++ b/puppet/modules/couchdb/spec/classes/couchdb_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+describe 'couchdb' do
+ context 'given it is a wheezy system' do
+ let(:params) { {:admin_pw => 'foo'} }
+ let(:facts) do
+ {
+ :operatingsystemrelease => '7',
+ :operatingsystem => 'Debian',
+ :lsbdistcodename => 'wheezy',
+ }
+ end
+ it "should install couchrest 1.2" do
+ should contain_package('couchrest').with({
+ 'ensure'=> '1.2',
+ })
+ end
+ end
+ context 'given it is a jessie system' do
+ let(:params) { {:admin_pw => 'foo'} }
+ let(:facts) do
+ {
+ :operatingsystemrelease => '8',
+ :operatingsystem => 'Debian',
+ :lsbdistcodename => 'jessie',
+ }
+ end
+ it "should install latest couchrest version" do
+ should contain_package('couchrest').with({
+ 'ensure'=> 'latest',
+ })
+ end
+ end
+end
+
diff --git a/puppet/modules/couchdb/spec/fixtures/manifests/site.pp b/puppet/modules/couchdb/spec/fixtures/manifests/site.pp
new file mode 100644
index 00000000..a959fb77
--- /dev/null
+++ b/puppet/modules/couchdb/spec/fixtures/manifests/site.pp
@@ -0,0 +1,8 @@
+# set a default exec path
+# the logoutput exec parameter defaults to "on_error" in puppet 3,
+# but to "false" in puppet 2.7, so we need to set this globally here
+Exec {
+ logoutput => on_failure,
+ path => '/usr/bin:/usr/sbin/:/bin:/sbin:/usr/local/bin:/usr/local/sbin'
+}
+
diff --git a/puppet/modules/couchdb/spec/functions/versioncmp_spec.rb b/puppet/modules/couchdb/spec/functions/versioncmp_spec.rb
new file mode 100644
index 00000000..0a244275
--- /dev/null
+++ b/puppet/modules/couchdb/spec/functions/versioncmp_spec.rb
@@ -0,0 +1,9 @@
+require 'spec_helper'
+
+describe 'versioncmp' do
+ it { should run.with_params('7.2','8').and_return(-1) }
+ it { should run.with_params('7','8').and_return(-1) }
+ it { should run.with_params('8','8').and_return(0) }
+ it { should run.with_params('8.1','8').and_return(1) }
+end
+
diff --git a/puppet/modules/couchdb/spec/spec_helper.rb b/puppet/modules/couchdb/spec/spec_helper.rb
new file mode 100644
index 00000000..b55ede81
--- /dev/null
+++ b/puppet/modules/couchdb/spec/spec_helper.rb
@@ -0,0 +1,9 @@
+require 'rspec-puppet'
+
+fixture_path = File.expand_path(File.join(__FILE__, '..', 'fixtures'))
+
+RSpec.configure do |c|
+ c.module_path = File.join(fixture_path, 'modules')
+ c.manifest_dir = File.join(fixture_path, 'manifests')
+ c.environmentpath = File.join(Dir.pwd, 'spec')
+end
diff --git a/puppet/modules/couchdb/templates/admin.ini.erb b/puppet/modules/couchdb/templates/admin.ini.erb
new file mode 100644
index 00000000..479f8bfc
--- /dev/null
+++ b/puppet/modules/couchdb/templates/admin.ini.erb
@@ -0,0 +1,9 @@
+<%- require 'digest' -%>
+[admins]
+admin = <%= @admin_hash %>
+
+[couchdb]
+<%- # uuid uniquely identifies this couchdb instance. if not set, couchdb will set a random one
+ # but we want a stable one so that this config file doesn't change all the time.
+ # Md5 of hostname and ipaddress seems reasonable, but it could be based on anything. -%>
+uuid = <%= Digest::MD5.hexdigest(Facter.value("hostname") + Facter.value("ipaddress")) %>
diff --git a/puppet/modules/couchdb/templates/bigcouch/default.ini b/puppet/modules/couchdb/templates/bigcouch/default.ini
new file mode 100644
index 00000000..a315ddab
--- /dev/null
+++ b/puppet/modules/couchdb/templates/bigcouch/default.ini
@@ -0,0 +1,172 @@
+[couchdb]
+database_dir = /opt/bigcouch/var/lib
+view_index_dir = /opt/bigcouch/var/lib
+max_document_size = 67108864
+os_process_timeout = 5000
+max_dbs_open = 500
+delayed_commits = false
+
+[cluster]
+; Default number of shards for a new database
+q = 8
+; Default number of copies of each shard
+n = 3
+
+[chttpd]
+port = 5984
+docroot = /opt/bigcouch/share/www
+
+; Options for the MochiWeb HTTP server.
+;server_options = [{backlog, 128}, {acceptor_pool_size, 16}]
+
+; For more socket options, consult Erlang's module 'inet' man page.
+;socket_options = [{recbuf, 262144}, {sndbuf, 262144}, {nodelay, true}]
+
+bind_address = <%= scope.lookupvar('couchdb::chttpd_bind_address') %>
+
+[chttps]
+port = 6984
+
+; cert_file = /full/path/to/server_cert.pem
+; key_file = /full/path/to/server_key.pem
+; password = somepassword
+; also remember to enable the chttps daemon in [daemons] section.
+
+; set to true to validate peer certificates
+verify_ssl_certificates = false
+
+; Path to file containing PEM encoded CA certificates (trusted
+; certificates used for verifying a peer certificate). May be omitted if
+; you do not want to verify the peer.
+;cacert_file = /full/path/to/cacertf
+
+; The verification fun (optional) if not specified, the default
+; verification fun will be used.
+;verify_fun = {Module, VerifyFun}
+ssl_certificate_max_depth = 1
+
+[httpd]
+port = 5986
+bind_address = 127.0.0.1
+authentication_handlers = {couch_httpd_oauth, oauth_authentication_handler}, {couch_httpd_auth, cookie_authentication_handler}, {couch_httpd_auth, default_authentication_handler}
+default_handler = {couch_httpd_db, handle_request}
+secure_rewrites = true
+vhost_global_handlers = _utils, _uuids, _session, _oauth, _users
+allow_jsonp = false
+log_max_chunk_size = 1000000
+
+[ssl]
+port = 6984
+
+[log]
+file = /opt/bigcouch/var/log/bigcouch.log
+level = info
+include_sasl = true
+
+[couch_httpd_auth]
+authentication_db = _users
+authentication_redirect = /_utils/session.html
+require_valid_user = false
+timeout = 43200 ; (default to 12 hours) number of seconds before automatic logout
+auth_cache_size = 50 ; size is number of cache entries
+
+[query_servers]
+javascript = /opt/bigcouch/bin/couchjs /opt/bigcouch/share/couchjs/main.js
+
+[query_server_config]
+reduce_limit = true
+os_process_soft_limit = 100
+
+[daemons]
+view_manager={couch_view, start_link, []}
+external_manager={couch_external_manager, start_link, []}
+query_servers={couch_proc_manager, start_link, []}
+httpd={couch_httpd, start_link, []}
+stats_aggregator={couch_stats_aggregator, start, []}
+stats_collector={couch_stats_collector, start, []}
+uuids={couch_uuids, start, []}
+auth_cache={couch_auth_cache, start_link, []}
+replication_manager={couch_replication_manager, start_link, []}
+vhosts={couch_httpd_vhost, start_link, []}
+os_daemons={couch_os_daemons, start_link, []}
+; Uncomment next line to enable SSL daemon
+; chttpsd = {chttpd, start_link, [https]}
+
+[httpd_global_handlers]
+/ = {couch_httpd_misc_handlers, handle_welcome_req, <<"Welcome">>}
+favicon.ico = {couch_httpd_misc_handlers, handle_favicon_req, "/opt/bigcouch/share/www"}
+
+_utils = {couch_httpd_misc_handlers, handle_utils_dir_req, "/opt/bigcouch/share/www"}
+_all_dbs = {couch_httpd_misc_handlers, handle_all_dbs_req}
+_active_tasks = {couch_httpd_misc_handlers, handle_task_status_req}
+_config = {couch_httpd_misc_handlers, handle_config_req}
+_replicate = {couch_httpd_misc_handlers, handle_replicate_req}
+_uuids = {couch_httpd_misc_handlers, handle_uuids_req}
+_restart = {couch_httpd_misc_handlers, handle_restart_req}
+_stats = {couch_httpd_stats_handlers, handle_stats_req}
+_log = {couch_httpd_misc_handlers, handle_log_req}
+_session = {couch_httpd_auth, handle_session_req}
+_oauth = {couch_httpd_oauth, handle_oauth_req}
+_system = {chttpd_misc, handle_system_req}
+
+[httpd_db_handlers]
+_view_cleanup = {couch_httpd_db, handle_view_cleanup_req}
+_compact = {couch_httpd_db, handle_compact_req}
+_design = {couch_httpd_db, handle_design_req}
+_temp_view = {couch_httpd_view, handle_temp_view_req}
+_changes = {couch_httpd_db, handle_changes_req}
+
+[httpd_design_handlers]
+_view = {couch_httpd_view, handle_view_req}
+_show = {couch_httpd_show, handle_doc_show_req}
+_list = {couch_httpd_show, handle_view_list_req}
+_info = {couch_httpd_db, handle_design_info_req}
+_rewrite = {couch_httpd_rewrite, handle_rewrite_req}
+_update = {couch_httpd_show, handle_doc_update_req}
+
+; enable external as an httpd handler, then link it with commands here.
+; note, this api is still under consideration.
+; [external]
+; mykey = /path/to/mycommand
+
+; Here you can setup commands for CouchDB to manage
+; while it is alive. It will attempt to keep each command
+; alive if it exits.
+; [os_daemons]
+; some_daemon_name = /path/to/script -with args
+
+
+[uuids]
+; Known algorithms:
+; random - 128 bits of random awesome
+; All awesome, all the time.
+; sequential - monotonically increasing ids with random increments
+; First 26 hex characters are random. Last 6 increment in
+; random amounts until an overflow occurs. On overflow, the
+; random prefix is regenerated and the process starts over.
+; utc_random - Time since Jan 1, 1970 UTC with microseconds
+; First 14 characters are the time in hex. Last 18 are random.
+algorithm = sequential
+
+[stats]
+; rate is in milliseconds
+rate = 1000
+; sample intervals are in seconds
+samples = [0, 60, 300, 900]
+
+[attachments]
+compression_level = 8 ; from 1 (lowest, fastest) to 9 (highest, slowest), 0 to disable compression
+compressible_types = text/*, application/javascript, application/json, application/xml
+
+[replicator]
+db = _replicator
+; Maximum replicaton retry count can be a non-negative integer or "infinity".
+max_replication_retry_count = 10
+max_http_sessions = 20
+max_http_pipeline_size = 50
+; set to true to validate peer certificates
+verify_ssl_certificates = false
+; file containing a list of peer trusted certificates (PEM format)
+; ssl_trusted_certificates_file = /etc/ssl/certs/ca-certificates.crt
+; maximum peer certificate depth (must be set even if certificate validation is off)
+ssl_certificate_max_depth = 3
diff --git a/puppet/modules/couchdb/templates/bigcouch/vm.args b/puppet/modules/couchdb/templates/bigcouch/vm.args
new file mode 100644
index 00000000..4618a52c
--- /dev/null
+++ b/puppet/modules/couchdb/templates/bigcouch/vm.args
@@ -0,0 +1,32 @@
+# Each node in the system must have a unique name. A name can be short
+# (specified using -sname) or it can by fully qualified (-name). There can be
+# no communication between nodes running with the -sname flag and those running
+# with the -name flag.
+-name bigcouch
+
+# All nodes must share the same magic cookie for distributed Erlang to work.
+# Comment out this line if you synchronized the cookies by other means (using
+# the ~/.erlang.cookie file, for example).
+-setcookie <%= scope.lookupvar('couchdb::bigcouch_cookie') %>
+
+# Tell SASL not to log progress reports
+-sasl errlog_type error
+
+# Use kernel poll functionality if supported by emulator
++K true
+
+# Start a pool of asynchronous IO threads
++A 16
+
+# Comment this line out to enable the interactive Erlang shell on startup
++Bd -noinput
+
+# read config files
+# otherwise /etc/couchdb/local.d/admin.ini wouldn't be read mysteriously
+-couch_ini /etc/couchdb/default.ini /etc/couchdb/local.ini /etc/couchdb/local.d/admin.ini /etc/couchdb/default.ini /etc/couchdb/local.ini /etc/couchdb/local.d/admin.ini
+#
+
+# make firewalling easier, see
+# http://stackoverflow.com/questions/8459949/bigcouch-cluster-connection-issue#comment10467603_8463814
+
+-kernel inet_dist_listen_min <%= scope.lookupvar('couchdb::ednp_port') %> inet_dist_use_interface "{127,0,0,1}"
diff --git a/puppet/modules/couchdb/templates/couchdb-backup.py.erb b/puppet/modules/couchdb/templates/couchdb-backup.py.erb
new file mode 100644
index 00000000..c49df65b
--- /dev/null
+++ b/puppet/modules/couchdb/templates/couchdb-backup.py.erb
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+# file manage by puppet
+
+import os
+import gzip
+import tarfile
+import datetime
+import urllib2
+import simplejson
+import couchdb.tools.dump
+from os.path import join
+
+DB_URL="http://127.0.0.1:5984"
+DUMP_DIR="<%= backupdir %>"
+TODAY=datetime.datetime.today().strftime("%A").lower()
+
+ftar = os.path.join(DUMP_DIR,"%s.tar" % TODAY)
+tmp_ftar = os.path.join(DUMP_DIR,"_%s.tar" % TODAY)
+tar = tarfile.open(tmp_ftar, "w")
+
+databases = simplejson.load(urllib2.urlopen("%s/_all_dbs" % DB_URL))
+
+for db in databases:
+ db_file = os.path.join(DUMP_DIR,"%s.gz" % db)
+ f = gzip.open(db_file, 'wb')
+ couchdb.tools.dump.dump_db(os.path.join(DB_URL,db), output=f)
+ f.close()
+ tar.add(db_file,"%s.gz" % db)
+ os.remove(db_file)
+
+tar.close()
+os.rename(tmp_ftar,ftar)
diff --git a/puppet/modules/git/files/config/CentOS/git-daemon b/puppet/modules/git/files/config/CentOS/git-daemon
new file mode 100644
index 00000000..a9b208c2
--- /dev/null
+++ b/puppet/modules/git/files/config/CentOS/git-daemon
@@ -0,0 +1,26 @@
+# git-daemon config file
+
+# location of the lockfile
+#LOCKFILE=/var/lock/subsys/git-daemon
+
+# which directory to server
+#GITDIR=/srv/git
+
+# do we serve vhosts?
+# setting this to yes assumes that you
+# have in $GITDIR per vhost to serve
+# a subdirectory containing their repos.
+# for example:
+# - /srv/git/git.example.com
+# - /srv/git/git.example.org
+#GITVHOST=no
+
+# the user git-daemon should run with
+#GITUSER=nobody
+
+# options for the daemon
+#OPTIONS="--reuseaddr --verbose --detach"
+
+# location of the daemon
+#GITDAEMON=/usr/bin/git-daemon
+
diff --git a/puppet/modules/git/files/config/CentOS/git-daemon.vhosts b/puppet/modules/git/files/config/CentOS/git-daemon.vhosts
new file mode 100644
index 00000000..62bb9d4b
--- /dev/null
+++ b/puppet/modules/git/files/config/CentOS/git-daemon.vhosts
@@ -0,0 +1,27 @@
+# git-daemon config file
+
+# location of the lockfile
+#LOCKFILE=/var/lock/subsys/git-daemon
+
+# which directory to server
+#GITDIR=/srv/git
+
+# do we serve vhosts?
+# setting this to yes assumes that you
+# have in $GITDIR per vhost to serve
+# a subdirectory containing their repos.
+# for example:
+# - /srv/git/git.example.com
+# - /srv/git/git.example.org
+#GITVHOST=no
+GITVHOST=yes
+
+# the user git-daemon should run with
+#GITUSER=nobody
+
+# options for the daemon
+#OPTIONS="--reuseaddr --verbose --detach"
+
+# location of the daemon
+#GITDAEMON=/usr/bin/git-daemon
+
diff --git a/puppet/modules/git/files/config/Debian/git-daemon b/puppet/modules/git/files/config/Debian/git-daemon
new file mode 100644
index 00000000..b25e1e7f
--- /dev/null
+++ b/puppet/modules/git/files/config/Debian/git-daemon
@@ -0,0 +1,22 @@
+# Defaults for the git-daemon initscript
+
+# Set to yes to start git-daemon
+RUN=yes
+
+# Set to the user and group git-daemon should run as
+USER=nobody
+GROUP=nogroup
+
+# Set the base path and the directory where the repositories are.
+REPOSITORIES="/srv/git"
+
+# Provide a way to have custom setup.
+#
+# Note, when ADVANCED_OPTS is defined the REPOSITORIES setting is ignored,
+# so take good care to specify exactly what git-daemon have to do.
+#
+# Here is an example from the man page:
+#ADVANCED_OPTS="--verbose --export-all \
+# --interpolated-path=/pub/%IP/%D \
+# /pub/192.168.1.200/software \
+# /pub/10.10.220.23/software"
diff --git a/puppet/modules/git/files/init.d/CentOS/git-daemon b/puppet/modules/git/files/init.d/CentOS/git-daemon
new file mode 100644
index 00000000..aed20756
--- /dev/null
+++ b/puppet/modules/git/files/init.d/CentOS/git-daemon
@@ -0,0 +1,75 @@
+#!/bin/bash
+# puppet Init script for running the git-daemon
+#
+# Author: Marcel Haerry <mh+rpms(at)immerda.ch>
+#
+# chkconfig: - 98 02
+#
+# description: Enables the git-daemon to serve various directories. By default it serves /srv/git
+# processname: git-daemon
+# config: /etc/sysconfig/git-daemon
+
+PATH=/usr/bin:/sbin:/bin:/usr/sbin
+export PATH
+
+[ -f /etc/sysconfig/git-daemon ] && . /etc/sysconfig/git-daemon
+lockfile=${LOCKFILE-/var/lock/subsys/git-daemon}
+gitdir=${GITDIR-/srv/git}
+gitvhost=${GITVHOST-no}
+user=${GITUSER-nobody}
+options=${OPTIONS-"--reuseaddr --verbose --detach"}
+gitdaemon=${GITDAEMON-/usr/bin/git-daemon}
+RETVAL=0
+
+gitoptions="--user=${user} ${options}"
+if [ $gitvhost = yes ]; then
+ gitoptions="${gitoptions} --interpolated-path=${gitdir}/%H/%D"
+else
+ gitoptions="${gitoptions} --base-path=${gitdir}"
+fi
+
+# Source function library.
+. /etc/rc.d/init.d/functions
+
+start() {
+ echo -n $"Starting git-daemon: "
+ daemon $gitdaemon $gitoptions
+ RETVAL=$?
+ echo
+ [ $RETVAL = 0 ] && touch ${lockfile}
+ return $RETVAL
+}
+
+stop() {
+ echo -n $"Stopping git-daemon: "
+ killproc $gitdaemon
+ RETVAL=$?
+ echo
+ [ $RETVAL = 0 ] && rm -f ${lockfile}
+}
+
+restart() {
+ stop
+ start
+}
+
+case "$1" in
+ start)
+ start
+ ;;
+ stop)
+ stop
+ ;;
+ restart)
+ restart
+ ;;
+ status)
+ status $gitdaemon
+ RETVAL=$?
+ ;;
+ *)
+ echo $"Usage: $0 {start|stop|status|restart}"
+ exit 1
+esac
+
+exit $RETVAL
diff --git a/puppet/modules/git/files/init.d/Debian/git-daemon b/puppet/modules/git/files/init.d/Debian/git-daemon
new file mode 100644
index 00000000..ab57c4a1
--- /dev/null
+++ b/puppet/modules/git/files/init.d/Debian/git-daemon
@@ -0,0 +1,151 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides: git-daemon
+# Required-Start: $network $remote_fs $syslog
+# Required-Stop: $network $remote_fs $syslog
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: git-daemon service
+# Description: git-daemon makes git repositories available via the git
+# protocol.
+### END INIT INFO
+
+# Author: Antonio Ospite <ospite@studenti.unina.it>
+#
+# Please remove the "Author" lines above and replace them
+# with your own name if you copy and modify this script.
+
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/lib/git-core
+DESC="git-daemon service"
+NAME=git-daemon
+DAEMON=/usr/lib/git-core/$NAME
+PIDFILE=/var/run/$NAME.pid
+SCRIPTNAME=/etc/init.d/$NAME
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Fallback options values, we use these when
+# the /etc/default/git-daemon file does not exist
+RUN=no
+USER=git
+GROUP=git
+REPOSITORIES="/srv/git/"
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# If ADVANCED_OPTS is empty, use a default setting
+if [ "x$ADVANCED_OPTS" == "x" ];
+then
+ ADVANCED_OPTS="--base-path=$REPOSITORIES $REPOSITORIES"
+fi
+
+DAEMON_ARGS="--syslog --reuseaddr \
+ --user=$USER --group=$GROUP \
+ $ADVANCED_OPTS"
+
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+ # Return
+ # 0 if daemon has been started
+ # 1 if daemon was already running
+ # 2 if daemon could not be started
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
+ || return 1
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --background --make-pidfile -- \
+ $DAEMON_ARGS \
+ || return 2
+
+ return 0
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+ # Return
+ # 0 if daemon has been stopped
+ # 1 if daemon was already stopped
+ # 2 if daemon could not be stopped
+ # other if a failure occurred
+ start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
+ RETVAL="$?"
+ [ "$RETVAL" = 2 ] && return 2
+ # Wait for children to finish too if this is a daemon that forks
+ # and if the daemon is only ever run from this initscript.
+ # If the above conditions are not satisfied then add some other code
+ # that waits for the process to drop all resources that could be
+ # needed by services started subsequently. A last resort is to
+ # sleep for some time.
+ start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
+ [ "$?" = 2 ] && return 2
+ # Many daemons don't delete their pidfiles when they exit.
+ rm -f $PIDFILE
+ return "$RETVAL"
+}
+
+case "$1" in
+ start)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
+ do_start
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ stop)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ status)
+ status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
+ ;;
+ restart|force-reload)
+ #
+ # If the "reload" option is implemented then remove the
+ # 'force-reload' alias
+ #
+ log_daemon_msg "Restarting $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1)
+ do_start
+ case "$?" in
+ 0) log_end_msg 0 ;;
+ 1) log_end_msg 1 ;; # Old process is still running
+ *) log_end_msg 1 ;; # Failed to start
+ esac
+ ;;
+ *)
+ # Failed to stop
+ log_end_msg 1
+ ;;
+ esac
+ ;;
+ *)
+ echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
+ exit 3
+ ;;
+esac
+
+:
diff --git a/puppet/modules/git/files/web/gitweb.conf b/puppet/modules/git/files/web/gitweb.conf
new file mode 100644
index 00000000..88226aaa
--- /dev/null
+++ b/puppet/modules/git/files/web/gitweb.conf
@@ -0,0 +1,53 @@
+# The gitweb config file is a fragment of perl code. You can set variables
+# using "our $variable = value"; text from "#" character until the end of a
+# line is ignored. See perlsyn(1) man page for details.
+#
+# See /usr/share/doc/gitweb-*/README and /usr/share/doc/gitweb-*/INSTALL for
+# more details and available configuration variables.
+
+# Set the path to git projects. This is an absolute filesystem path which will
+# be prepended to the project path.
+#our $projectroot = "/var/lib/git";
+
+# Set the list of git base URLs used for URL to where fetch project from, i.e.
+# the full URL is "$git_base_url/$project". By default this is empty
+#our @git_base_url_list = qw(git://git.example.com
+# ssh://git.example.com/var/lib/git);
+
+# Enable the 'blame' blob view, showing the last commit that modified
+# each line in the file. This can be very CPU-intensive. Disabled by default
+#$feature{'blame'}{'default'} = [1];
+#
+# Allow projects to override the default setting via git config file.
+# Example: gitweb.blame = 0|1;
+#$feature{'blame'}{'override'} = 1;
+
+# Disable the 'snapshot' link, providing a compressed archive of any tree. This
+# can potentially generate high traffic if you have large project. Enabled for
+# .tar.gz snapshots by default.
+#
+# Value is a list of formats defined in %known_snapshot_formats that you wish
+# to offer.
+#$feature{'snapshot'}{'default'} = [];
+#
+# Allow projects to override the default setting via git config file.
+# Example: gitweb.snapshot = tbz2,zip; (use "none" to disable)
+#$feature{'snapshot'}{'override'} = 1;
+
+# Disable grep search, which will list the files in currently selected tree
+# containing the given string. This can be potentially CPU-intensive, of
+# course. Enabled by default.
+#$feature{'grep'}{'default'} = [0];
+#
+# Allow projects to override the default setting via git config file.
+# Example: gitweb.grep = 0|1;
+#$feature{'grep'}{'override'} = 1;
+
+# Disable the pickaxe search, which will list the commits that modified a given
+# string in a file. This can be practical and quite faster alternative to
+# 'blame', but still potentially CPU-intensive. Enabled by default.
+#$feature{'pickaxe'}{'default'} = [0];
+#
+# Allow projects to override the default setting via git config file.
+# Example: gitweb.pickaxe = 0|1;
+#$feature{'pickaxe'}{'override'} = 1;
diff --git a/puppet/modules/git/files/xinetd.d/git b/puppet/modules/git/files/xinetd.d/git
new file mode 100644
index 00000000..64c53e8b
--- /dev/null
+++ b/puppet/modules/git/files/xinetd.d/git
@@ -0,0 +1,16 @@
+# default: off
+# description: The git dæmon allows git repositories to be exported using
+# the git:// protocol.
+
+service git
+{
+ disable = no
+ socket_type = stream
+ wait = no
+ user = nobody
+ server = /usr/bin/git-daemon
+ server_args = --base-path=/srv/git --export-all --user-path=public_git --syslog --inetd --verbose
+ log_on_failure += USERID
+# xinetd doesn't do this by default. bug #195265
+ flags = IPv6
+}
diff --git a/puppet/modules/git/files/xinetd.d/git.disabled b/puppet/modules/git/files/xinetd.d/git.disabled
new file mode 100644
index 00000000..dcfae918
--- /dev/null
+++ b/puppet/modules/git/files/xinetd.d/git.disabled
@@ -0,0 +1,16 @@
+# default: off
+# description: The git dæmon allows git repositories to be exported using
+# the git:// protocol.
+
+service git
+{
+ disable = yes
+ socket_type = stream
+ wait = no
+ user = nobody
+ server = /usr/bin/git-daemon
+ server_args = --base-path=/srv/git --export-all --user-path=public_git --syslog --inetd --verbose
+ log_on_failure += USERID
+# xinetd doesn't do this by default. bug #195265
+ flags = IPv6
+}
diff --git a/puppet/modules/git/files/xinetd.d/git.vhosts b/puppet/modules/git/files/xinetd.d/git.vhosts
new file mode 100644
index 00000000..98938206
--- /dev/null
+++ b/puppet/modules/git/files/xinetd.d/git.vhosts
@@ -0,0 +1,16 @@
+# default: off
+# description: The git dæmon allows git repositories to be exported using
+# the git:// protocol.
+
+service git
+{
+ disable = no
+ socket_type = stream
+ wait = no
+ user = nobody
+ server = /usr/bin/git-daemon
+ server_args = --interpolated-path=/srv/git/%H/%D --syslog --inetd --verbose
+ log_on_failure += USERID
+# xinetd doesn't do this by default. bug #195265
+ flags = IPv6
+}
diff --git a/puppet/modules/git/manifests/base.pp b/puppet/modules/git/manifests/base.pp
new file mode 100644
index 00000000..e6188390
--- /dev/null
+++ b/puppet/modules/git/manifests/base.pp
@@ -0,0 +1,7 @@
+class git::base {
+
+ package { 'git':
+ ensure => present,
+ alias => 'git',
+ }
+}
diff --git a/puppet/modules/git/manifests/centos.pp b/puppet/modules/git/manifests/centos.pp
new file mode 100644
index 00000000..96344756
--- /dev/null
+++ b/puppet/modules/git/manifests/centos.pp
@@ -0,0 +1,2 @@
+class git::centos inherits git::base {
+}
diff --git a/puppet/modules/git/manifests/changes.pp b/puppet/modules/git/manifests/changes.pp
new file mode 100644
index 00000000..71112051
--- /dev/null
+++ b/puppet/modules/git/manifests/changes.pp
@@ -0,0 +1,33 @@
+# Usage
+# git::changes { name:
+# cwd => "/path/to/git/"
+# user => "me",
+# ensure => {*assume-unchanged*, tracked}
+# }
+#
+
+define git::changes ( $cwd, $user, $ensure='assume-unchanged' ) {
+
+ case $ensure {
+ default: { err ( "unknown ensure value '${ensure}'" ) }
+
+ assume-unchanged: {
+ exec { "assume-unchanged ${name}":
+ command => "/usr/bin/git update-index --assume-unchanged ${name}",
+ cwd => $cwd,
+ user => $user,
+ unless => "/usr/bin/git ls-files -v | grep '^[ch] ${name}'",
+ }
+ }
+
+ tracked: {
+ exec { "track changes ${name}":
+ command => "/usr/bin/git update-index --no-assume-unchanged ${name}",
+ cwd => $cwd,
+ user => $user,
+ onlyif => "/usr/bin/git ls-files -v | grep '^[ch] ${name}'",
+ }
+ }
+ }
+}
+
diff --git a/puppet/modules/git/manifests/clone.pp b/puppet/modules/git/manifests/clone.pp
new file mode 100644
index 00000000..29f0b2b3
--- /dev/null
+++ b/puppet/modules/git/manifests/clone.pp
@@ -0,0 +1,60 @@
+# submodules: Whether we should initialize and update
+# submodules as well
+# Default: false
+# clone_before: before which resources a cloning should
+# happen. This is releveant in combination
+# with submodules as the exec of submodules
+# requires the `cwd` and you might get a
+# dependency cycle if you manage $projectroot
+# somewhere else.
+define git::clone(
+ $ensure = present,
+ $git_repo,
+ $projectroot,
+ $submodules = false,
+ $clone_before = 'absent',
+ $cloneddir_user='root',
+ $cloneddir_group='0',
+ $cloneddir_restrict_mode=true
+){
+ case $ensure {
+ absent: {
+ exec{"rm -rf $projectroot":
+ onlyif => "test -d $projectroot",
+ }
+ }
+ default: {
+ require ::git
+ exec {"git-clone_${name}":
+ command => "git clone --no-hardlinks ${git_repo} ${projectroot}",
+ creates => "${projectroot}/.git",
+ user => root,
+ notify => Exec["git-clone-chown_${name}"],
+ }
+ if $clone_before != 'absent' {
+ Exec["git-clone_${name}"]{
+ before => $clone_before,
+ }
+ }
+ if $submodules {
+ exec{"git-submodules_${name}":
+ command => "git submodule init && git submodule update",
+ cwd => $projectroot,
+ refreshonly => true,
+ subscribe => Exec["git-clone_${name}"],
+ }
+ }
+ exec {"git-clone-chown_${name}":
+ command => "chown -R ${cloneddir_user}:${cloneddir_group} ${projectroot};chmod -R og-rwx ${projectroot}/.git",
+ refreshonly => true
+ }
+ if $cloneddir_restrict_mode {
+ exec {"git-clone-chmod_${name}":
+ command => "chmod -R o-rwx ${projectroot}",
+ refreshonly => true,
+ subscribe => Exec["git-clone_${name}"],
+ }
+ }
+ }
+ }
+}
diff --git a/puppet/modules/git/manifests/daemon.pp b/puppet/modules/git/manifests/daemon.pp
new file mode 100644
index 00000000..1e85ff84
--- /dev/null
+++ b/puppet/modules/git/manifests/daemon.pp
@@ -0,0 +1,17 @@
+class git::daemon {
+
+ include git
+
+ case $operatingsystem {
+ centos: { include git::daemon::centos }
+ debian: { include git::daemon::base }
+ }
+
+ if $use_shorewall {
+ include shorewall::rules::gitdaemon
+ }
+
+ if $use_nagios {
+ nagios::service { "git-daemon": check_command => "check_git!${fqdn}"; }
+ }
+}
diff --git a/puppet/modules/git/manifests/daemon/base.pp b/puppet/modules/git/manifests/daemon/base.pp
new file mode 100644
index 00000000..6a03d4fd
--- /dev/null
+++ b/puppet/modules/git/manifests/daemon/base.pp
@@ -0,0 +1,31 @@
+class git::daemon::base inherits git::base {
+
+ file { 'git-daemon_initscript':
+ source => [ "puppet://$server/modules/site_git/init.d/${fqdn}/git-daemon",
+ "puppet://$server/modules/site_git/init.d/${operatingsystem}/git-daemon",
+ "puppet://$server/modules/site_git/init.d/git-daemon",
+ "puppet://$server/modules/git/init.d/${operatingsystem}/git-daemon",
+ "puppet://$server/modules/git/init.d/git-daemon" ],
+ require => Package['git'],
+ path => "/etc/init.d/git-daemon",
+ owner => root, group => 0, mode => 0755;
+ }
+
+ file { 'git-daemon_config':
+ source => [ "puppet://$server/modules/site_git/config/${fqdn}/git-daemon",
+ "puppet://$server/modules/site_git/config/${operatingsystem}/git-daemon",
+ "puppet://$server/modules/site_git/config/git-daemon",
+ "puppet://$server/modules/git/config/${operatingsystem}/git-daemon",
+ "puppet://$server/modules/git/config/git-daemon" ],
+ require => Package['git'],
+ path => "/etc/default/git-daemon",
+ owner => root, group => 0, mode => 0644;
+ }
+
+ service { 'git-daemon':
+ ensure => running,
+ enable => true,
+ hasstatus => true,
+ require => [ File['git-daemon_initscript'], File['git-daemon_config'] ],
+ }
+}
diff --git a/puppet/modules/git/manifests/daemon/centos.pp b/puppet/modules/git/manifests/daemon/centos.pp
new file mode 100644
index 00000000..e276259d
--- /dev/null
+++ b/puppet/modules/git/manifests/daemon/centos.pp
@@ -0,0 +1,19 @@
+class git::daemon::centos inherits git::daemon::base {
+
+ package { 'git-daemon':
+ ensure => installed,
+ require => Package['git'],
+ alias => 'git-daemon',
+ }
+
+ File['git-daemon_initscript'] {
+ path => '/etc/init.d/git-daemon',
+ require +> Package['git-daemon'],
+ }
+
+ File['git-daemon_config'] {
+ path => '/etc/init.d/git-daemon',
+ require +> Package['git-daemon'],
+ }
+
+}
diff --git a/puppet/modules/git/manifests/daemon/disable.pp b/puppet/modules/git/manifests/daemon/disable.pp
new file mode 100644
index 00000000..c044e962
--- /dev/null
+++ b/puppet/modules/git/manifests/daemon/disable.pp
@@ -0,0 +1,33 @@
+class git::daemon::disable inherits git::daemon::base {
+
+ if defined(Package['git-daemon']) {
+ Package['git-daemon'] {
+ ensure => absent,
+ }
+ }
+
+ File['git-daemon_initscript'] {
+ ensure => absent,
+ }
+
+ File['git-daemon_config'] {
+ ensure => absent,
+ }
+
+ Service['git-daemon'] {
+ ensure => stopped,
+ enable => false,
+ require => undef,
+ before => File['git-daemon_initscript'],
+ }
+
+ if $use_shorewall {
+ include shorewall::rules::gitdaemon::absent
+ }
+
+ if $use_nagios {
+ nagios::service { "git-daemon": check_command => "check_git!${fqdn}", ensure => absent; }
+ }
+}
+
+
diff --git a/puppet/modules/git/manifests/daemon/vhosts.pp b/puppet/modules/git/manifests/daemon/vhosts.pp
new file mode 100644
index 00000000..9591330f
--- /dev/null
+++ b/puppet/modules/git/manifests/daemon/vhosts.pp
@@ -0,0 +1,10 @@
+class git::daemon::vhosts inherits git::daemon {
+
+ File['git-daemon_config']{
+ source => [ "puppet://$server/modules/site_git/config/${fqdn}/git-daemon.vhosts",
+ "puppet://$server/modules/site_git/config/${operatingsystem}/git-daemon.vhosts",
+ "puppet://$server/modules/site_git/config/git-daemon.vhosts",
+ "puppet://$server/modules/git/config/${operatingsystem}/git-daemon.vhosts",
+ "puppet://$server/modules/git/config/git-daemon.vhosts" ],
+ }
+}
diff --git a/puppet/modules/git/manifests/debian.pp b/puppet/modules/git/manifests/debian.pp
new file mode 100644
index 00000000..2e63d692
--- /dev/null
+++ b/puppet/modules/git/manifests/debian.pp
@@ -0,0 +1,6 @@
+class git::debian inherits git::base {
+
+ Package['git'] {
+ name => 'git-core',
+ }
+}
diff --git a/puppet/modules/git/manifests/init.pp b/puppet/modules/git/manifests/init.pp
new file mode 100644
index 00000000..4693af75
--- /dev/null
+++ b/puppet/modules/git/manifests/init.pp
@@ -0,0 +1,25 @@
+#
+# git module
+#
+# Copyright 2008, Puzzle ITC
+# Marcel Härry haerry+puppet(at)puzzle.ch
+# Simon Josi josi+puppet(at)puzzle.ch
+#
+# This program is free software; you can redistribute
+# it and/or modify it under the terms of the GNU
+# General Public License version 3 as published by
+# the Free Software Foundation.
+#
+
+class git {
+
+ case $operatingsystem {
+ debian: { include git::debian }
+ centos: { include git::centos }
+ }
+
+ if $use_shorewall {
+ include shorewall::rules::out::git
+ }
+
+}
diff --git a/puppet/modules/git/manifests/svn.pp b/puppet/modules/git/manifests/svn.pp
new file mode 100644
index 00000000..ea934749
--- /dev/null
+++ b/puppet/modules/git/manifests/svn.pp
@@ -0,0 +1,10 @@
+# manifests/svn.pp
+
+class git::svn {
+ include ::git
+ include subversion
+
+ package { 'git-svn':
+ require => [ Package['git'], Package['subversion'] ],
+ }
+}
diff --git a/puppet/modules/git/manifests/web.pp b/puppet/modules/git/manifests/web.pp
new file mode 100644
index 00000000..3cf5139e
--- /dev/null
+++ b/puppet/modules/git/manifests/web.pp
@@ -0,0 +1,20 @@
+class git::web {
+ include git
+
+ package { 'gitweb':
+ ensure => present,
+ require => Package['git'],
+ }
+
+ file { '/etc/gitweb.d':
+ ensure => directory,
+ owner => root, group => 0, mode => 0755;
+ }
+ file { '/etc/gitweb.conf':
+ source => [ "puppet:///modules/site_git/web/${fqdn}/gitweb.conf",
+ "puppet:///modules/site_git/web/gitweb.conf",
+ "puppet:///modules/git/web/gitweb.conf" ],
+ require => Package['gitweb'],
+ owner => root, group => 0, mode => 0644;
+ }
+}
diff --git a/puppet/modules/git/manifests/web/absent.pp b/puppet/modules/git/manifests/web/absent.pp
new file mode 100644
index 00000000..4d0dba33
--- /dev/null
+++ b/puppet/modules/git/manifests/web/absent.pp
@@ -0,0 +1,17 @@
+class git::web::absent {
+
+ package { 'gitweb':
+ ensure => absent,
+ }
+
+ file { '/etc/gitweb.d':
+ ensure => absent,
+ purge => true,
+ force => true,
+ recurse => true,
+ }
+ file { '/etc/gitweb.conf':
+ ensure => absent,
+ }
+}
+
diff --git a/puppet/modules/git/manifests/web/lighttpd.pp b/puppet/modules/git/manifests/web/lighttpd.pp
new file mode 100644
index 00000000..980e23c0
--- /dev/null
+++ b/puppet/modules/git/manifests/web/lighttpd.pp
@@ -0,0 +1,7 @@
+class git::web::lighttpd {
+ include ::lighttpd
+
+ lighttpd::config::file{'lighttpd-gitweb':
+ content => 'global { server.modules += ("mod_rewrite", "mod_redirect", "mod_alias", "mod_setenv", "mod_cgi" ) }',
+ }
+}
diff --git a/puppet/modules/git/manifests/web/repo.pp b/puppet/modules/git/manifests/web/repo.pp
new file mode 100644
index 00000000..da6f74f0
--- /dev/null
+++ b/puppet/modules/git/manifests/web/repo.pp
@@ -0,0 +1,56 @@
+# domain: the domain under which this repo will be avaiable
+# projectroot: where the git repos are listened
+# projects_list: which repos to export
+#
+# logmode:
+# - default: Do normal logging including ips
+# - anonym: Don't log ips
+define git::web::repo(
+ $ensure = 'present',
+ $projectroot = 'absent',
+ $projects_list = 'absent',
+ $logmode = 'default',
+ $sitename = 'absent'
+){
+ if ($ensure == 'present') and (($projects_list == 'absent') or ($projectroot == 'absent')){
+ fail("You have to pass \$project_list and \$projectroot for ${name} if it should be present!")
+ }
+ if $ensure == 'present' { include git::web }
+ $gitweb_url = $name
+ case $gitweb_sitename {
+ 'absent': { $gitweb_sitename = "${name} git repository" }
+ default: { $gitweb_sitename = $sitename }
+ }
+ $gitweb_config = "/etc/gitweb.d/${name}.conf"
+ file{"${gitweb_config}": }
+ if $ensure == 'present' {
+ File["${gitweb_config}"]{
+ content => template("git/web/config")
+ }
+ } else {
+ File["${gitweb_config}"]{
+ ensure => absent,
+ }
+ }
+ case $gitweb_webserver {
+ 'lighttpd': {
+ git::web::repo::lighttpd{$name:
+ ensure => $ensure,
+ logmode => $logmode,
+ gitweb_url => $gitweb_url,
+ gitweb_config => $gitweb_config,
+ }
+ }
+ 'apache': {
+ apache::vhost::gitweb{$gitweb_url:
+ logmode => $logmode,
+ ensure => $ensure,
+ }
+ }
+ default: {
+ if ($ensure == 'present') {
+ fail("no supported \$gitweb_webserver defined on ${fqdn}, so can't do git::web::repo: ${name}")
+ }
+ }
+ }
+}
diff --git a/puppet/modules/git/manifests/web/repo/lighttpd.pp b/puppet/modules/git/manifests/web/repo/lighttpd.pp
new file mode 100644
index 00000000..11cee4ce
--- /dev/null
+++ b/puppet/modules/git/manifests/web/repo/lighttpd.pp
@@ -0,0 +1,16 @@
+# logmode:
+# - default: Do normal logging including ips
+# - anonym: Don't log ips
+define git::web::repo::lighttpd(
+ $ensure = 'present',
+ $gitweb_url,
+ $logmode = 'default',
+ $gitweb_config
+){
+ if $ensure == 'present' { include git::web::lighttpd }
+
+ lighttpd::vhost::file{$name:
+ ensure => $ensure,
+ content => template('git/web/lighttpd');
+ }
+}
diff --git a/puppet/modules/git/templates/web/config b/puppet/modules/git/templates/web/config
new file mode 100644
index 00000000..5286f6a6
--- /dev/null
+++ b/puppet/modules/git/templates/web/config
@@ -0,0 +1,31 @@
+# Include the global configuration, if found.
+do "/etc/gitweb.conf" if -e "/etc/gitweb.conf";
+
+# Point to projects.list file generated by gitosis.
+# Here gitosis manages the user "git", who has a
+# home directory of /srv/example.com/git
+$projects_list = "<%= projects_list %>";
+
+# Where the actual repositories are located.
+$projectroot = "<%= projectroot %>";
+
+# By default, gitweb will happily let people browse any repository
+# they guess the name of. This may or may not be what you wanted. I
+# choose to allow gitweb to show only repositories that git-daemon
+# is already sharing anonymously.
+$export_ok = "git-daemon-export-ok";
+
+# Alternatively, you could set these, to allow exactly the things in
+# projects.list, which in this case is the repos with gitweb=yes
+# in gitosis.conf. This means you don't need daemon=yes, but you
+# can't have repositories hidden but browsable if you know the name.
+# And note gitweb already allows downloading the full repository,
+# so you might as well serve git-daemon too.
+# $export_ok = "";
+# $strict_export = "true";
+
+# A list of base urls where all the repositories can be cloned from.
+# Easier than having per-repository cloneurl files.
+@git_base_url_list = ('git://<%= gitweb_url %>');
+
+$GITWEB_SITENAME = "<%= gitweb_sitename %>"
diff --git a/puppet/modules/git/templates/web/lighttpd b/puppet/modules/git/templates/web/lighttpd
new file mode 100644
index 00000000..cf244691
--- /dev/null
+++ b/puppet/modules/git/templates/web/lighttpd
@@ -0,0 +1,21 @@
+$HTTP["host"] == "<%= gitweb_url %>" {
+ url.redirect += (
+ "^$" => "/",
+ )
+
+ <%- if logmode.to_s == 'anonym' -%>
+ accesslog.format = "127.0.0.1 %V %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""
+ <%- end -%>
+
+ alias.url += (
+ "/static/gitweb.css" => "/var/www/git/static/gitweb.css",
+ "/static/git-logo.png" => "/var/www/git/static/git-logo.png",
+ "/static/git-favicon.png" => "/var/www/git/static/git-favicon.png",
+ "/" => "/var/www/git/gitweb.cgi",
+ )
+
+ setenv.add-environment = (
+ "GITWEB_CONFIG" => "<%= gitweb_config %>"
+ )
+ cgi.assign = ( ".cgi" => "" )
+}
diff --git a/puppet/modules/haproxy/.fixtures.yml b/puppet/modules/haproxy/.fixtures.yml
new file mode 100644
index 00000000..8d6f22d6
--- /dev/null
+++ b/puppet/modules/haproxy/.fixtures.yml
@@ -0,0 +1,5 @@
+fixtures:
+ repositories:
+ concat: "git://github.com/ripienaar/puppet-concat.git"
+ symlinks:
+ haproxy: "#{source_dir}"
diff --git a/puppet/modules/haproxy/.gemfile b/puppet/modules/haproxy/.gemfile
new file mode 100644
index 00000000..9aad840c
--- /dev/null
+++ b/puppet/modules/haproxy/.gemfile
@@ -0,0 +1,5 @@
+source :rubygems
+
+puppetversion = ENV.key?('PUPPET_VERSION') ? "= #{ENV['PUPPET_VERSION']}" : ['>= 2.7']
+gem 'puppet', puppetversion
+gem 'puppetlabs_spec_helper', '>= 0.1.0'
diff --git a/puppet/modules/haproxy/.travis.yml b/puppet/modules/haproxy/.travis.yml
new file mode 100644
index 00000000..fdbc95dc
--- /dev/null
+++ b/puppet/modules/haproxy/.travis.yml
@@ -0,0 +1,23 @@
+language: ruby
+rvm:
+ - 1.8.7
+ - 1.9.3
+script: "rake spec"
+branches:
+ only:
+ - master
+env:
+ - PUPPET_VERSION=2.6.17
+ - PUPPET_VERSION=2.7.19
+ #- PUPPET_VERSION=3.0.1 # Breaks due to rodjek/rspec-puppet#58
+notifications:
+ email: false
+gemfile: .gemfile
+matrix:
+ exclude:
+ - rvm: 1.9.3
+ gemfile: .gemfile
+ env: PUPPET_VERSION=2.6.17
+ - rvm: 1.8.7
+ gemfile: .gemfile
+ env: PUPPET_VERSION=3.0.1
diff --git a/puppet/modules/haproxy/CHANGELOG b/puppet/modules/haproxy/CHANGELOG
new file mode 100644
index 00000000..0b6d670f
--- /dev/null
+++ b/puppet/modules/haproxy/CHANGELOG
@@ -0,0 +1,5 @@
+2012-10-12 - Version 0.2.0
+- Initial public release
+- Backwards incompatible changes all around
+- No longer needs ordering passed for more than one listener
+- Accepts multiple listen ips/ports/server_names
diff --git a/puppet/modules/haproxy/Modulefile b/puppet/modules/haproxy/Modulefile
new file mode 100644
index 00000000..e729739b
--- /dev/null
+++ b/puppet/modules/haproxy/Modulefile
@@ -0,0 +1,12 @@
+name 'puppetlabs-haproxy'
+version '0.2.0'
+source 'git://github.com/puppetlabs/puppetlabs-haproxy'
+author 'Puppet Labs'
+license 'Apache License, Version 2.0'
+summary 'Haproxy Module'
+description 'An Haproxy module for Redhat family OSes using Storeconfigs'
+project_page 'http://github.com/puppetlabs/puppetlabs-haproxy'
+
+## Add dependencies, if any:
+# dependency 'username/name', '>= 1.2.0'
+dependency 'ripienaar/concat', '>= 0.1.0'
diff --git a/puppet/modules/haproxy/README.md b/puppet/modules/haproxy/README.md
new file mode 100644
index 00000000..d209e9ab
--- /dev/null
+++ b/puppet/modules/haproxy/README.md
@@ -0,0 +1,87 @@
+PuppetLabs Module for haproxy
+=============================
+
+HAProxy is an HA proxying daemon for load-balancing to clustered services. It
+can proxy TCP directly, or other kinds of traffic such as HTTP.
+
+Dependencies
+------------
+
+Tested and built on Debian, Ubuntu and CentOS
+
+Currently requires the ripienaar/concat module on the Puppet Forge and uses storeconfigs on the Puppet Master to export/collect resources
+from all balancer members.
+
+Basic Usage
+-----------
+
+This haproxy uses storeconfigs to collect and realize balancer member servers
+on a load balancer server.
+
+*To install and configure HAProxy server listening on port 8140*
+
+```puppet
+node 'haproxy-server' {
+ class { 'haproxy': }
+ haproxy::listen { 'puppet00':
+ ipaddress => $::ipaddress,
+ ports => '8140',
+ }
+}
+```
+
+*To add backend loadbalance members*
+
+```puppet
+node 'webserver01' {
+ @@haproxy::balancermember { $fqdn:
+ listening_service => 'puppet00',
+ server_names => $::hostname,
+ ipaddresses => $::ipaddress,
+ ports => '8140',
+ options => 'check'
+ }
+}
+```
+
+Configuring haproxy options
+---------------------------
+
+The base `haproxy` class can accept two parameters which will configure basic
+behaviour of the haproxy server daemon:
+
+- `global_options` to configure the `global` section in `haproxy.cfg`
+- `defaults_options` to configure the `defaults` section in `haproxy.cfg`
+
+Configuring haproxy daemon listener
+-----------------------------------
+
+One `haproxy::listen` defined resource should be defined for each HAProxy loadbalanced set of backend servers. The title of the `haproxy::listen` resource is the key to which balancer members will be proxied to. The `ipaddress` field should be the public ip address which the loadbalancer will be contacted on. The `ports` attribute can accept an array or comma-separated list of ports which should be proxied to the `haproxy::balancermemeber` nodes.
+
+Configuring haproxy loadbalanced member nodes
+---------------------------------------------
+
+The `haproxy::balacemember` defined resource should be exported from each node
+which is serving loadbalanced traffic. the `listening_service` attribute will
+associate it with `haproxy::listen` directives on the haproxy node.
+`ipaddresses` and `ports` will be assigned to the member to be contacted on. If an array of `ipaddresses` and `server_names` are provided then they will be added to the config in lock-step.
+
+
+Copyright and License
+---------------------
+
+Copyright (C) 2012 [Puppet Labs](https://www.puppetlabs.com/) Inc
+
+Puppet Labs can be contacted at: info@puppetlabs.com
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/puppet/modules/haproxy/Rakefile b/puppet/modules/haproxy/Rakefile
new file mode 100644
index 00000000..cd3d3799
--- /dev/null
+++ b/puppet/modules/haproxy/Rakefile
@@ -0,0 +1 @@
+require 'puppetlabs_spec_helper/rake_tasks'
diff --git a/puppet/modules/haproxy/manifests/balancermember.pp b/puppet/modules/haproxy/manifests/balancermember.pp
new file mode 100644
index 00000000..a0e27539
--- /dev/null
+++ b/puppet/modules/haproxy/manifests/balancermember.pp
@@ -0,0 +1,95 @@
+# == Define Resource Type: haproxy::balancermember
+#
+# This type will setup a balancer member inside a listening service
+# configuration block in /etc/haproxy/haproxy.cfg on the load balancer.
+# currently it only has the ability to specify the instance name,
+# ip address, port, and whether or not it is a backup. More features
+# can be added as needed. The best way to implement this is to export
+# this resource for all haproxy balancer member servers, and then collect
+# them on the main haproxy load balancer.
+#
+# === Requirement/Dependencies:
+#
+# Currently requires the ripienaar/concat module on the Puppet Forge and
+# uses storeconfigs on the Puppet Master to export/collect resources
+# from all balancer members.
+#
+# === Parameters
+#
+# [*name*]
+# The title of the resource is arbitrary and only utilized in the concat
+# fragment name.
+#
+# [*listening_service*]
+# The haproxy service's instance name (or, the title of the
+# haproxy::listen resource). This must match up with a declared
+# haproxy::listen resource.
+#
+# [*ports*]
+# An array or commas-separated list of ports for which the balancer member
+# will accept connections from the load balancer. Note that cookie values
+# aren't yet supported, but shouldn't be difficult to add to the
+# configuration. If you use an array in server_names and ipaddresses, the
+# same port is used for all balancermembers.
+#
+# [*server_names*]
+# The name of the balancer member server as known to haproxy in the
+# listening service's configuration block. This defaults to the
+# hostname. Can be an array of the same length as ipaddresses,
+# in which case a balancermember is created for each pair of
+# server_names and ipaddresses (in lockstep).
+#
+# [*ipaddresses*]
+# The ip address used to contact the balancer member server.
+# Can be an array, see documentation to server_names.
+#
+# [*options*]
+# An array of options to be specified after the server declaration
+# in the listening service's configuration block.
+#
+#
+# === Examples
+#
+# Exporting the resource for a balancer member:
+#
+# @@haproxy::balancermember { 'haproxy':
+# listening_service => 'puppet00',
+# ports => '8140',
+# server_names => $::hostname,
+# ipaddresses => $::ipaddress,
+# options => 'check',
+# }
+#
+#
+# Collecting the resource on a load balancer
+#
+# Haproxy::Balancermember <<| listening_service == 'puppet00' |>>
+#
+# Creating the resource for multiple balancer members at once
+# (for single-pass installation of haproxy without requiring a first
+# pass to export the resources if you know the members in advance):
+#
+# haproxy::balancermember { 'haproxy':
+# listening_service => 'puppet00',
+# ports => '8140',
+# server_names => ['server01', 'server02'],
+# ipaddresses => ['192.168.56.200', '192.168.56.201'],
+# options => 'check',
+# }
+#
+# (this resource can be declared anywhere)
+#
+define haproxy::balancermember (
+ $listening_service,
+ $ports,
+ $server_names = $::hostname,
+ $ipaddresses = $::ipaddress,
+ $options = ''
+) {
+ # Template uses $ipaddresses, $server_name, $ports, $option
+ concat::fragment { "${listening_service}_balancermember_${name}":
+ order => "20-${listening_service}-${name}",
+ target => '/etc/haproxy/haproxy.cfg',
+ content => template('haproxy/haproxy_balancermember.erb'),
+ }
+}
diff --git a/puppet/modules/haproxy/manifests/init.pp b/puppet/modules/haproxy/manifests/init.pp
new file mode 100644
index 00000000..b91591a3
--- /dev/null
+++ b/puppet/modules/haproxy/manifests/init.pp
@@ -0,0 +1,149 @@
+# == Class: haproxy
+#
+# A Puppet module, using storeconfigs, to model an haproxy configuration.
+# Currently VERY limited - Pull requests accepted!
+#
+# === Requirement/Dependencies:
+#
+# Currently requires the ripienaar/concat module on the Puppet Forge and
+# uses storeconfigs on the Puppet Master to export/collect resources
+# from all balancer members.
+#
+# === Parameters
+#
+# [*enable*]
+# Chooses whether haproxy should be installed or ensured absent.
+# Currently ONLY accepts valid boolean true/false values.
+#
+# [*version*]
+# Allows you to specify what version of the package to install.
+# Default is simply 'present'
+#
+# [*global_options*]
+# A hash of all the haproxy global options. If you want to specify more
+# than one option (i.e. multiple timeout or stats options), pass those
+# options as an array and you will get a line for each of them in the
+# resultant haproxy.cfg file.
+#
+# [*defaults_options*]
+# A hash of all the haproxy defaults options. If you want to specify more
+# than one option (i.e. multiple timeout or stats options), pass those
+# options as an array and you will get a line for each of them in the
+# resultant haproxy.cfg file.
+#
+#
+# === Examples
+#
+# class { 'haproxy':
+# enable => true,
+# global_options => {
+# 'log' => "${::ipaddress} local0",
+# 'chroot' => '/var/lib/haproxy',
+# 'pidfile' => '/var/run/haproxy.pid',
+# 'maxconn' => '4000',
+# 'user' => 'haproxy',
+# 'group' => 'haproxy',
+# 'daemon' => '',
+# 'stats' => 'socket /var/lib/haproxy/stats'
+# },
+# defaults_options => {
+# 'log' => 'global',
+# 'stats' => 'enable',
+# 'option' => 'redispatch',
+# 'retries' => '3',
+# 'timeout' => [
+# 'http-request 10s',
+# 'queue 1m',
+# 'connect 10s',
+# 'client 1m',
+# 'server 1m',
+# 'check 10s'
+# ],
+# 'maxconn' => '8000'
+# },
+# }
+#
+class haproxy (
+ $manage_service = true,
+ $enable = true,
+ $version = 'present',
+ $global_options = $haproxy::params::global_options,
+ $defaults_options = $haproxy::params::defaults_options
+) inherits haproxy::params {
+ include concat::setup
+
+ package { 'haproxy':
+ ensure => $enable ? {
+ true => $version,
+ false => absent,
+ },
+ name => 'haproxy',
+ }
+
+ if $enable {
+ concat { '/etc/haproxy/haproxy.cfg':
+ owner => '0',
+ group => '0',
+ mode => '0644',
+ require => Package['haproxy'],
+ notify => $manage_service ? {
+ true => Service['haproxy'],
+ false => undef,
+ },
+ }
+
+ # Simple Header
+ concat::fragment { '00-header':
+ target => '/etc/haproxy/haproxy.cfg',
+ order => '01',
+ content => "# This file managed by Puppet\n",
+ }
+
+ # Template uses $global_options, $defaults_options
+ concat::fragment { 'haproxy-base':
+ target => '/etc/haproxy/haproxy.cfg',
+ order => '10',
+ content => template('haproxy/haproxy-base.cfg.erb'),
+ }
+
+ if ($::osfamily == 'Debian') {
+ file { '/etc/default/haproxy':
+ content => 'ENABLED=1',
+ require => Package['haproxy'],
+ before => $manage_service ? {
+ true => Service['haproxy'],
+ false => undef,
+ },
+ }
+ }
+
+ file { $global_options['chroot']:
+ ensure => directory,
+ owner => $global_options['user'],
+ group => $global_options['group'],
+ mode => '0550',
+ require => Package['haproxy']
+ }
+
+ }
+
+ if $manage_service {
+ service { 'haproxy':
+ ensure => $enable ? {
+ true => running,
+ false => stopped,
+ },
+ enable => $enable ? {
+ true => true,
+ false => false,
+ },
+ name => 'haproxy',
+ hasrestart => true,
+ hasstatus => true,
+ require => [
+ Concat['/etc/haproxy/haproxy.cfg'],
+ File[$global_options['chroot']],
+ ],
+ }
+ }
+}
diff --git a/puppet/modules/haproxy/manifests/listen.pp b/puppet/modules/haproxy/manifests/listen.pp
new file mode 100644
index 00000000..00636e3d
--- /dev/null
+++ b/puppet/modules/haproxy/manifests/listen.pp
@@ -0,0 +1,95 @@
+# == Define Resource Type: haproxy::listen
+#
+# This type will setup a listening service configuration block inside
+# the haproxy.cfg file on an haproxy load balancer. Each listening service
+# configuration needs one or more load balancer member server (that can be
+# declared with the haproxy::balancermember defined resource type). Using
+# storeconfigs, you can export the haproxy::balancermember resources on all
+# load balancer member servers, and then collect them on a single haproxy
+# load balancer server.
+#
+# === Requirement/Dependencies:
+#
+# Currently requires the ripienaar/concat module on the Puppet Forge and
+# uses storeconfigs on the Puppet Master to export/collect resources
+# from all balancer members.
+#
+# === Parameters
+#
+# [*name*]
+# The namevar of the defined resource type is the listening service's name.
+# This name goes right after the 'listen' statement in haproxy.cfg
+#
+# [*ports*]
+# Ports on which the proxy will listen for connections on the ip address
+# specified in the virtual_ip parameter. Accepts either a single
+# comma-separated string or an array of strings which may be ports or
+# hyphenated port ranges.
+#
+# [*ipaddress*]
+# The ip address the proxy binds to. Empty addresses, '*', and '0.0.0.0'
+# mean that the proxy listens to all valid addresses on the system.
+#
+# [*mode*]
+# The mode of operation for the listening service. Valid values are 'tcp',
+# HTTP', and 'health'.
+#
+# [*options*]
+# A hash of options that are inserted into the listening service
+# configuration block.
+#
+# [*collect_exported*]
+# Boolean, default 'true'. True means 'collect exported @@balancermember resources'
+# (for the case when every balancermember node exports itself), false means
+# 'rely on the existing declared balancermember resources' (for the case when you
+# know the full set of balancermembers in advance and use haproxy::balancermember
+# with array arguments, which allows you to deploy everything in 1 run)
+#
+#
+# === Examples
+#
+# Exporting the resource for a balancer member:
+#
+# haproxy::listen { 'puppet00':
+# ipaddress => $::ipaddress,
+# ports => '18140',
+# mode => 'tcp',
+# options => {
+# 'option' => [
+# 'tcplog',
+# 'ssl-hello-chk'
+# ],
+# 'balance' => 'roundrobin'
+# },
+# }
+#
+# === Authors
+#
+# Gary Larizza <gary@puppetlabs.com>
+#
+define haproxy::listen (
+ $ports,
+ $ipaddress = [$::ipaddress],
+ $mode = 'tcp',
+ $collect_exported = true,
+ $options = {
+ 'option' => [
+ 'tcplog',
+ 'ssl-hello-chk'
+ ],
+ 'balance' => 'roundrobin'
+ }
+) {
+ # Template uses: $name, $ipaddress, $ports, $options
+ concat::fragment { "${name}_listen_block":
+ order => "20-${name}-00",
+ target => '/etc/haproxy/haproxy.cfg',
+ content => template('haproxy/haproxy_listen_block.erb'),
+ }
+
+ if $collect_exported {
+ Haproxy::Balancermember <<| listening_service == $name |>>
+ }
+ # else: the resources have been created and they introduced their
+ # concat fragments. We don't have to do anything about them.
+}
diff --git a/puppet/modules/haproxy/manifests/params.pp b/puppet/modules/haproxy/manifests/params.pp
new file mode 100644
index 00000000..53442ddc
--- /dev/null
+++ b/puppet/modules/haproxy/manifests/params.pp
@@ -0,0 +1,65 @@
+# == Class: haproxy::params
+#
+# This is a container class holding default parameters for for haproxy class.
+# currently, only the Redhat family is supported, but this can be easily
+# extended by changing package names and configuration file paths.
+#
+class haproxy::params {
+ case $osfamily {
+ Redhat: {
+ $global_options = {
+ 'log' => "${::ipaddress} local0",
+ 'chroot' => '/var/lib/haproxy',
+ 'pidfile' => '/var/run/haproxy.pid',
+ 'maxconn' => '4000',
+ 'user' => 'haproxy',
+ 'group' => 'haproxy',
+ 'daemon' => '',
+ 'stats' => 'socket /var/lib/haproxy/stats'
+ }
+ $defaults_options = {
+ 'log' => 'global',
+ 'stats' => 'enable',
+ 'option' => 'redispatch',
+ 'retries' => '3',
+ 'timeout' => [
+ 'http-request 10s',
+ 'queue 1m',
+ 'connect 10s',
+ 'client 1m',
+ 'server 1m',
+ 'check 10s',
+ ],
+ 'maxconn' => '8000'
+ }
+ }
+ Debian: {
+ $global_options = {
+ 'log' => "${::ipaddress} local0",
+ 'chroot' => '/var/lib/haproxy',
+ 'pidfile' => '/var/run/haproxy.pid',
+ 'maxconn' => '4000',
+ 'user' => 'haproxy',
+ 'group' => 'haproxy',
+ 'daemon' => '',
+ 'stats' => 'socket /var/lib/haproxy/stats'
+ }
+ $defaults_options = {
+ 'log' => 'global',
+ 'stats' => 'enable',
+ 'option' => 'redispatch',
+ 'retries' => '3',
+ 'timeout' => [
+ 'http-request 10s',
+ 'queue 1m',
+ 'connect 10s',
+ 'client 1m',
+ 'server 1m',
+ 'check 10s',
+ ],
+ 'maxconn' => '8000'
+ }
+ }
+ default: { fail("The $::osfamily operating system is not supported with the haproxy module") }
+ }
+}
diff --git a/puppet/modules/haproxy/spec/classes/haproxy_spec.rb b/puppet/modules/haproxy/spec/classes/haproxy_spec.rb
new file mode 100644
index 00000000..4b5902ce
--- /dev/null
+++ b/puppet/modules/haproxy/spec/classes/haproxy_spec.rb
@@ -0,0 +1,138 @@
+require 'spec_helper'
+
+describe 'haproxy', :type => :class do
+ let(:default_facts) do
+ {
+ :concat_basedir => '/dne',
+ :ipaddress => '10.10.10.10'
+ }
+ end
+ context 'on supported platforms' do
+ describe 'for OS-agnostic configuration' do
+ ['Debian', 'RedHat'].each do |osfamily|
+ context "on #{osfamily} family operatingsystems" do
+ let(:facts) do
+ { :osfamily => osfamily }.merge default_facts
+ end
+ let(:params) do
+ {'enable' => true}
+ end
+ it { should include_class('concat::setup') }
+ it 'should install the haproxy package' do
+ subject.should contain_package('haproxy').with(
+ 'ensure' => 'present'
+ )
+ end
+ it 'should install the haproxy service' do
+ subject.should contain_service('haproxy').with(
+ 'ensure' => 'running',
+ 'enable' => 'true',
+ 'hasrestart' => 'true',
+ 'hasstatus' => 'true',
+ 'require' => [
+ 'Concat[/etc/haproxy/haproxy.cfg]',
+ 'File[/var/lib/haproxy]'
+ ]
+ )
+ end
+ it 'should set up /etc/haproxy/haproxy.cfg as a concat resource' do
+ subject.should contain_concat('/etc/haproxy/haproxy.cfg').with(
+ 'owner' => '0',
+ 'group' => '0',
+ 'mode' => '0644'
+ )
+ end
+ it 'should manage the chroot directory' do
+ subject.should contain_file('/var/lib/haproxy').with(
+ 'ensure' => 'directory'
+ )
+ end
+ it 'should contain a header concat fragment' do
+ subject.should contain_concat__fragment('00-header').with(
+ 'target' => '/etc/haproxy/haproxy.cfg',
+ 'order' => '01',
+ 'content' => "# This file managed by Puppet\n"
+ )
+ end
+ it 'should contain a haproxy-base concat fragment' do
+ subject.should contain_concat__fragment('haproxy-base').with(
+ 'target' => '/etc/haproxy/haproxy.cfg',
+ 'order' => '10'
+ )
+ end
+ describe 'Base concat fragment contents' do
+ let(:contents) { param_value(subject, 'concat::fragment', 'haproxy-base', 'content').split("\n") }
+ it 'should contain global and defaults sections' do
+ contents.should include('global')
+ contents.should include('defaults')
+ end
+ it 'should log to an ip address for local0' do
+ contents.should be_any { |match| match =~ / log \d+(\.\d+){3} local0/ }
+ end
+ it 'should specify the default chroot' do
+ contents.should include(' chroot /var/lib/haproxy')
+ end
+ it 'should specify the correct user' do
+ contents.should include(' user haproxy')
+ end
+ it 'should specify the correct group' do
+ contents.should include(' group haproxy')
+ end
+ it 'should specify the correct pidfile' do
+ contents.should include(' pidfile /var/run/haproxy.pid')
+ end
+ end
+ end
+ context "on #{osfamily} family operatingsystems without managing the service" do
+ let(:facts) do
+ { :osfamily => osfamily }.merge default_facts
+ end
+ let(:params) do
+ {
+ 'enable' => true,
+ 'manage_service' => false,
+ }
+ end
+ it { should include_class('concat::setup') }
+ it 'should install the haproxy package' do
+ subject.should contain_package('haproxy').with(
+ 'ensure' => 'present'
+ )
+ end
+ it 'should install the haproxy service' do
+ subject.should_not contain_service('haproxy')
+ end
+ end
+ end
+ end
+ describe 'for OS-specific configuration' do
+ context 'only on Debian family operatingsystems' do
+ let(:facts) do
+ { :osfamily => 'Debian' }.merge default_facts
+ end
+ it 'should manage haproxy service defaults' do
+ subject.should contain_file('/etc/default/haproxy').with(
+ 'before' => 'Service[haproxy]',
+ 'require' => 'Package[haproxy]'
+ )
+ verify_contents(subject, '/etc/default/haproxy', ['ENABLED=1'])
+ end
+ end
+ context 'only on RedHat family operatingsystems' do
+ let(:facts) do
+ { :osfamily => 'RedHat' }.merge default_facts
+ end
+ end
+ end
+ end
+ context 'on unsupported operatingsystems' do
+ let(:facts) do
+ { :osfamily => 'RainbowUnicorn' }.merge default_facts
+ end
+ it do
+ expect {
+ should contain_service('haproxy')
+ }.to raise_error(Puppet::Error, /operating system is not supported with the haproxy module/)
+ end
+ end
+end
diff --git a/puppet/modules/haproxy/spec/defines/balancermember_spec.rb b/puppet/modules/haproxy/spec/defines/balancermember_spec.rb
new file mode 100644
index 00000000..74bc7a8b
--- /dev/null
+++ b/puppet/modules/haproxy/spec/defines/balancermember_spec.rb
@@ -0,0 +1,82 @@
+require 'spec_helper'
+
+describe 'haproxy::balancermember' do
+ let(:title) { 'tyler' }
+ let(:facts) do
+ {
+ :ipaddress => '1.1.1.1',
+ :hostname => 'dero'
+ }
+ end
+
+ context 'with a single balancermember option' do
+ let(:params) do
+ {
+ :name => 'tyler',
+ :listening_service => 'croy',
+ :ports => '18140',
+ :options => 'check'
+ }
+ end
+
+ it { should contain_concat__fragment('croy_balancermember_tyler').with(
+ 'order' => '20-croy-tyler',
+ 'target' => '/etc/haproxy/haproxy.cfg',
+ 'content' => " server dero 1.1.1.1:18140 check\n\n"
+ ) }
+ end
+
+ context 'with multiple balancermember options' do
+ let(:params) do
+ {
+ :name => 'tyler',
+ :listening_service => 'croy',
+ :ports => '18140',
+ :options => ['check', 'close']
+ }
+ end
+
+ it { should contain_concat__fragment('croy_balancermember_tyler').with(
+ 'order' => '20-croy-tyler',
+ 'target' => '/etc/haproxy/haproxy.cfg',
+ 'content' => " server dero 1.1.1.1:18140 check close\n\n"
+ ) }
+ end
+
+ context 'with multiple servers' do
+ let(:params) do
+ {
+ :name => 'tyler',
+ :listening_service => 'croy',
+ :ports => '18140',
+ :server_names => ['server01', 'server02'],
+ :ipaddresses => ['192.168.56.200', '192.168.56.201'],
+ :options => ['check']
+ }
+ end
+
+ it { should contain_concat__fragment('croy_balancermember_tyler').with(
+ 'order' => '20-croy-tyler',
+ 'target' => '/etc/haproxy/haproxy.cfg',
+ 'content' => " server server01 192.168.56.200:18140 check\n server server02 192.168.56.201:18140 check\n\n"
+ ) }
+ end
+ context 'with multiple servers and multiple ports' do
+ let(:params) do
+ {
+ :name => 'tyler',
+ :listening_service => 'croy',
+ :ports => ['18140','18150'],
+ :server_names => ['server01', 'server02'],
+ :ipaddresses => ['192.168.56.200', '192.168.56.201'],
+ :options => ['check']
+ }
+ end
+
+ it { should contain_concat__fragment('croy_balancermember_tyler').with(
+ 'order' => '20-croy-tyler',
+ 'target' => '/etc/haproxy/haproxy.cfg',
+ 'content' => " server server01 192.168.56.200:18140,192.168.56.200:18150 check\n server server02 192.168.56.201:18140,192.168.56.201:18150 check\n\n"
+ ) }
+ end
+end
diff --git a/puppet/modules/haproxy/spec/defines/listen_spec.rb b/puppet/modules/haproxy/spec/defines/listen_spec.rb
new file mode 100644
index 00000000..31dd4c85
--- /dev/null
+++ b/puppet/modules/haproxy/spec/defines/listen_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+
+describe 'haproxy::listen' do
+ let(:title) { 'tyler' }
+ let(:facts) {{ :ipaddress => '1.1.1.1' }}
+ context "when only one port is provided" do
+ let(:params) do
+ {
+ :name => 'croy',
+ :ports => '18140'
+ }
+ end
+
+ it { should contain_concat__fragment('croy_listen_block').with(
+ 'order' => '20-croy-00',
+ 'target' => '/etc/haproxy/haproxy.cfg',
+ 'content' => "listen croy\n\n bind 1.1.1.1:18140\n\n balance roundrobin\n option tcplog\n option ssl-hello-chk\n"
+ ) }
+ end
+ context "when an array of ports is provided" do
+ let(:params) do
+ {
+ :name => 'apache',
+ :ipaddress => '23.23.23.23',
+ :ports => [
+ '80',
+ '443',
+ ]
+ }
+ end
+
+ it { should contain_concat__fragment('apache_listen_block').with(
+ 'order' => '20-apache-00',
+ 'target' => '/etc/haproxy/haproxy.cfg',
+ 'content' => "listen apache\n\n bind 23.23.23.23:80\n\n bind 23.23.23.23:443\n\n balance roundrobin\n option tcplog\n option ssl-hello-chk\n"
+ ) }
+ end
+ context "when a comma-separated list of ports is provided" do
+ let(:params) do
+ {
+ :name => 'apache',
+ :ipaddress => '23.23.23.23',
+ :ports => '80,443'
+ }
+ end
+
+ it { should contain_concat__fragment('apache_listen_block').with(
+ 'order' => '20-apache-00',
+ 'target' => '/etc/haproxy/haproxy.cfg',
+ 'content' => "listen apache\n\n bind 23.23.23.23:80\n\n bind 23.23.23.23:443\n\n balance roundrobin\n option tcplog\n option ssl-hello-chk\n"
+ ) }
+ end
+end
diff --git a/puppet/modules/haproxy/spec/spec.opts b/puppet/modules/haproxy/spec/spec.opts
new file mode 100644
index 00000000..91cd6427
--- /dev/null
+++ b/puppet/modules/haproxy/spec/spec.opts
@@ -0,0 +1,6 @@
+--format
+s
+--colour
+--loadby
+mtime
+--backtrace
diff --git a/puppet/modules/haproxy/spec/spec_helper.rb b/puppet/modules/haproxy/spec/spec_helper.rb
new file mode 100644
index 00000000..2c6f5664
--- /dev/null
+++ b/puppet/modules/haproxy/spec/spec_helper.rb
@@ -0,0 +1 @@
+require 'puppetlabs_spec_helper/module_spec_helper'
diff --git a/puppet/modules/haproxy/templates/haproxy-base.cfg.erb b/puppet/modules/haproxy/templates/haproxy-base.cfg.erb
new file mode 100644
index 00000000..f25d5c34
--- /dev/null
+++ b/puppet/modules/haproxy/templates/haproxy-base.cfg.erb
@@ -0,0 +1,21 @@
+global
+<% @global_options.sort.each do |key,val| -%>
+<% if val.is_a?(Array) -%>
+<% val.each do |item| -%>
+ <%= key %> <%= item %>
+<% end -%>
+<% else -%>
+ <%= key %> <%= val %>
+<% end -%>
+<% end -%>
+
+defaults
+<% @defaults_options.sort.each do |key,val| -%>
+<% if val.is_a?(Array) -%>
+<% val.each do |item| -%>
+ <%= key %> <%= item %>
+<% end -%>
+<% else -%>
+ <%= key %> <%= val %>
+<% end -%>
+<% end -%>
diff --git a/puppet/modules/haproxy/templates/haproxy_balancermember.erb b/puppet/modules/haproxy/templates/haproxy_balancermember.erb
new file mode 100644
index 00000000..1d03f565
--- /dev/null
+++ b/puppet/modules/haproxy/templates/haproxy_balancermember.erb
@@ -0,0 +1,3 @@
+<% Array(ipaddresses).zip(Array(server_names)).each do |ipaddress,host| -%>
+ server <%= host %> <%= ipaddress %>:<%= Array(ports).collect {|x|x.split(',')}.flatten.join(",#{ipaddress}:") %> <%= Array(options).join(" ") %>
+<% end %>
diff --git a/puppet/modules/haproxy/templates/haproxy_listen_block.erb b/puppet/modules/haproxy/templates/haproxy_listen_block.erb
new file mode 100644
index 00000000..129313f1
--- /dev/null
+++ b/puppet/modules/haproxy/templates/haproxy_listen_block.erb
@@ -0,0 +1,10 @@
+listen <%= name %>
+ mode <%= mode %>
+<% Array(ipaddress).uniq.each do |virtual_ip| (ports.is_a?(Array) ? ports : Array(ports.split(","))).each do |port| %>
+ bind <%= virtual_ip %>:<%= port %>
+<% end end %>
+<% options.sort.each do |key, val| -%>
+<% Array(val).each do |item| -%>
+ <%= key %> <%= item %>
+<% end -%>
+<% end -%>
diff --git a/puppet/modules/haproxy/tests/init.pp b/puppet/modules/haproxy/tests/init.pp
new file mode 100644
index 00000000..77590ac8
--- /dev/null
+++ b/puppet/modules/haproxy/tests/init.pp
@@ -0,0 +1,69 @@
+# Declare haproxy base class with configuration options
+class { 'haproxy':
+ enable => true,
+ global_options => {
+ 'log' => "${::ipaddress} local0",
+ 'chroot' => '/var/lib/haproxy',
+ 'pidfile' => '/var/run/haproxy.pid',
+ 'maxconn' => '4000',
+ 'user' => 'haproxy',
+ 'group' => 'haproxy',
+ 'daemon' => '',
+ 'stats' => 'socket /var/lib/haproxy/stats',
+ },
+ defaults_options => {
+ 'log' => 'global',
+ 'stats' => 'enable',
+ 'option' => 'redispatch',
+ 'retries' => '3',
+ 'timeout' => [
+ 'http-request 10s',
+ 'queue 1m',
+ 'connect 10s',
+ 'client 1m',
+ 'server 1m',
+ 'check 10s',
+ ],
+ 'maxconn' => '8000',
+ },
+}
+
+# Export a balancermember server, note that the listening_service parameter
+# will/must correlate with an haproxy::listen defined resource type.
+@@haproxy::balancermember { $fqdn:
+ order => '21',
+ listening_service => 'puppet00',
+ server_name => $::hostname,
+ balancer_ip => $::ipaddress,
+ balancer_port => '8140',
+ balancermember_options => 'check'
+}
+
+# Declare a couple of Listening Services for haproxy.cfg
+# Note that the balancermember server resources are being collected in
+# the haproxy::config defined resource type with the following line:
+# Haproxy::Balancermember <<| listening_service == $name |>>
+haproxy::listen { 'puppet00':
+ order => '20',
+ ipaddress => $::ipaddress,
+ ports => '18140',
+ options => {
+ 'option' => [
+ 'tcplog',
+ 'ssl-hello-chk',
+ ],
+ 'balance' => 'roundrobin',
+ },
+}
+haproxy::listen { 'stats':
+ order => '30',
+ ipaddress => '',
+ ports => '9090',
+ options => {
+ 'mode' => 'http',
+ 'stats' => [
+ 'uri /',
+ 'auth puppet:puppet'
+ ],
+ },
+}
diff --git a/puppet/modules/haveged/manifests/init.pp b/puppet/modules/haveged/manifests/init.pp
new file mode 100644
index 00000000..8f901937
--- /dev/null
+++ b/puppet/modules/haveged/manifests/init.pp
@@ -0,0 +1,16 @@
+class haveged {
+
+ package { 'haveged':
+ ensure => present,
+ }
+
+ service { 'haveged':
+ ensure => running,
+ hasrestart => true,
+ hasstatus => true,
+ enable => true,
+ require => Package['haveged'];
+ }
+
+ include site_check_mk::agent::haveged
+}
diff --git a/puppet/modules/journald/manifests/init.pp b/puppet/modules/journald/manifests/init.pp
new file mode 100644
index 00000000..879baba4
--- /dev/null
+++ b/puppet/modules/journald/manifests/init.pp
@@ -0,0 +1,7 @@
+class journald {
+
+ service { 'systemd-journald':
+ ensure => running,
+ enable => true,
+ }
+}
diff --git a/puppet/modules/leap/manifests/cli/install.pp b/puppet/modules/leap/manifests/cli/install.pp
new file mode 100644
index 00000000..25e87033
--- /dev/null
+++ b/puppet/modules/leap/manifests/cli/install.pp
@@ -0,0 +1,46 @@
+# installs leap_cli on node
+class leap::cli::install ( $source = false ) {
+ if $source {
+ # needed for building leap_cli from source
+ include ::git
+ include ::rubygems
+
+ class { '::ruby':
+ install_dev => true
+ }
+
+ class { 'bundler::install': install_method => 'package' }
+
+ Class[Ruby] ->
+ Class[rubygems] ->
+ Class[bundler::install]
+
+
+ vcsrepo { '/srv/leap/cli':
+ ensure => present,
+ force => true,
+ revision => 'develop',
+ provider => 'git',
+ source => 'https://leap.se/git/leap_cli.git',
+ owner => 'root',
+ group => 'root',
+ notify => Exec['install_leap_cli'],
+ require => Package['git']
+ }
+
+ exec { 'install_leap_cli':
+ command => '/usr/bin/rake build && /usr/bin/rake install',
+ cwd => '/srv/leap/cli',
+ user => 'root',
+ environment => 'USER=root',
+ refreshonly => true,
+ require => [ Class[bundler::install] ]
+ }
+ }
+ else {
+ package { 'leap_cli':
+ ensure => installed,
+ provider => gem
+ }
+ }
+}
diff --git a/puppet/modules/leap/manifests/init.pp b/puppet/modules/leap/manifests/init.pp
new file mode 100644
index 00000000..bbae3781
--- /dev/null
+++ b/puppet/modules/leap/manifests/init.pp
@@ -0,0 +1,3 @@
+class leap {
+
+} \ No newline at end of file
diff --git a/puppet/modules/leap/manifests/logfile.pp b/puppet/modules/leap/manifests/logfile.pp
new file mode 100644
index 00000000..adb3ca8a
--- /dev/null
+++ b/puppet/modules/leap/manifests/logfile.pp
@@ -0,0 +1,34 @@
+#
+# make syslog log to a particular file for a particular process.
+#
+# arguments:
+#
+# * name: what config files are named as (eg. /etc/rsyslog.d/50-$name.conf)
+# * log: the full path of the log file (defaults to /var/log/leap/$name.log
+# * process: the syslog tag to filter on (defaults to name)
+#
+define leap::logfile($process = $name, $log = undef) {
+ if $log {
+ $logfile = $log
+ } else {
+ $logfile = "/var/log/leap/${name}.log"
+ }
+
+ rsyslog::snippet { "50-${name}":
+ content => template('leap/rsyslog.erb')
+ }
+
+ augeas {
+ "logrotate_${name}":
+ context => "/files/etc/logrotate.d/${name}/rule",
+ changes => [
+ "set file ${logfile}",
+ 'set rotate 5',
+ 'set schedule daily',
+ 'set compress compress',
+ 'set missingok missingok',
+ 'set ifempty notifempty',
+ 'set copytruncate copytruncate'
+ ]
+ }
+}
diff --git a/puppet/modules/leap/templates/rsyslog.erb b/puppet/modules/leap/templates/rsyslog.erb
new file mode 100644
index 00000000..7bb5316f
--- /dev/null
+++ b/puppet/modules/leap/templates/rsyslog.erb
@@ -0,0 +1,5 @@
+if $programname startswith '<%= @process %>' then {
+ action(type="omfile" file="<%= @logfile %>" template="RSYSLOG_TraditionalFileFormat")
+ stop
+}
+
diff --git a/puppet/modules/leap_mx/manifests/init.pp b/puppet/modules/leap_mx/manifests/init.pp
new file mode 100644
index 00000000..d758e3ab
--- /dev/null
+++ b/puppet/modules/leap_mx/manifests/init.pp
@@ -0,0 +1,119 @@
+# deploy leap mx service
+class leap_mx {
+
+ $leap_mx = hiera('couchdb_leap_mx_user')
+ $couchdb_user = $leap_mx['username']
+ $couchdb_password = $leap_mx['password']
+
+ $couchdb_host = 'localhost'
+ $couchdb_port = '4096'
+
+ $sources = hiera('sources')
+
+ include soledad::common
+
+ #
+ # USER AND GROUP
+ #
+ # Make the user for leap-mx. This user is where all legitimate, non-system
+ # mail is delivered so leap-mx can process it. Previously, we let the system
+ # pick a uid/gid, but we need to know what they are set to in order to set the
+ # virtual_uid_maps and virtual_gid_maps. Its a bit overkill write a fact just
+ # for this, so instead we pick arbitrary numbers that seem unlikely to be used
+ # and then use them in the postfix configuration
+
+ group { 'leap-mx':
+ ensure => present,
+ gid => 42424,
+ allowdupe => false;
+ }
+
+ user { 'leap-mx':
+ ensure => present,
+ comment => 'Leap Mail',
+ allowdupe => false,
+ uid => 42424,
+ gid => 'leap-mx',
+ home => '/var/mail/leap-mx',
+ shell => '/bin/false',
+ managehome => true,
+ require => Group['leap-mx'];
+ }
+
+ file {
+ '/var/mail/leap-mx':
+ ensure => directory,
+ owner => 'leap-mx',
+ group => 'leap-mx',
+ mode => '0755',
+ require => User['leap-mx'];
+
+ '/var/mail/leap-mx/Maildir':
+ ensure => directory,
+ owner => 'leap-mx',
+ group => 'leap-mx',
+ mode => '0700';
+
+ '/var/mail/leap-mx/Maildir/new':
+ ensure => directory,
+ owner => 'leap-mx',
+ group => 'leap-mx',
+ mode => '0700';
+
+ '/var/mail/leap-mx/Maildir/cur':
+ ensure => directory,
+ owner => 'leap-mx',
+ group => 'leap-mx',
+ mode => '0700';
+
+ '/var/mail/leap-mx/Maildir/tmp':
+ ensure => directory,
+ owner => 'leap-mx',
+ group => 'leap-mx',
+ mode => '0700';
+ }
+
+ #
+ # LEAP-MX CONFIG
+ #
+
+ file { '/etc/leap/mx.conf':
+ content => template('leap_mx/mx.conf.erb'),
+ owner => 'leap-mx',
+ group => 'leap-mx',
+ mode => '0600',
+ notify => Service['leap-mx'];
+ }
+
+ leap::logfile { 'leap-mx':
+ log => '/var/log/leap/mx.log',
+ process => 'leap-mx'
+ }
+
+ #
+ # LEAP-MX CODE AND DEPENDENCIES
+ #
+
+ package {
+ $sources['leap-mx']['package']:
+ ensure => $sources['leap-mx']['revision'],
+ require => [
+ Class['site_apt::leap_repo'],
+ User['leap-mx'] ];
+
+ 'leap-keymanager':
+ ensure => latest;
+ }
+
+ #
+ # LEAP-MX DAEMON
+ #
+
+ service { 'leap-mx':
+ ensure => running,
+ enable => true,
+ hasstatus => true,
+ hasrestart => true,
+ require => [ Package['leap-mx'] ];
+ }
+}
diff --git a/puppet/modules/leap_mx/templates/mx.conf.erb b/puppet/modules/leap_mx/templates/mx.conf.erb
new file mode 100644
index 00000000..b54b3a86
--- /dev/null
+++ b/puppet/modules/leap_mx/templates/mx.conf.erb
@@ -0,0 +1,18 @@
+[mail1]
+path=/var/mail/leap-mx/Maildir
+recursive=True
+
+[couchdb]
+user=<%= @couchdb_user %>
+password=<%= @couchdb_password %>
+server=<%= @couchdb_host %>
+port=<%= @couchdb_port %>
+
+[alias map]
+port=4242
+
+[check recipient]
+port=2244
+
+[fingerprint map]
+port=2424
diff --git a/puppet/modules/lsb/manifests/base.pp b/puppet/modules/lsb/manifests/base.pp
new file mode 100644
index 00000000..9dc8d5a4
--- /dev/null
+++ b/puppet/modules/lsb/manifests/base.pp
@@ -0,0 +1,3 @@
+class lsb::base {
+ package{'lsb': ensure => present }
+}
diff --git a/puppet/modules/lsb/manifests/centos.pp b/puppet/modules/lsb/manifests/centos.pp
new file mode 100644
index 00000000..b7006187
--- /dev/null
+++ b/puppet/modules/lsb/manifests/centos.pp
@@ -0,0 +1,5 @@
+class lsb::centos inherits lsb::base {
+ Package['lsb']{
+ name => 'redhat-lsb',
+ }
+}
diff --git a/puppet/modules/lsb/manifests/debian.pp b/puppet/modules/lsb/manifests/debian.pp
new file mode 100644
index 00000000..c32070f3
--- /dev/null
+++ b/puppet/modules/lsb/manifests/debian.pp
@@ -0,0 +1,6 @@
+class lsb::debian inherits lsb::base {
+ Package['lsb']{
+ name => 'lsb-release',
+ require => undef,
+ }
+}
diff --git a/puppet/modules/lsb/manifests/init.pp b/puppet/modules/lsb/manifests/init.pp
new file mode 100644
index 00000000..85b34e1f
--- /dev/null
+++ b/puppet/modules/lsb/manifests/init.pp
@@ -0,0 +1,6 @@
+class lsb {
+ case $::operatingsystem {
+ debian,ubuntu: { include lsb::debian }
+ centos: { include lsb::centos }
+ }
+}
diff --git a/puppet/modules/nagios/LICENSE b/puppet/modules/nagios/LICENSE
new file mode 100644
index 00000000..94a9ed02
--- /dev/null
+++ b/puppet/modules/nagios/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/puppet/modules/nagios/README b/puppet/modules/nagios/README
new file mode 100644
index 00000000..0c42b4a7
--- /dev/null
+++ b/puppet/modules/nagios/README
@@ -0,0 +1,305 @@
+Introduction/Notes
+==================
+
+This modules was inspired and based on the work of David Schmitt
+The immerda project group adapted and improved this module.
+Mainly we made it using the new native puppet nagios commands
+as well we made it more modular to fit for multidistro usage.
+
+In it's current form, this module can be used on CentOS and Debian.
+
+
+Overview
+========
+
+To use the nagios resources in an puppetmaster setup you need to activate
+[storeconfigs](https://docs.puppetlabs.com/puppet/latest/reference/configuration.html#storeconfigs) on the puppetmaster.
+You can also use this module in a masterless setup, please set the
+`storeconfigs` parameter to `false` when declaring the nafios class.
+
+You need to be running version 0.25 or later of puppet.
+
+
+Monitor
+-------
+
+On one node the "nagios" class has to be included. By default this installs
+apache using the "apache" module. To use lighttpd instead, set the "httpd"
+parameter to the "nagios" class to "lighttpd", or, if the web server is not to
+be managed by puppet, set the "httpd" parameter to "absent".
+
+
+Hosts
+-----
+
+On a node which shall be monitored with nagios, include the "nagios::target".
+This just creates a host declaration for this host's "$::ipaddress" fact. If
+the $::ipaddress of your target is not the one you wish to modify, you can use
+"nagios::target::fqdn" instead, which will use the $::fqdn fact of the host instead.
+
+Pass the $parents variable to the target class for enabling the reachability
+features of nagios. If a node needs more customisation, use the
+native "@@nagios_host" type directly (the double-ampersand declares the object
+as an exported resource).
+
+To monitor hosts not managed by puppet, add "nagios_host" objects to the
+monitoring node. The required parameters are "alias", "address" and "use". If
+you don't specify a proper nagios template with the "use" parameter, some extra
+parameters are needed. You may look up the nagios documentation for this.
+
+
+Services
+--------
+
+Services can be monitored by using the "nagios::service" component.
+
+The simplest form is:
+
+ nagios::service { 'check_http':
+ check_command => 'http_port!80',
+ }
+
+The intention being obviously to put such declarations into a component defining
+a service, thereby being automatically applied together with all instances of
+the service.
+
+Obviously, the check command must either be defined using nagios_command objects
+(some are supplied in nagios::defaults::commands) or in the nagios configuration
+files directly.
+
+NRPE client configuration
+=========================
+
+To setup a machine as an NRPE client, the class 'nagios::nrpe' should be used:
+
+ class { 'nagios::nrpe':
+ allowed_hosts => '10.2.3.4,10.5.6.7',
+ }
+
+The class can take the following parameters to change configuration or
+configuration directory:
+
+ * $cfg_dir : Defines the path to the NRPE configuration. The default is to use
+ the path used by packages per your distro.
+
+ * $pid_file : Sets the path of the PID file. The default value is the path
+ used by init script shipped with your distro's packages.
+
+ * $plugin_dir : Defines the path in which nagios plugins that are to be
+ executed with NRPE commands are stored. The default value is the path where
+ your distro's nagios package stores plugins.
+
+ * $server_address : The IP address to which the NRPE client daemon should
+ bind. The default behaviour is to bind to all IPs.
+
+ * $allowed_hosts : A string containing a comma-separated list of host IPs that
+ are allowed to request NRPE commands to be run. The default value is to
+ allow only 127.0.0.1, so you might want to pass in a list of additional host
+ IPs.
+
+ * $dont_blame : A string that enables ('1') or disables ('0') NRPE command
+ arguments. Enabling arguments can lead to potentials of shell escapes so it
+ should be used with caution and only if absolutely needed. This is disabled
+ by default.
+
+NRPE Services
+-------------
+
+Some Nagios services need to be checked via NRPE. The following will make the
+nagios server define a service that will check the NRPE command 'check_cpu' on
+the current node:
+
+ nagios::service { 'CPU Usage':
+ use_nrpe => true,
+ check_command => "check_cpu",
+ nrpe_args => "-t 60"
+ }
+
+NRPE Commands
+-------------
+
+To be able to call NRPE commands on a host, one needs to define that command
+and what it is going to execute:
+
+ nagios::nrpe::command { 'debsums':
+ check_command => '/usr/lib/nagios/plugins/check_debsums openssh-server'
+ }
+
+
+Upgrade Notes
+=============
+
+The nagios::target bits have been reworked, the notable changes that
+may affect an upgrade are:
+
+. previous versions had nagios::target::nat which used the $::fqdn for
+the address part of nagios::target, this has been renamed to
+nagios::target::fqdn to be more clear. if you were using
+nagios::target::nat then you will need to change those references to
+::fqdn
+
+. previous versions of this module used $::fqdn for the nagios::target
+address, now it is using $::ipaddress. If you need $::fqdn, use
+nagios::target::fqdn instead of nagios::target
+
+. previous versions of nagios_host used the parameter named 'ip', that
+has been changed to 'address'
+
+
+IRC bot
+=======
+
+Notifications can easily be sent to an IRC channel by using a bot. To do so,
+simply include 'nagios::irc_bot' on the nagios server and define the right
+$nagios_nsa_* variables (see the 'Variables' section below).
+
+You can then use the notification commands 'notify-by-irc' and
+'host-notify-by-irc' with service and host definitions to make them report
+state changes over IRC.
+
+Caveats
+=======
+
+
+Consistency/Validation/Verification
+-----------------------------------
+
+After convergance of the configuration, the system is obviously consistent.
+That is, all defined services are monitored. The problem is though, that it is
+neither automatically valid - it is not guaranteed that all components declare a
+nagios::service - and even if the configuration is valid it definitly is
+unverified, since that is always a judgment call for an external observer.
+
+
+Removal of nagios objects
+-------------------------
+
+This module does not automatically purge nagios objects such as hosts and
+services that become absent from the manifests. One must set ensure => absent
+to guarantee the removal of nagios objects from the configuration as desired.
+
+
+Templates not supported using native types
+------------------------------------------
+
+Templates of hosts and services cannot yet be defined using native types. In
+this module, they are provided using a file resource by the class
+nagios::defaults::templates
+
+See : http://projects.reductivelabs.com/issues/1180
+
+
+Variables
+=========
+
+Options to change the behavior of the nagios class:
+
+- allow_external_cmd: Set to true, if you'd like to ensure that your http
+ daemon can write to the external command file. You
+ may also need to flip "check_external_commands" in
+ "nagios.cfg" to enable this functionality.
+
+For the irc_bot class:
+
+- nsa_socket: This optional variable can be used to specify the path to
+ the socket file that the IRC daemon should use.
+
+- nsa_server: When using the IRC bot, this defines the server address of
+ the IRC network on which the bot will connect.
+
+- nsa_port: Defines the port number on the IRC server on which the bot
+ should connect. When this variable is not set, the port used
+ by default is 6667.
+
+- nsa_nickname: This is the nickname that the IRC bot will take.
+
+- nsa_password: Some networks require a password to connect to them.
+ This defines such a password.
+
+- nsa_channel: The name of the channel that the IRC bot will join and
+ will post notifications to.
+
+- nsa_pidfile: This optional variable can be used to define the path to
+ the file that will contain the process ID of the IRC bot
+ daemon.
+- nsa_realname: The IRC bot user's real name that will be displayed. By
+ default, the real name is 'Nagios'.
+
+- nsa_usenotices: The IRC bot will by default "say" to the channel the
+ nagios message, but you can switch this variable to
+ 'notice' if you would prefer them to be sent as IRC
+ NOTICE messages.
+
+PNP4Nagios integration
+======================
+
+For PNP4Nagios integration information, please see README.pnp4nagios
+
+Examples
+========
+
+Usage example:
+
+~~~
+node nagios {
+
+ class { 'nagios': } -> class { 'nagios::defaults': }
+
+ # Declare another nagios command
+ nagios::command { http_port:
+ command_line => '/usr/lib/nagios/plugins/check_http -p $ARG1$ -H $HOSTADDRESS$ -I $HOSTADDRESS$'
+ }
+
+ # Declare unmanaged hosts
+ nagios_host {
+ 'router01.mydomain.com':
+ alias => 'router01',
+ notes => 'MyDomain Gateway',
+ address => '10.0.0.1',
+ use => 'generic-host';
+ 'router02.mydomain.com':
+ alias => 'router02',
+ address => '192.168.0.1',
+ parents => 'router01',
+ use => 'generic-host';
+ }
+
+}
+
+
+node target {
+
+ # Monitor this host
+ class{'nagios::target':
+ parents = 'router01'
+ }
+
+ # monitor a service
+ $apache2_port = 8080
+ include apache2
+
+ # This actually does this somewhere:
+ #nagios::service { "http_${apache2_port}":
+ # check_command => "http_port!${apache2_port}"
+ #}
+
+}
+~~~
+
+TODO
+====
+
+- Provide a default http vhost
+- Add facility to deploy nagios plugins
+- Add more useful commands and services
+- When Puppet will support them, supply nagios templates using native types
+
+
+License
+=======
+
+Copyright (C) 2007 David Schmitt <david@schmitt.edv-bus.at>
+See the file LICENSE in the top directory for the full license.
+
+Copyright (C) 2010 Riseup Networks <micah@riseup.net>
+
diff --git a/puppet/modules/nagios/README.pnp4nagios b/puppet/modules/nagios/README.pnp4nagios
new file mode 100644
index 00000000..dd407021
--- /dev/null
+++ b/puppet/modules/nagios/README.pnp4nagios
@@ -0,0 +1,65 @@
+PNP4Nagios integration
+======================
+
+As of 2012/01 debian packages for pnp4nagios are availible from lenny-backports on.
+
+See http://docs.pnp4nagios.org/pnp-0.6/start for installation notes.
+
+Integration in the nagios/icinga webinterface is configured by including either
+the nagios::pnp4nagios or the nagios::pnp4nagios::popup class, the later one
+includes fancy popups when you hoover over the extra service action image. For
+hosts you need to use the host-pnp definition, for services the srv-pnp def.
+i.e.
+
+ @@nagios_service { "ping_example_node":
+ use => "generic-service,srv-pnp",
+ ...
+
+ nagios_host { 'example_node':
+ use => 'generic-host,host-pnp',
+ ...
+
+In the default config files the "Bulk Mode with NPCD" is used
+(see http://docs.pnp4nagios.org/en/pnp-0.6/config for more infos about the different
+modes).
+
+Include the nagios::pnp4nagios::popup class for fancy popups when you hoover over
+the extra service action image.
+
+
+Please include this in your nagios.cfg:
+
+process_performance_data=1
+
+#http://docs.pnp4nagios.org/en/pnp-0.6/config#bulk_mode_mit_npcd
+#
+# Service Performance-Data
+#
+service_perfdata_file=/var/lib/nagios3/service-perfdata
+service_perfdata_file_template=DATATYPE::SERVICEPERFDATA\tTIMET::$TIMET$\tHOSTNAME::$HOSTNAME$\tSERVICEDESC::$SERVICEDESC$\tSERVICEPERFDATA::$SERVICEPERFDATA$\tSERVICECHECKCOMMAND::$SERVICECHECKCOMMAND$\tHOSTSTATE::$HOSTSTATE$\tHOSTSTATETYPE::$HOSTSTATETYPE$\tSERVICESTATE::$SERVICESTATE$\tSERVICESTATETYPE::$SERVICESTATETYPE$
+service_perfdata_file_mode=a
+service_perfdata_file_processing_interval=15
+service_perfdata_file_processing_command=process-service-perfdata-file-pnp4nagios-bulk-npcd
+
+#
+# Host Performance-Data
+#
+host_perfdata_file=/var/lib/nagios3/host-perfdata
+host_perfdata_file_template=DATATYPE::HOSTPERFDATA\tTIMET::$TIMET$\tHOSTNAME::$HOSTNAME$\tHOSTPERFDATA::$HOSTPERFDATA$\tHOSTCHECKCOMMAND::$HOSTCHECKCOMMAND$\tHOSTSTATE::$HOSTSTATE$\tHOSTSTATETYPE::$HOSTSTATETYPE$
+host_perfdata_file_mode=a
+host_perfdata_file_processing_interval=15
+host_perfdata_file_processing_command=process-host-perfdata-file-pnp4nagios-bulk-npcd
+
+
+For hosts you need to use the host-pnp definition, for services the srv-pnp def.
+i.e.
+
+ @@nagios_service { "ping_example_node":
+ use => "generic-service,srv-pnp",
+ ...
+
+ nagios_host { 'example_node':
+ use => 'generic-host,host-pnp',
+ ...
+
+
diff --git a/puppet/modules/nagios/files/configs/CentOS/cgi.cfg b/puppet/modules/nagios/files/configs/CentOS/cgi.cfg
new file mode 100644
index 00000000..cd625d4c
--- /dev/null
+++ b/puppet/modules/nagios/files/configs/CentOS/cgi.cfg
@@ -0,0 +1,280 @@
+#################################################################
+#
+# CGI.CFG - Sample CGI Configuration File for Nagios 2.9
+#
+# Last Modified: 11-21-2006
+#
+#################################################################
+
+
+# MAIN CONFIGURATION FILE
+# This tells the CGIs where to find your main configuration file.
+# The CGIs will read the main and host config files for any other
+# data they might need.
+
+main_config_file=/etc/nagios/nagios.cfg
+
+
+
+# PHYSICAL HTML PATH
+# This is the path where the HTML files for Nagios reside. This
+# value is used to locate the logo images needed by the statusmap
+# and statuswrl CGIs.
+
+physical_html_path=/usr/share/nagios/share
+
+
+
+# URL HTML PATH
+# This is the path portion of the URL that corresponds to the
+# physical location of the Nagios HTML files (as defined above).
+# This value is used by the CGIs to locate the online documentation
+# and graphics. If you access the Nagios pages with an URL like
+# http://www.myhost.com/nagios, this value should be '/nagios'
+# (without the quotes).
+
+url_html_path=/nagios
+
+
+
+# CONTEXT-SENSITIVE HELP
+# This option determines whether or not a context-sensitive
+# help icon will be displayed for most of the CGIs.
+# Values: 0 = disables context-sensitive help
+# 1 = enables context-sensitive help
+
+show_context_help=0
+
+
+
+# NAGIOS PROCESS CHECK COMMAND
+# This is the full path and filename of the program used to check
+# the status of the Nagios process. It is used only by the CGIs
+# and is completely optional. However, if you don't use it, you'll
+# see warning messages in the CGIs about the Nagios process
+# not running and you won't be able to execute any commands from
+# the web interface. The program should follow the same rules
+# as plugins; the return codes are the same as for the plugins,
+# it should have timeout protection, it should output something
+# to STDIO, etc.
+#
+# Note: The command line for the check_nagios plugin below may
+# have to be tweaked a bit, as different versions of the plugin
+# use different command line arguments/syntaxes.
+
+#nagios_check_command=/usr/lib64/nagios/plugins/check_nagios /var/log/nagios/status.dat 5 '/usr/sbin/nagios'
+
+
+
+# AUTHENTICATION USAGE
+# This option controls whether or not the CGIs will use any
+# authentication when displaying host and service information, as
+# well as committing commands to Nagios for processing.
+#
+# Read the HTML documentation to learn how the authorization works!
+#
+# NOTE: It is a really *bad* idea to disable authorization, unless
+# you plan on removing the command CGI (cmd.cgi)! Failure to do
+# so will leave you wide open to kiddies messing with Nagios and
+# possibly hitting you with a denial of service attack by filling up
+# your drive by continuously writing to your command file!
+#
+# Setting this value to 0 will cause the CGIs to *not* use
+# authentication (bad idea), while any other value will make them
+# use the authentication functions (the default).
+
+use_authentication=1
+
+
+
+# DEFAULT USER
+# Setting this variable will define a default user name that can
+# access pages without authentication. This allows people within a
+# secure domain (i.e., behind a firewall) to see the current status
+# without authenticating. You may want to use this to avoid basic
+# authentication if you are not using a secure server since basic
+# authentication transmits passwords in the clear.
+#
+# Important: Do not define a default username unless you are
+# running a secure web server and are sure that everyone who has
+# access to the CGIs has been authenticated in some manner! If you
+# define this variable, anyone who has not authenticated to the web
+# server will inherit all rights you assign to this user!
+
+#default_user_name=guest
+
+
+
+# SYSTEM/PROCESS INFORMATION ACCESS
+# This option is a comma-delimited list of all usernames that
+# have access to viewing the Nagios process information as
+# provided by the Extended Information CGI (extinfo.cgi). By
+# default, *no one* has access to this unless you choose to
+# not use authorization. You may use an asterisk (*) to
+# authorize any user who has authenticated to the web server.
+
+#authorized_for_system_information=nagiosadmin,theboss,jdoe
+authorized_for_system_information=admin
+
+# CONFIGURATION INFORMATION ACCESS
+# This option is a comma-delimited list of all usernames that
+# can view ALL configuration information (hosts, commands, etc).
+# By default, users can only view configuration information
+# for the hosts and services they are contacts for. You may use
+# an asterisk (*) to authorize any user who has authenticated
+# to the web server.
+
+#authorized_for_configuration_information=nagiosadmin,jdoe
+authorized_for_configuration_information=admin
+
+
+
+# SYSTEM/PROCESS COMMAND ACCESS
+# This option is a comma-delimited list of all usernames that
+# can issue shutdown and restart commands to Nagios via the
+# command CGI (cmd.cgi). Users in this list can also change
+# the program mode to active or standby. By default, *no one*
+# has access to this unless you choose to not use authorization.
+# You may use an asterisk (*) to authorize any user who has
+# authenticated to the web server.
+
+#authorized_for_system_commands=nagiosadmin
+authorized_for_system_commands=admin
+
+
+
+# GLOBAL HOST/SERVICE VIEW ACCESS
+# These two options are comma-delimited lists of all usernames that
+# can view information for all hosts and services that are being
+# monitored. By default, users can only view information
+# for hosts or services that they are contacts for (unless you
+# you choose to not use authorization). You may use an asterisk (*)
+# to authorize any user who has authenticated to the web server.
+
+
+#authorized_for_all_services=nagiosadmin,guest
+authorized_for_all_services=admin
+#authorized_for_all_hosts=nagiosadmin,guest
+authorized_for_all_hosts=admin
+
+
+
+# GLOBAL HOST/SERVICE COMMAND ACCESS
+# These two options are comma-delimited lists of all usernames that
+# can issue host or service related commands via the command
+# CGI (cmd.cgi) for all hosts and services that are being monitored.
+# By default, users can only issue commands for hosts or services
+# that they are contacts for (unless you you choose to not use
+# authorization). You may use an asterisk (*) to authorize any
+# user who has authenticated to the web server.
+
+#authorized_for_all_service_commands=nagiosadmin
+authorized_for_all_service_commands=admin
+#authorized_for_all_host_commands=nagiosadmin
+authorized_for_all_host_commands=admin
+
+
+
+
+# STATUSMAP BACKGROUND IMAGE
+# This option allows you to specify an image to be used as a
+# background in the statusmap CGI. It is assumed that the image
+# resides in the HTML images path (i.e. /usr/local/nagios/share/images).
+# This path is automatically determined by appending "/images"
+# to the path specified by the 'physical_html_path' directive.
+# Note: The image file may be in GIF, PNG, JPEG, or GD2 format.
+# However, I recommend that you convert your image to GD2 format
+# (uncompressed), as this will cause less CPU load when the CGI
+# generates the image.
+
+#statusmap_background_image=smbackground.gd2
+
+
+
+# DEFAULT STATUSMAP LAYOUT METHOD
+# This option allows you to specify the default layout method
+# the statusmap CGI should use for drawing hosts. If you do
+# not use this option, the default is to use user-defined
+# coordinates. Valid options are as follows:
+# 0 = User-defined coordinates
+# 1 = Depth layers
+# 2 = Collapsed tree
+# 3 = Balanced tree
+# 4 = Circular
+# 5 = Circular (Marked Up)
+
+default_statusmap_layout=5
+
+
+
+# DEFAULT STATUSWRL LAYOUT METHOD
+# This option allows you to specify the default layout method
+# the statuswrl (VRML) CGI should use for drawing hosts. If you
+# do not use this option, the default is to use user-defined
+# coordinates. Valid options are as follows:
+# 0 = User-defined coordinates
+# 2 = Collapsed tree
+# 3 = Balanced tree
+# 4 = Circular
+
+default_statuswrl_layout=4
+
+
+
+# STATUSWRL INCLUDE
+# This option allows you to include your own objects in the
+# generated VRML world. It is assumed that the file
+# resides in the HTML path (i.e. /usr/local/nagios/share).
+
+#statuswrl_include=myworld.wrl
+
+
+
+# PING SYNTAX
+# This option determines what syntax should be used when
+# attempting to ping a host from the WAP interface (using
+# the statuswml CGI. You must include the full path to
+# the ping binary, along with all required options. The
+# $HOSTADDRESS$ macro is substituted with the address of
+# the host before the command is executed.
+# Please note that the syntax for the ping binary is
+# notorious for being different on virtually ever *NIX
+# OS and distribution, so you may have to tweak this to
+# work on your system.
+
+ping_syntax=/bin/ping -n -U -c 5 $HOSTADDRESS$
+
+
+
+# REFRESH RATE
+# This option allows you to specify the refresh rate in seconds
+# of various CGIs (status, statusmap, extinfo, and outages).
+
+refresh_rate=90
+
+
+
+# SOUND OPTIONS
+# These options allow you to specify an optional audio file
+# that should be played in your browser window when there are
+# problems on the network. The audio files are used only in
+# the status CGI. Only the sound for the most critical problem
+# will be played. Order of importance (higher to lower) is as
+# follows: unreachable hosts, down hosts, critical services,
+# warning services, and unknown services. If there are no
+# visible problems, the sound file optionally specified by
+# 'normal_sound' variable will be played.
+#
+#
+# <varname>=<sound_file>
+#
+# Note: All audio files must be placed in the /media subdirectory
+# under the HTML path (i.e. /usr/local/nagios/share/media/).
+
+#host_unreachable_sound=hostdown.wav
+#host_down_sound=hostdown.wav
+#service_critical_sound=critical.wav
+#service_warning_sound=warning.wav
+#service_unknown_sound=warning.wav
+#normal_sound=noproblem.wav
+
diff --git a/puppet/modules/nagios/files/configs/CentOS/nagios.cfg b/puppet/modules/nagios/files/configs/CentOS/nagios.cfg
new file mode 100644
index 00000000..b88e3db7
--- /dev/null
+++ b/puppet/modules/nagios/files/configs/CentOS/nagios.cfg
@@ -0,0 +1,949 @@
+##############################################################################
+#
+# NAGIOS.CFG - Sample Main Config File for Nagios 2.10
+#
+# Read the documentation for more information on this configuration
+# file. I've provided some comments here, but things may not be so
+# clear without further explanation.
+#
+# Last Modified: 12-21-2006
+#
+##############################################################################
+
+
+# LOG FILE
+# This is the main log file where service and host events are logged
+# for historical purposes. This should be the first option specified
+# in the config file!!!
+
+log_file=/var/log/nagios/nagios.log
+
+
+
+# OBJECT CONFIGURATION FILE(S)
+# This is the configuration file in which you define hosts, host
+# groups, contacts, contact groups, services, etc. I guess it would
+# be better called an object definition file, but for historical
+# reasons it isn't. You can split object definitions into several
+# different config files by using multiple cfg_file statements here.
+# Nagios will read and process all the config files you define.
+# This can be very useful if you want to keep command definitions
+# separate from host and contact definitions...
+
+# Puppet-managed configuration files
+cfg_file=/etc/nagios/nagios_templates.cfg
+cfg_file=/etc/nagios/nagios_command.cfg
+cfg_file=/etc/nagios/nagios_contact.cfg
+cfg_file=/etc/nagios/nagios_contactgroup.cfg
+cfg_file=/etc/nagios/nagios_host.cfg
+cfg_file=/etc/nagios/nagios_hostdependency.cfg
+cfg_file=/etc/nagios/nagios_hostescalation.cfg
+cfg_file=/etc/nagios/nagios_hostextinfo.cfg
+cfg_file=/etc/nagios/nagios_hostgroup.cfg
+cfg_file=/etc/nagios/nagios_hostgroupescalation.cfg
+cfg_file=/etc/nagios/nagios_service.cfg
+cfg_file=/etc/nagios/nagios_servicedependency.cfg
+cfg_file=/etc/nagios/nagios_serviceescalation.cfg
+cfg_file=/etc/nagios/nagios_serviceextinfo.cfg
+cfg_file=/etc/nagios/nagios_servicegroup.cfg
+cfg_file=/etc/nagios/nagios_timeperiod.cfg
+
+# OBJECT CACHE FILE
+# This option determines where object definitions are cached when
+# Nagios starts/restarts. The CGIs read object definitions from
+# this cache file (rather than looking at the object config files
+# directly) in order to prevent inconsistencies that can occur
+# when the config files are modified after Nagios starts.
+
+object_cache_file=/var/log/nagios/objects.cache
+
+
+
+# RESOURCE FILE
+# This is an optional resource file that contains $USERx$ macro
+# definitions. Multiple resource files can be specified by using
+# multiple resource_file definitions. The CGIs will not attempt to
+# read the contents of resource files, so information that is
+# considered to be sensitive (usernames, passwords, etc) can be
+# defined as macros in this file and restrictive permissions (600)
+# can be placed on this file.
+
+resource_file=/etc/nagios/private/resource.cfg
+
+
+
+# STATUS FILE
+# This is where the current status of all monitored services and
+# hosts is stored. Its contents are read and processed by the CGIs.
+# The contents of the status file are deleted every time Nagios
+# restarts.
+
+status_file=/var/log/nagios/status.dat
+
+
+
+# NAGIOS USER
+# This determines the effective user that Nagios should run as.
+# You can either supply a username or a UID.
+
+nagios_user=nagios
+
+
+
+# NAGIOS GROUP
+# This determines the effective group that Nagios should run as.
+# You can either supply a group name or a GID.
+
+nagios_group=nagios
+
+
+
+# EXTERNAL COMMAND OPTION
+# This option allows you to specify whether or not Nagios should check
+# for external commands (in the command file defined below). By default
+# Nagios will *not* check for external commands, just to be on the
+# cautious side. If you want to be able to use the CGI command interface
+# you will have to enable this. Setting this value to 0 disables command
+# checking (the default), other values enable it.
+
+check_external_commands=0
+
+
+
+# EXTERNAL COMMAND CHECK INTERVAL
+# This is the interval at which Nagios should check for external commands.
+# This value works of the interval_length you specify later. If you leave
+# that at its default value of 60 (seconds), a value of 1 here will cause
+# Nagios to check for external commands every minute. If you specify a
+# number followed by an "s" (i.e. 15s), this will be interpreted to mean
+# actual seconds rather than a multiple of the interval_length variable.
+# Note: In addition to reading the external command file at regularly
+# scheduled intervals, Nagios will also check for external commands after
+# event handlers are executed.
+# NOTE: Setting this value to -1 causes Nagios to check the external
+# command file as often as possible.
+
+#command_check_interval=15s
+command_check_interval=-1
+
+
+
+# EXTERNAL COMMAND FILE
+# This is the file that Nagios checks for external command requests.
+# It is also where the command CGI will write commands that are submitted
+# by users, so it must be writeable by the user that the web server
+# is running as (usually 'nobody'). Permissions should be set at the
+# directory level instead of on the file, as the file is deleted every
+# time its contents are processed.
+
+command_file=/var/spool/nagios/cmd/nagios.cmd
+
+
+
+# EXTERNAL COMMAND BUFFER SLOTS
+# This settings is used to tweak the number of items or "slots" that
+# the Nagios daemon should allocate to the buffer that holds incoming
+# external commands before they are processed. As external commands
+# are processed by the daemon, they are removed from the buffer.
+
+external_command_buffer_slots=4096
+
+
+
+# COMMENT FILE
+# This is the file that Nagios will use for storing host and service
+# comments.
+
+comment_file=/var/log/nagios/comments.dat
+
+
+
+# DOWNTIME FILE
+# This is the file that Nagios will use for storing host and service
+# downtime data.
+
+downtime_file=/var/log/nagios/downtime.dat
+
+
+
+# LOCK FILE
+# This is the lockfile that Nagios will use to store its PID number
+# in when it is running in daemon mode.
+
+lock_file=/var/run/nagios.pid
+
+
+
+# TEMP FILE
+# This is a temporary file that is used as scratch space when Nagios
+# updates the status log, cleans the comment file, etc. This file
+# is created, used, and deleted throughout the time that Nagios is
+# running.
+
+temp_file=/var/log/nagios/nagios.tmp
+
+
+
+# EVENT BROKER OPTIONS
+# Controls what (if any) data gets sent to the event broker.
+# Values: 0 = Broker nothing
+# -1 = Broker everything
+# <other> = See documentation
+
+event_broker_options=-1
+
+
+
+# EVENT BROKER MODULE(S)
+# This directive is used to specify an event broker module that should
+# by loaded by Nagios at startup. Use multiple directives if you want
+# to load more than one module. Arguments that should be passed to
+# the module at startup are seperated from the module path by a space.
+#
+# Example:
+#
+# broker_module=<modulepath> [moduleargs]
+
+#broker_module=/somewhere/module1.o
+#broker_module=/somewhere/module2.o arg1 arg2=3 debug=0
+
+
+
+
+# LOG ROTATION METHOD
+# This is the log rotation method that Nagios should use to rotate
+# the main log file. Values are as follows..
+# n = None - don't rotate the log
+# h = Hourly rotation (top of the hour)
+# d = Daily rotation (midnight every day)
+# w = Weekly rotation (midnight on Saturday evening)
+# m = Monthly rotation (midnight last day of month)
+
+log_rotation_method=d
+
+
+
+# LOG ARCHIVE PATH
+# This is the directory where archived (rotated) log files should be
+# placed (assuming you've chosen to do log rotation).
+
+log_archive_path=/var/log/nagios/archives
+
+
+
+# LOGGING OPTIONS
+# If you want messages logged to the syslog facility, as well as the
+# NetAlarm log file set this option to 1. If not, set it to 0.
+
+use_syslog=1
+
+
+
+# NOTIFICATION LOGGING OPTION
+# If you don't want notifications to be logged, set this value to 0.
+# If notifications should be logged, set the value to 1.
+
+log_notifications=1
+
+
+
+# SERVICE RETRY LOGGING OPTION
+# If you don't want service check retries to be logged, set this value
+# to 0. If retries should be logged, set the value to 1.
+
+log_service_retries=1
+
+
+
+# HOST RETRY LOGGING OPTION
+# If you don't want host check retries to be logged, set this value to
+# 0. If retries should be logged, set the value to 1.
+
+log_host_retries=1
+
+
+
+# EVENT HANDLER LOGGING OPTION
+# If you don't want host and service event handlers to be logged, set
+# this value to 0. If event handlers should be logged, set the value
+# to 1.
+
+log_event_handlers=1
+
+
+
+# INITIAL STATES LOGGING OPTION
+# If you want Nagios to log all initial host and service states to
+# the main log file (the first time the service or host is checked)
+# you can enable this option by setting this value to 1. If you
+# are not using an external application that does long term state
+# statistics reporting, you do not need to enable this option. In
+# this case, set the value to 0.
+
+log_initial_states=0
+
+
+
+# EXTERNAL COMMANDS LOGGING OPTION
+# If you don't want Nagios to log external commands, set this value
+# to 0. If external commands should be logged, set this value to 1.
+# Note: This option does not include logging of passive service
+# checks - see the option below for controlling whether or not
+# passive checks are logged.
+
+log_external_commands=1
+
+
+
+# PASSIVE CHECKS LOGGING OPTION
+# If you don't want Nagios to log passive host and service checks, set
+# this value to 0. If passive checks should be logged, set
+# this value to 1.
+
+log_passive_checks=1
+
+
+
+# GLOBAL HOST AND SERVICE EVENT HANDLERS
+# These options allow you to specify a host and service event handler
+# command that is to be run for every host or service state change.
+# The global event handler is executed immediately prior to the event
+# handler that you have optionally specified in each host or
+# service definition. The command argument is the short name of a
+# command definition that you define in your host configuration file.
+# Read the HTML docs for more information.
+
+#global_host_event_handler=somecommand
+#global_service_event_handler=somecommand
+
+
+
+# SERVICE INTER-CHECK DELAY METHOD
+# This is the method that Nagios should use when initially
+# "spreading out" service checks when it starts monitoring. The
+# default is to use smart delay calculation, which will try to
+# space all service checks out evenly to minimize CPU load.
+# Using the dumb setting will cause all checks to be scheduled
+# at the same time (with no delay between them)! This is not a
+# good thing for production, but is useful when testing the
+# parallelization functionality.
+# n = None - don't use any delay between checks
+# d = Use a "dumb" delay of 1 second between checks
+# s = Use "smart" inter-check delay calculation
+# x.xx = Use an inter-check delay of x.xx seconds
+
+service_inter_check_delay_method=s
+
+
+
+# MAXIMUM SERVICE CHECK SPREAD
+# This variable determines the timeframe (in minutes) from the
+# program start time that an initial check of all services should
+# be completed. Default is 30 minutes.
+
+max_service_check_spread=30
+
+
+
+# SERVICE CHECK INTERLEAVE FACTOR
+# This variable determines how service checks are interleaved.
+# Interleaving the service checks allows for a more even
+# distribution of service checks and reduced load on remote
+# hosts. Setting this value to 1 is equivalent to how versions
+# of Nagios previous to 0.0.5 did service checks. Set this
+# value to s (smart) for automatic calculation of the interleave
+# factor unless you have a specific reason to change it.
+# s = Use "smart" interleave factor calculation
+# x = Use an interleave factor of x, where x is a
+# number greater than or equal to 1.
+
+service_interleave_factor=s
+
+
+
+# HOST INTER-CHECK DELAY METHOD
+# This is the method that Nagios should use when initially
+# "spreading out" host checks when it starts monitoring. The
+# default is to use smart delay calculation, which will try to
+# space all host checks out evenly to minimize CPU load.
+# Using the dumb setting will cause all checks to be scheduled
+# at the same time (with no delay between them)!
+# n = None - don't use any delay between checks
+# d = Use a "dumb" delay of 1 second between checks
+# s = Use "smart" inter-check delay calculation
+# x.xx = Use an inter-check delay of x.xx seconds
+
+host_inter_check_delay_method=s
+
+
+
+# MAXIMUM HOST CHECK SPREAD
+# This variable determines the timeframe (in minutes) from the
+# program start time that an initial check of all hosts should
+# be completed. Default is 30 minutes.
+
+max_host_check_spread=30
+
+
+
+# MAXIMUM CONCURRENT SERVICE CHECKS
+# This option allows you to specify the maximum number of
+# service checks that can be run in parallel at any given time.
+# Specifying a value of 1 for this variable essentially prevents
+# any service checks from being parallelized. A value of 0
+# will not restrict the number of concurrent checks that are
+# being executed.
+
+max_concurrent_checks=0
+
+
+
+# SERVICE CHECK REAPER FREQUENCY
+# This is the frequency (in seconds!) that Nagios will process
+# the results of services that have been checked.
+
+service_reaper_frequency=10
+
+
+
+# CHECK RESULT BUFFER SLOTS
+# This settings is used to tweak the number of items or "slots" that
+# the Nagios daemon should allocate to the buffer that holds
+# service check results before they are processed. As check results
+# are processed by the daemon, they are removed from the buffer.
+
+check_result_buffer_slots=4096
+
+
+
+# AUTO-RESCHEDULING OPTION
+# This option determines whether or not Nagios will attempt to
+# automatically reschedule active host and service checks to
+# "smooth" them out over time. This can help balance the load on
+# the monitoring server.
+# WARNING: THIS IS AN EXPERIMENTAL FEATURE - IT CAN DEGRADE
+# PERFORMANCE, RATHER THAN INCREASE IT, IF USED IMPROPERLY
+
+auto_reschedule_checks=0
+
+
+
+# AUTO-RESCHEDULING INTERVAL
+# This option determines how often (in seconds) Nagios will
+# attempt to automatically reschedule checks. This option only
+# has an effect if the auto_reschedule_checks option is enabled.
+# Default is 30 seconds.
+# WARNING: THIS IS AN EXPERIMENTAL FEATURE - IT CAN DEGRADE
+# PERFORMANCE, RATHER THAN INCREASE IT, IF USED IMPROPERLY
+
+auto_rescheduling_interval=30
+
+
+
+
+# AUTO-RESCHEDULING WINDOW
+# This option determines the "window" of time (in seconds) that
+# Nagios will look at when automatically rescheduling checks.
+# Only host and service checks that occur in the next X seconds
+# (determined by this variable) will be rescheduled. This option
+# only has an effect if the auto_reschedule_checks option is
+# enabled. Default is 180 seconds (3 minutes).
+# WARNING: THIS IS AN EXPERIMENTAL FEATURE - IT CAN DEGRADE
+# PERFORMANCE, RATHER THAN INCREASE IT, IF USED IMPROPERLY
+
+auto_rescheduling_window=180
+
+
+
+# SLEEP TIME
+# This is the number of seconds to sleep between checking for system
+# events and service checks that need to be run.
+
+sleep_time=0.25
+
+
+
+# TIMEOUT VALUES
+# These options control how much time Nagios will allow various
+# types of commands to execute before killing them off. Options
+# are available for controlling maximum time allotted for
+# service checks, host checks, event handlers, notifications, the
+# ocsp command, and performance data commands. All values are in
+# seconds.
+
+service_check_timeout=60
+host_check_timeout=30
+event_handler_timeout=30
+notification_timeout=30
+ocsp_timeout=5
+perfdata_timeout=5
+
+
+
+# RETAIN STATE INFORMATION
+# This setting determines whether or not Nagios will save state
+# information for services and hosts before it shuts down. Upon
+# startup Nagios will reload all saved service and host state
+# information before starting to monitor. This is useful for
+# maintaining long-term data on state statistics, etc, but will
+# slow Nagios down a bit when it (re)starts. Since its only
+# a one-time penalty, I think its well worth the additional
+# startup delay.
+
+retain_state_information=1
+
+
+
+# STATE RETENTION FILE
+# This is the file that Nagios should use to store host and
+# service state information before it shuts down. The state
+# information in this file is also read immediately prior to
+# starting to monitor the network when Nagios is restarted.
+# This file is used only if the preserve_state_information
+# variable is set to 1.
+
+state_retention_file=/var/log/nagios/retention.dat
+
+
+
+# RETENTION DATA UPDATE INTERVAL
+# This setting determines how often (in minutes) that Nagios
+# will automatically save retention data during normal operation.
+# If you set this value to 0, Nagios will not save retention
+# data at regular interval, but it will still save retention
+# data before shutting down or restarting. If you have disabled
+# state retention, this option has no effect.
+
+retention_update_interval=60
+
+
+
+# USE RETAINED PROGRAM STATE
+# This setting determines whether or not Nagios will set
+# program status variables based on the values saved in the
+# retention file. If you want to use retained program status
+# information, set this value to 1. If not, set this value
+# to 0.
+
+use_retained_program_state=1
+
+
+
+# USE RETAINED SCHEDULING INFO
+# This setting determines whether or not Nagios will retain
+# the scheduling info (next check time) for hosts and services
+# based on the values saved in the retention file. If you
+# If you want to use retained scheduling info, set this
+# value to 1. If not, set this value to 0.
+
+use_retained_scheduling_info=0
+
+
+
+# INTERVAL LENGTH
+# This is the seconds per unit interval as used in the
+# host/contact/service configuration files. Setting this to 60 means
+# that each interval is one minute long (60 seconds). Other settings
+# have not been tested much, so your mileage is likely to vary...
+
+interval_length=60
+
+
+
+# AGGRESSIVE HOST CHECKING OPTION
+# If you don't want to turn on aggressive host checking features, set
+# this value to 0 (the default). Otherwise set this value to 1 to
+# enable the aggressive check option. Read the docs for more info
+# on what aggressive host check is or check out the source code in
+# base/checks.c
+
+use_aggressive_host_checking=0
+
+
+
+# SERVICE CHECK EXECUTION OPTION
+# This determines whether or not Nagios will actively execute
+# service checks when it initially starts. If this option is
+# disabled, checks are not actively made, but Nagios can still
+# receive and process passive check results that come in. Unless
+# you're implementing redundant hosts or have a special need for
+# disabling the execution of service checks, leave this enabled!
+# Values: 1 = enable checks, 0 = disable checks
+
+execute_service_checks=1
+
+
+
+# PASSIVE SERVICE CHECK ACCEPTANCE OPTION
+# This determines whether or not Nagios will accept passive
+# service checks results when it initially (re)starts.
+# Values: 1 = accept passive checks, 0 = reject passive checks
+
+accept_passive_service_checks=1
+
+
+
+# HOST CHECK EXECUTION OPTION
+# This determines whether or not Nagios will actively execute
+# host checks when it initially starts. If this option is
+# disabled, checks are not actively made, but Nagios can still
+# receive and process passive check results that come in. Unless
+# you're implementing redundant hosts or have a special need for
+# disabling the execution of host checks, leave this enabled!
+# Values: 1 = enable checks, 0 = disable checks
+
+execute_host_checks=1
+
+
+
+# PASSIVE HOST CHECK ACCEPTANCE OPTION
+# This determines whether or not Nagios will accept passive
+# host checks results when it initially (re)starts.
+# Values: 1 = accept passive checks, 0 = reject passive checks
+
+accept_passive_host_checks=1
+
+
+
+# NOTIFICATIONS OPTION
+# This determines whether or not Nagios will sent out any host or
+# service notifications when it is initially (re)started.
+# Values: 1 = enable notifications, 0 = disable notifications
+
+enable_notifications=1
+
+
+
+# EVENT HANDLER USE OPTION
+# This determines whether or not Nagios will run any host or
+# service event handlers when it is initially (re)started. Unless
+# you're implementing redundant hosts, leave this option enabled.
+# Values: 1 = enable event handlers, 0 = disable event handlers
+
+enable_event_handlers=1
+
+
+
+# PROCESS PERFORMANCE DATA OPTION
+# This determines whether or not Nagios will process performance
+# data returned from service and host checks. If this option is
+# enabled, host performance data will be processed using the
+# host_perfdata_command (defined below) and service performance
+# data will be processed using the service_perfdata_command (also
+# defined below). Read the HTML docs for more information on
+# performance data.
+# Values: 1 = process performance data, 0 = do not process performance data
+
+process_performance_data=0
+
+
+
+# HOST AND SERVICE PERFORMANCE DATA PROCESSING COMMANDS
+# These commands are run after every host and service check is
+# performed. These commands are executed only if the
+# enable_performance_data option (above) is set to 1. The command
+# argument is the short name of a command definition that you
+# define in your host configuration file. Read the HTML docs for
+# more information on performance data.
+
+#host_perfdata_command=process-host-perfdata
+#service_perfdata_command=process-service-perfdata
+
+
+
+# HOST AND SERVICE PERFORMANCE DATA FILES
+# These files are used to store host and service performance data.
+# Performance data is only written to these files if the
+# enable_performance_data option (above) is set to 1.
+
+#host_perfdata_file=/tmp/host-perfdata
+#service_perfdata_file=/tmp/service-perfdata
+
+
+
+# HOST AND SERVICE PERFORMANCE DATA FILE TEMPLATES
+# These options determine what data is written (and how) to the
+# performance data files. The templates may contain macros, special
+# characters (\t for tab, \r for carriage return, \n for newline)
+# and plain text. A newline is automatically added after each write
+# to the performance data file. Some examples of what you can do are
+# shown below.
+
+#host_perfdata_file_template=[HOSTPERFDATA]\t$TIMET$\t$HOSTNAME$\t$HOSTEXECUTIONTIME$\t$HOSTOUTPUT$\t$HOSTPERFDATA$
+#service_perfdata_file_template=[SERVICEPERFDATA]\t$TIMET$\t$HOSTNAME$\t$SERVICEDESC$\t$SERVICEEXECUTIONTIME$\t$SERVICELATENCY$\t$SERVICEOUTPUT$\t$SERVICEPERFDATA$
+
+
+
+
+# HOST AND SERVICE PERFORMANCE DATA FILE MODES
+# This option determines whether or not the host and service
+# performance data files are opened in write ("w") or append ("a")
+# mode. Unless you are the files are named pipes, you will probably
+# want to use the default mode of append ("a").
+
+#host_perfdata_file_mode=a
+#service_perfdata_file_mode=a
+
+
+
+# HOST AND SERVICE PERFORMANCE DATA FILE PROCESSING INTERVAL
+# These options determine how often (in seconds) the host and service
+# performance data files are processed using the commands defined
+# below. A value of 0 indicates the files should not be periodically
+# processed.
+
+#host_perfdata_file_processing_interval=0
+#service_perfdata_file_processing_interval=0
+
+
+
+# HOST AND SERVICE PERFORMANCE DATA FILE PROCESSING COMMANDS
+# These commands are used to periodically process the host and
+# service performance data files. The interval at which the
+# processing occurs is determined by the options above.
+
+#host_perfdata_file_processing_command=process-host-perfdata-file
+#service_perfdata_file_processing_command=process-service-perfdata-file
+
+
+
+# OBSESS OVER SERVICE CHECKS OPTION
+# This determines whether or not Nagios will obsess over service
+# checks and run the ocsp_command defined below. Unless you're
+# planning on implementing distributed monitoring, do not enable
+# this option. Read the HTML docs for more information on
+# implementing distributed monitoring.
+# Values: 1 = obsess over services, 0 = do not obsess (default)
+
+obsess_over_services=0
+
+
+
+# OBSESSIVE COMPULSIVE SERVICE PROCESSOR COMMAND
+# This is the command that is run for every service check that is
+# processed by Nagios. This command is executed only if the
+# obsess_over_service option (above) is set to 1. The command
+# argument is the short name of a command definition that you
+# define in your host configuration file. Read the HTML docs for
+# more information on implementing distributed monitoring.
+
+#ocsp_command=somecommand
+
+
+
+# ORPHANED SERVICE CHECK OPTION
+# This determines whether or not Nagios will periodically
+# check for orphaned services. Since service checks are not
+# rescheduled until the results of their previous execution
+# instance are processed, there exists a possibility that some
+# checks may never get rescheduled. This seems to be a rare
+# problem and should not happen under normal circumstances.
+# If you have problems with service checks never getting
+# rescheduled, you might want to try enabling this option.
+# Values: 1 = enable checks, 0 = disable checks
+
+check_for_orphaned_services=1
+
+
+
+# SERVICE FRESHNESS CHECK OPTION
+# This option determines whether or not Nagios will periodically
+# check the "freshness" of service results. Enabling this option
+# is useful for ensuring passive checks are received in a timely
+# manner.
+# Values: 1 = enabled freshness checking, 0 = disable freshness checking
+
+check_service_freshness=1
+
+
+
+# SERVICE FRESHNESS CHECK INTERVAL
+# This setting determines how often (in seconds) Nagios will
+# check the "freshness" of service check results. If you have
+# disabled service freshness checking, this option has no effect.
+
+service_freshness_check_interval=60
+
+
+
+# HOST FRESHNESS CHECK OPTION
+# This option determines whether or not Nagios will periodically
+# check the "freshness" of host results. Enabling this option
+# is useful for ensuring passive checks are received in a timely
+# manner.
+# Values: 1 = enabled freshness checking, 0 = disable freshness checking
+
+check_host_freshness=0
+
+
+
+# HOST FRESHNESS CHECK INTERVAL
+# This setting determines how often (in seconds) Nagios will
+# check the "freshness" of host check results. If you have
+# disabled host freshness checking, this option has no effect.
+
+host_freshness_check_interval=60
+
+
+
+# AGGREGATED STATUS UPDATES
+# This option determines whether or not Nagios will
+# aggregate updates of host, service, and program status
+# data. Normally, status data is updated immediately when
+# a change occurs. This can result in high CPU loads if
+# you are monitoring a lot of services. If you want Nagios
+# to only refresh status data every few seconds, disable
+# this option.
+# Values: 1 = enable aggregate updates, 0 = disable aggregate updates
+
+aggregate_status_updates=1
+
+
+
+# AGGREGATED STATUS UPDATE INTERVAL
+# Combined with the aggregate_status_updates option,
+# this option determines the frequency (in seconds!) that
+# Nagios will periodically dump program, host, and
+# service status data. If you are not using aggregated
+# status data updates, this option has no effect.
+
+status_update_interval=15
+
+
+
+# FLAP DETECTION OPTION
+# This option determines whether or not Nagios will try
+# and detect hosts and services that are "flapping".
+# Flapping occurs when a host or service changes between
+# states too frequently. When Nagios detects that a
+# host or service is flapping, it will temporarily suppress
+# notifications for that host/service until it stops
+# flapping. Flap detection is very experimental, so read
+# the HTML documentation before enabling this feature!
+# Values: 1 = enable flap detection
+# 0 = disable flap detection (default)
+
+enable_flap_detection=0
+
+
+
+# FLAP DETECTION THRESHOLDS FOR HOSTS AND SERVICES
+# Read the HTML documentation on flap detection for
+# an explanation of what this option does. This option
+# has no effect if flap detection is disabled.
+
+low_service_flap_threshold=5.0
+high_service_flap_threshold=20.0
+low_host_flap_threshold=5.0
+high_host_flap_threshold=20.0
+
+
+
+# DATE FORMAT OPTION
+# This option determines how short dates are displayed. Valid options
+# include:
+# us (MM-DD-YYYY HH:MM:SS)
+# euro (DD-MM-YYYY HH:MM:SS)
+# iso8601 (YYYY-MM-DD HH:MM:SS)
+# strict-iso8601 (YYYY-MM-DDTHH:MM:SS)
+#
+
+date_format=us
+
+
+
+# P1.PL FILE LOCATION
+# This value determines where the p1.pl perl script (used by the
+# embedded Perl interpreter) is located. If you didn't compile
+# Nagios with embedded Perl support, this option has no effect.
+
+p1_file=/usr/sbin/p1.pl
+
+
+
+# ILLEGAL OBJECT NAME CHARACTERS
+# This option allows you to specify illegal characters that cannot
+# be used in host names, service descriptions, or names of other
+# object types.
+
+illegal_object_name_chars=`~!$%^&*|'"<>?,()=
+
+
+
+# ILLEGAL MACRO OUTPUT CHARACTERS
+# This option allows you to specify illegal characters that are
+# stripped from macros before being used in notifications, event
+# handlers, etc. This DOES NOT affect macros used in service or
+# host check commands.
+# The following macros are stripped of the characters you specify:
+# $HOSTOUTPUT$
+# $HOSTPERFDATA$
+# $HOSTACKAUTHOR$
+# $HOSTACKCOMMENT$
+# $SERVICEOUTPUT$
+# $SERVICEPERFDATA$
+# $SERVICEACKAUTHOR$
+# $SERVICEACKCOMMENT$
+
+illegal_macro_output_chars=`~$&|'"<>
+
+
+
+# REGULAR EXPRESSION MATCHING
+# This option controls whether or not regular expression matching
+# takes place in the object config files. Regular expression
+# matching is used to match host, hostgroup, service, and service
+# group names/descriptions in some fields of various object types.
+# Values: 1 = enable regexp matching, 0 = disable regexp matching
+
+use_regexp_matching=0
+
+
+
+# "TRUE" REGULAR EXPRESSION MATCHING
+# This option controls whether or not "true" regular expression
+# matching takes place in the object config files. This option
+# only has an effect if regular expression matching is enabled
+# (see above). If this option is DISABLED, regular expression
+# matching only occurs if a string contains wildcard characters
+# (* and ?). If the option is ENABLED, regexp matching occurs
+# all the time (which can be annoying).
+# Values: 1 = enable true matching, 0 = disable true matching
+
+use_true_regexp_matching=0
+
+
+
+
+# ADMINISTRATOR EMAIL ADDRESS
+# The email address of the administrator of *this* machine (the one
+# doing the monitoring). Nagios never uses this value itself, but
+# you can access this value by using the $ADMINEMAIL$ macro in your
+# notification commands.
+
+admin_email=nagios
+
+
+
+# ADMINISTRATOR PAGER NUMBER/ADDRESS
+# The pager number/address for the administrator of *this* machine.
+# Nagios never uses this value itself, but you can access this
+# value by using the $ADMINPAGER$ macro in your notification
+# commands.
+
+admin_pager=pagenagios
+
+
+
+# DAEMON CORE DUMP OPTION
+# This option determines whether or not Nagios is allowed to create
+# a core dump when it runs as a daemon. Note that it is generally
+# considered bad form to allow this, but it may be useful for
+# debugging purposes.
+# Values: 1 - Allow core dumps
+# 0 - Do not allow core dumps (default)
+
+daemon_dumps_core=0
+
+
+
diff --git a/puppet/modules/nagios/files/configs/CentOS/private/resource.cfg.i386 b/puppet/modules/nagios/files/configs/CentOS/private/resource.cfg.i386
new file mode 100644
index 00000000..0ccf2e17
--- /dev/null
+++ b/puppet/modules/nagios/files/configs/CentOS/private/resource.cfg.i386
@@ -0,0 +1,34 @@
+###########################################################################
+#
+# RESOURCE.CFG - Sample Resource File for Nagios 2.9
+#
+# Last Modified: 09-10-2003
+#
+# You can define $USERx$ macros in this file, which can in turn be used
+# in command definitions in your host config file(s). $USERx$ macros are
+# useful for storing sensitive information such as usernames, passwords,
+# etc. They are also handy for specifying the path to plugins and
+# event handlers - if you decide to move the plugins or event handlers to
+# a different directory in the future, you can just update one or two
+# $USERx$ macros, instead of modifying a lot of command definitions.
+#
+# The CGIs will not attempt to read the contents of resource files, so
+# you can set restrictive permissions (600 or 660) on them.
+#
+# Nagios supports up to 32 $USERx$ macros ($USER1$ through $USER32$)
+#
+# Resource files may also be used to store configuration directives for
+# external data sources like MySQL...
+#
+###########################################################################
+
+# Sets $USER1$ to be the path to the plugins
+$USER1$=/usr/lib/nagios/plugins
+
+# Sets $USER2$ to be the path to event handlers
+#$USER2$=/usr/lib64/nagios/plugins/eventhandlers
+
+# Store some usernames and passwords (hidden from the CGIs)
+#$USER3$=someuser
+#$USER4$=somepassword
+
diff --git a/puppet/modules/nagios/files/configs/CentOS/private/resource.cfg.x86_64 b/puppet/modules/nagios/files/configs/CentOS/private/resource.cfg.x86_64
new file mode 100644
index 00000000..b9f0841c
--- /dev/null
+++ b/puppet/modules/nagios/files/configs/CentOS/private/resource.cfg.x86_64
@@ -0,0 +1,34 @@
+###########################################################################
+#
+# RESOURCE.CFG - Sample Resource File for Nagios 2.9
+#
+# Last Modified: 09-10-2003
+#
+# You can define $USERx$ macros in this file, which can in turn be used
+# in command definitions in your host config file(s). $USERx$ macros are
+# useful for storing sensitive information such as usernames, passwords,
+# etc. They are also handy for specifying the path to plugins and
+# event handlers - if you decide to move the plugins or event handlers to
+# a different directory in the future, you can just update one or two
+# $USERx$ macros, instead of modifying a lot of command definitions.
+#
+# The CGIs will not attempt to read the contents of resource files, so
+# you can set restrictive permissions (600 or 660) on them.
+#
+# Nagios supports up to 32 $USERx$ macros ($USER1$ through $USER32$)
+#
+# Resource files may also be used to store configuration directives for
+# external data sources like MySQL...
+#
+###########################################################################
+
+# Sets $USER1$ to be the path to the plugins
+$USER1$=/usr/lib64/nagios/plugins
+
+# Sets $USER2$ to be the path to event handlers
+#$USER2$=/usr/lib64/nagios/plugins/eventhandlers
+
+# Store some usernames and passwords (hidden from the CGIs)
+#$USER3$=someuser
+#$USER4$=somepassword
+
diff --git a/puppet/modules/nagios/files/configs/Debian/cgi.cfg b/puppet/modules/nagios/files/configs/Debian/cgi.cfg
new file mode 100644
index 00000000..103db8a1
--- /dev/null
+++ b/puppet/modules/nagios/files/configs/Debian/cgi.cfg
@@ -0,0 +1,330 @@
+#################################################################
+#
+# CGI.CFG - Sample CGI Configuration File for Nagios
+#
+#################################################################
+
+
+# MAIN CONFIGURATION FILE
+# This tells the CGIs where to find your main configuration file.
+# The CGIs will read the main and host config files for any other
+# data they might need.
+
+main_config_file=/etc/nagios3/nagios.cfg
+
+
+
+# PHYSICAL HTML PATH
+# This is the path where the HTML files for Nagios reside. This
+# value is used to locate the logo images needed by the statusmap
+# and statuswrl CGIs.
+
+physical_html_path=/usr/share/nagios3/htdocs
+
+
+
+# URL HTML PATH
+# This is the path portion of the URL that corresponds to the
+# physical location of the Nagios HTML files (as defined above).
+# This value is used by the CGIs to locate the online documentation
+# and graphics. If you access the Nagios pages with an URL like
+# http://www.myhost.com/nagios, this value should be '/nagios'
+# (without the quotes).
+
+url_html_path=/nagios3
+
+
+
+# CONTEXT-SENSITIVE HELP
+# This option determines whether or not a context-sensitive
+# help icon will be displayed for most of the CGIs.
+# Values: 0 = disables context-sensitive help
+# 1 = enables context-sensitive help
+
+show_context_help=1
+
+
+
+# PENDING STATES OPTION
+# This option determines what states should be displayed in the web
+# interface for hosts/services that have not yet been checked.
+# Values: 0 = leave hosts/services that have not been check yet in their original state
+# 1 = mark hosts/services that have not been checked yet as PENDING
+
+use_pending_states=1
+
+nagios_check_command=/usr/lib/nagios/plugins/check_nagios /var/cache/nagios3/status.dat 5 '/usr/sbin/nagios3'
+
+
+# AUTHENTICATION USAGE
+# This option controls whether or not the CGIs will use any
+# authentication when displaying host and service information, as
+# well as committing commands to Nagios for processing.
+#
+# Read the HTML documentation to learn how the authorization works!
+#
+# NOTE: It is a really *bad* idea to disable authorization, unless
+# you plan on removing the command CGI (cmd.cgi)! Failure to do
+# so will leave you wide open to kiddies messing with Nagios and
+# possibly hitting you with a denial of service attack by filling up
+# your drive by continuously writing to your command file!
+#
+# Setting this value to 0 will cause the CGIs to *not* use
+# authentication (bad idea), while any other value will make them
+# use the authentication functions (the default).
+
+use_authentication=1
+
+
+
+
+# x509 CERT AUTHENTICATION
+# When enabled, this option allows you to use x509 cert (SSL)
+# authentication in the CGIs. This is an advanced option and should
+# not be enabled unless you know what you're doing.
+
+use_ssl_authentication=0
+
+
+
+
+# DEFAULT USER
+# Setting this variable will define a default user name that can
+# access pages without authentication. This allows people within a
+# secure domain (i.e., behind a firewall) to see the current status
+# without authenticating. You may want to use this to avoid basic
+# authentication if you are not using a secure server since basic
+# authentication transmits passwords in the clear.
+#
+# Important: Do not define a default username unless you are
+# running a secure web server and are sure that everyone who has
+# access to the CGIs has been authenticated in some manner! If you
+# define this variable, anyone who has not authenticated to the web
+# server will inherit all rights you assign to this user!
+
+#default_user_name=guest
+
+
+
+# SYSTEM/PROCESS INFORMATION ACCESS
+# This option is a comma-delimited list of all usernames that
+# have access to viewing the Nagios process information as
+# provided by the Extended Information CGI (extinfo.cgi). By
+# default, *no one* has access to this unless you choose to
+# not use authorization. You may use an asterisk (*) to
+# authorize any user who has authenticated to the web server.
+
+authorized_for_system_information=nagiosadmin
+
+
+
+# CONFIGURATION INFORMATION ACCESS
+# This option is a comma-delimited list of all usernames that
+# can view ALL configuration information (hosts, commands, etc).
+# By default, users can only view configuration information
+# for the hosts and services they are contacts for. You may use
+# an asterisk (*) to authorize any user who has authenticated
+# to the web server.
+
+authorized_for_configuration_information=nagiosadmin
+
+
+
+# SYSTEM/PROCESS COMMAND ACCESS
+# This option is a comma-delimited list of all usernames that
+# can issue shutdown and restart commands to Nagios via the
+# command CGI (cmd.cgi). Users in this list can also change
+# the program mode to active or standby. By default, *no one*
+# has access to this unless you choose to not use authorization.
+# You may use an asterisk (*) to authorize any user who has
+# authenticated to the web server.
+
+authorized_for_system_commands=nagiosadmin
+
+
+
+# GLOBAL HOST/SERVICE VIEW ACCESS
+# These two options are comma-delimited lists of all usernames that
+# can view information for all hosts and services that are being
+# monitored. By default, users can only view information
+# for hosts or services that they are contacts for (unless you
+# you choose to not use authorization). You may use an asterisk (*)
+# to authorize any user who has authenticated to the web server.
+
+
+authorized_for_all_services=nagiosadmin
+authorized_for_all_hosts=nagiosadmin
+
+
+
+# GLOBAL HOST/SERVICE COMMAND ACCESS
+# These two options are comma-delimited lists of all usernames that
+# can issue host or service related commands via the command
+# CGI (cmd.cgi) for all hosts and services that are being monitored.
+# By default, users can only issue commands for hosts or services
+# that they are contacts for (unless you you choose to not use
+# authorization). You may use an asterisk (*) to authorize any
+# user who has authenticated to the web server.
+
+authorized_for_all_service_commands=nagiosadmin
+authorized_for_all_host_commands=nagiosadmin
+
+
+
+
+# STATUSMAP BACKGROUND IMAGE
+# This option allows you to specify an image to be used as a
+# background in the statusmap CGI. It is assumed that the image
+# resides in the HTML images path (i.e. /usr/local/nagios/share/images).
+# This path is automatically determined by appending "/images"
+# to the path specified by the 'physical_html_path' directive.
+# Note: The image file may be in GIF, PNG, JPEG, or GD2 format.
+# However, I recommend that you convert your image to GD2 format
+# (uncompressed), as this will cause less CPU load when the CGI
+# generates the image.
+
+#statusmap_background_image=smbackground.gd2
+
+
+
+# DEFAULT STATUSMAP LAYOUT METHOD
+# This option allows you to specify the default layout method
+# the statusmap CGI should use for drawing hosts. If you do
+# not use this option, the default is to use user-defined
+# coordinates. Valid options are as follows:
+# 0 = User-defined coordinates
+# 1 = Depth layers
+# 2 = Collapsed tree
+# 3 = Balanced tree
+# 4 = Circular
+# 5 = Circular (Marked Up)
+
+default_statusmap_layout=5
+
+
+
+# DEFAULT STATUSWRL LAYOUT METHOD
+# This option allows you to specify the default layout method
+# the statuswrl (VRML) CGI should use for drawing hosts. If you
+# do not use this option, the default is to use user-defined
+# coordinates. Valid options are as follows:
+# 0 = User-defined coordinates
+# 2 = Collapsed tree
+# 3 = Balanced tree
+# 4 = Circular
+
+default_statuswrl_layout=4
+
+
+
+# STATUSWRL INCLUDE
+# This option allows you to include your own objects in the
+# generated VRML world. It is assumed that the file
+# resides in the HTML path (i.e. /usr/local/nagios/share).
+
+#statuswrl_include=myworld.wrl
+
+
+
+# PING SYNTAX
+# This option determines what syntax should be used when
+# attempting to ping a host from the WAP interface (using
+# the statuswml CGI. You must include the full path to
+# the ping binary, along with all required options. The
+# $HOSTADDRESS$ macro is substituted with the address of
+# the host before the command is executed.
+# Please note that the syntax for the ping binary is
+# notorious for being different on virtually ever *NIX
+# OS and distribution, so you may have to tweak this to
+# work on your system.
+
+ping_syntax=/bin/ping -n -U -c 5 $HOSTADDRESS$
+
+
+
+# REFRESH RATE
+# This option allows you to specify the refresh rate in seconds
+# of various CGIs (status, statusmap, extinfo, and outages).
+
+refresh_rate=90
+
+
+
+# ESCAPE HTML TAGS
+# This option determines whether HTML tags in host and service
+# status output is escaped in the web interface. If enabled,
+# your plugin output will not be able to contain clickable links.
+
+escape_html_tags=1
+
+
+
+
+# SOUND OPTIONS
+# These options allow you to specify an optional audio file
+# that should be played in your browser window when there are
+# problems on the network. The audio files are used only in
+# the status CGI. Only the sound for the most critical problem
+# will be played. Order of importance (higher to lower) is as
+# follows: unreachable hosts, down hosts, critical services,
+# warning services, and unknown services. If there are no
+# visible problems, the sound file optionally specified by
+# 'normal_sound' variable will be played.
+#
+#
+# <varname>=<sound_file>
+#
+# Note: All audio files must be placed in the /media subdirectory
+# under the HTML path (i.e. /usr/local/nagios/share/media/).
+
+#host_unreachable_sound=hostdown.wav
+#host_down_sound=hostdown.wav
+#service_critical_sound=critical.wav
+#service_warning_sound=warning.wav
+#service_unknown_sound=warning.wav
+#normal_sound=noproblem.wav
+
+
+
+# URL TARGET FRAMES
+# These options determine the target frames in which notes and
+# action URLs will open.
+
+action_url_target=_blank
+notes_url_target=_blank
+
+
+
+
+# LOCK AUTHOR NAMES OPTION
+# This option determines whether users can change the author name
+# when submitting comments, scheduling downtime. If disabled, the
+# author names will be locked into their contact name, as defined in Nagios.
+# Values: 0 = allow editing author names
+# 1 = lock author names (disallow editing)
+
+lock_author_names=1
+
+
+
+
+# SPLUNK INTEGRATION OPTIONS
+# These options allow you to enable integration with Splunk
+# in the web interface. If enabled, you'll be presented with
+# "Splunk It" links in various places in the CGIs (log file,
+# alert history, host/service detail, etc). Useful if you're
+# trying to research why a particular problem occurred.
+# For more information on Splunk, visit http://www.splunk.com/
+
+# This option determines whether the Splunk integration is enabled
+# Values: 0 = disable Splunk integration
+# 1 = enable Splunk integration
+
+#enable_splunk_integration=1
+
+
+# This option should be the URL used to access your instance of Splunk
+
+#splunk_url=http://127.0.0.1:8000/
+
+
diff --git a/puppet/modules/nagios/files/configs/Debian/nagios.cfg b/puppet/modules/nagios/files/configs/Debian/nagios.cfg
new file mode 100644
index 00000000..291a4741
--- /dev/null
+++ b/puppet/modules/nagios/files/configs/Debian/nagios.cfg
@@ -0,0 +1,1288 @@
+##############################################################################
+#
+# NAGIOS.CFG - Sample Main Config File for Nagios
+#
+#
+##############################################################################
+
+
+# LOG FILE
+# This is the main log file where service and host events are logged
+# for historical purposes. This should be the first option specified
+# in the config file!!!
+
+log_file=/var/log/nagios3/nagios.log
+
+
+
+# OBJECT CONFIGURATION FILE(S)
+# These are the object configuration files in which you define hosts,
+# host groups, contacts, contact groups, services, etc.
+# You can split your object definitions across several config files
+# if you wish (as shown below), or keep them all in a single config file.
+#cfg_file=/etc/nagios3/commands.cfg
+
+# Puppet-managed configuration files
+cfg_file=/etc/nagios3/nagios_templates.cfg
+cfg_file=/etc/nagios3/nagios_command.cfg
+cfg_file=/etc/nagios3/nagios_contact.cfg
+cfg_file=/etc/nagios3/nagios_contactgroup.cfg
+cfg_file=/etc/nagios3/nagios_host.cfg
+cfg_file=/etc/nagios3/nagios_hostdependency.cfg
+cfg_file=/etc/nagios3/nagios_hostescalation.cfg
+cfg_file=/etc/nagios3/nagios_hostextinfo.cfg
+cfg_file=/etc/nagios3/nagios_hostgroup.cfg
+cfg_file=/etc/nagios3/nagios_hostgroupescalation.cfg
+cfg_file=/etc/nagios3/nagios_service.cfg
+cfg_file=/etc/nagios3/nagios_servicedependency.cfg
+cfg_file=/etc/nagios3/nagios_serviceescalation.cfg
+cfg_file=/etc/nagios3/nagios_serviceextinfo.cfg
+cfg_file=/etc/nagios3/nagios_servicegroup.cfg
+cfg_file=/etc/nagios3/nagios_timeperiod.cfg
+
+# Debian also defaults to using the check commands defined by the debian
+# nagios-plugins package
+cfg_dir=/etc/nagios-plugins/config
+
+
+
+# OBJECT CACHE FILE
+# This option determines where object definitions are cached when
+# Nagios starts/restarts. The CGIs read object definitions from
+# this cache file (rather than looking at the object config files
+# directly) in order to prevent inconsistencies that can occur
+# when the config files are modified after Nagios starts.
+
+object_cache_file=/var/cache/nagios3/objects.cache
+
+
+
+# PRE-CACHED OBJECT FILE
+# This options determines the location of the precached object file.
+# If you run Nagios with the -p command line option, it will preprocess
+# your object configuration file(s) and write the cached config to this
+# file. You can then start Nagios with the -u option to have it read
+# object definitions from this precached file, rather than the standard
+# object configuration files (see the cfg_file and cfg_dir options above).
+# Using a precached object file can speed up the time needed to (re)start
+# the Nagios process if you've got a large and/or complex configuration.
+# Read the documentation section on optimizing Nagios to find our more
+# about how this feature works.
+
+precached_object_file=/var/lib/nagios3/objects.precache
+
+
+
+# RESOURCE FILE
+# This is an optional resource file that contains $USERx$ macro
+# definitions. Multiple resource files can be specified by using
+# multiple resource_file definitions. The CGIs will not attempt to
+# read the contents of resource files, so information that is
+# considered to be sensitive (usernames, passwords, etc) can be
+# defined as macros in this file and restrictive permissions (600)
+# can be placed on this file.
+
+resource_file=/etc/nagios3/resource.cfg
+
+
+
+# STATUS FILE
+# This is where the current status of all monitored services and
+# hosts is stored. Its contents are read and processed by the CGIs.
+# The contents of the status file are deleted every time Nagios
+# restarts.
+
+status_file=/var/cache/nagios3/status.dat
+
+
+
+# STATUS FILE UPDATE INTERVAL
+# This option determines the frequency (in seconds) that
+# Nagios will periodically dump program, host, and
+# service status data.
+
+status_update_interval=10
+
+
+
+# NAGIOS USER
+# This determines the effective user that Nagios should run as.
+# You can either supply a username or a UID.
+
+nagios_user=nagios
+
+
+
+# NAGIOS GROUP
+# This determines the effective group that Nagios should run as.
+# You can either supply a group name or a GID.
+
+nagios_group=nagios
+
+
+
+# EXTERNAL COMMAND OPTION
+# This option allows you to specify whether or not Nagios should check
+# for external commands (in the command file defined below). By default
+# Nagios will *not* check for external commands, just to be on the
+# cautious side. If you want to be able to use the CGI command interface
+# you will have to enable this.
+# Values: 0 = disable commands, 1 = enable commands
+
+check_external_commands=0
+
+
+
+# EXTERNAL COMMAND CHECK INTERVAL
+# This is the interval at which Nagios should check for external commands.
+# This value works of the interval_length you specify later. If you leave
+# that at its default value of 60 (seconds), a value of 1 here will cause
+# Nagios to check for external commands every minute. If you specify a
+# number followed by an "s" (i.e. 15s), this will be interpreted to mean
+# actual seconds rather than a multiple of the interval_length variable.
+# Note: In addition to reading the external command file at regularly
+# scheduled intervals, Nagios will also check for external commands after
+# event handlers are executed.
+# NOTE: Setting this value to -1 causes Nagios to check the external
+# command file as often as possible.
+
+#command_check_interval=15s
+command_check_interval=-1
+
+
+
+# EXTERNAL COMMAND FILE
+# This is the file that Nagios checks for external command requests.
+# It is also where the command CGI will write commands that are submitted
+# by users, so it must be writeable by the user that the web server
+# is running as (usually 'nobody'). Permissions should be set at the
+# directory level instead of on the file, as the file is deleted every
+# time its contents are processed.
+# Debian Users: In case you didn't read README.Debian yet, _NOW_ is the
+# time to do it.
+
+command_file=/var/lib/nagios3/rw/nagios.cmd
+
+
+
+# EXTERNAL COMMAND BUFFER SLOTS
+# This settings is used to tweak the number of items or "slots" that
+# the Nagios daemon should allocate to the buffer that holds incoming
+# external commands before they are processed. As external commands
+# are processed by the daemon, they are removed from the buffer.
+
+external_command_buffer_slots=4096
+
+
+
+# LOCK FILE
+# This is the lockfile that Nagios will use to store its PID number
+# in when it is running in daemon mode.
+
+lock_file=/var/run/nagios3/nagios3.pid
+
+
+
+# TEMP FILE
+# This is a temporary file that is used as scratch space when Nagios
+# updates the status log, cleans the comment file, etc. This file
+# is created, used, and deleted throughout the time that Nagios is
+# running.
+
+temp_file=/var/cache/nagios3/nagios.tmp
+
+
+
+# TEMP PATH
+# This is path where Nagios can create temp files for service and
+# host check results, etc.
+
+temp_path=/tmp
+
+
+
+# EVENT BROKER OPTIONS
+# Controls what (if any) data gets sent to the event broker.
+# Values: 0 = Broker nothing
+# -1 = Broker everything
+# <other> = See documentation
+
+event_broker_options=-1
+
+
+
+# EVENT BROKER MODULE(S)
+# This directive is used to specify an event broker module that should
+# by loaded by Nagios at startup. Use multiple directives if you want
+# to load more than one module. Arguments that should be passed to
+# the module at startup are seperated from the module path by a space.
+#
+#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+# WARNING !!! WARNING !!! WARNING !!! WARNING !!! WARNING !!! WARNING
+#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+#
+# Do NOT overwrite modules while they are being used by Nagios or Nagios
+# will crash in a fiery display of SEGFAULT glory. This is a bug/limitation
+# either in dlopen(), the kernel, and/or the filesystem. And maybe Nagios...
+#
+# The correct/safe way of updating a module is by using one of these methods:
+# 1. Shutdown Nagios, replace the module file, restart Nagios
+# 2. Delete the original module file, move the new module file into place, restart Nagios
+#
+# Example:
+#
+# broker_module=<modulepath> [moduleargs]
+
+#broker_module=/somewhere/module1.o
+#broker_module=/somewhere/module2.o arg1 arg2=3 debug=0
+
+
+
+# LOG ROTATION METHOD
+# This is the log rotation method that Nagios should use to rotate
+# the main log file. Values are as follows..
+# n = None - don't rotate the log
+# h = Hourly rotation (top of the hour)
+# d = Daily rotation (midnight every day)
+# w = Weekly rotation (midnight on Saturday evening)
+# m = Monthly rotation (midnight last day of month)
+
+log_rotation_method=d
+
+
+
+# LOG ARCHIVE PATH
+# This is the directory where archived (rotated) log files should be
+# placed (assuming you've chosen to do log rotation).
+
+log_archive_path=/var/log/nagios3/archives
+
+
+
+# LOGGING OPTIONS
+# If you want messages logged to the syslog facility, as well as the
+# Nagios log file set this option to 1. If not, set it to 0.
+
+use_syslog=1
+
+
+
+# NOTIFICATION LOGGING OPTION
+# If you don't want notifications to be logged, set this value to 0.
+# If notifications should be logged, set the value to 1.
+
+log_notifications=1
+
+
+
+# SERVICE RETRY LOGGING OPTION
+# If you don't want service check retries to be logged, set this value
+# to 0. If retries should be logged, set the value to 1.
+
+log_service_retries=1
+
+
+
+# HOST RETRY LOGGING OPTION
+# If you don't want host check retries to be logged, set this value to
+# 0. If retries should be logged, set the value to 1.
+
+log_host_retries=1
+
+
+
+# EVENT HANDLER LOGGING OPTION
+# If you don't want host and service event handlers to be logged, set
+# this value to 0. If event handlers should be logged, set the value
+# to 1.
+
+log_event_handlers=1
+
+
+
+# INITIAL STATES LOGGING OPTION
+# If you want Nagios to log all initial host and service states to
+# the main log file (the first time the service or host is checked)
+# you can enable this option by setting this value to 1. If you
+# are not using an external application that does long term state
+# statistics reporting, you do not need to enable this option. In
+# this case, set the value to 0.
+
+log_initial_states=0
+
+
+
+# EXTERNAL COMMANDS LOGGING OPTION
+# If you don't want Nagios to log external commands, set this value
+# to 0. If external commands should be logged, set this value to 1.
+# Note: This option does not include logging of passive service
+# checks - see the option below for controlling whether or not
+# passive checks are logged.
+
+log_external_commands=1
+
+
+
+# PASSIVE CHECKS LOGGING OPTION
+# If you don't want Nagios to log passive host and service checks, set
+# this value to 0. If passive checks should be logged, set
+# this value to 1.
+
+log_passive_checks=1
+
+
+
+# GLOBAL HOST AND SERVICE EVENT HANDLERS
+# These options allow you to specify a host and service event handler
+# command that is to be run for every host or service state change.
+# The global event handler is executed immediately prior to the event
+# handler that you have optionally specified in each host or
+# service definition. The command argument is the short name of a
+# command definition that you define in your host configuration file.
+# Read the HTML docs for more information.
+
+#global_host_event_handler=somecommand
+#global_service_event_handler=somecommand
+
+
+
+# SERVICE INTER-CHECK DELAY METHOD
+# This is the method that Nagios should use when initially
+# "spreading out" service checks when it starts monitoring. The
+# default is to use smart delay calculation, which will try to
+# space all service checks out evenly to minimize CPU load.
+# Using the dumb setting will cause all checks to be scheduled
+# at the same time (with no delay between them)! This is not a
+# good thing for production, but is useful when testing the
+# parallelization functionality.
+# n = None - don't use any delay between checks
+# d = Use a "dumb" delay of 1 second between checks
+# s = Use "smart" inter-check delay calculation
+# x.xx = Use an inter-check delay of x.xx seconds
+
+service_inter_check_delay_method=s
+
+
+
+# MAXIMUM SERVICE CHECK SPREAD
+# This variable determines the timeframe (in minutes) from the
+# program start time that an initial check of all services should
+# be completed. Default is 30 minutes.
+
+max_service_check_spread=30
+
+
+
+# SERVICE CHECK INTERLEAVE FACTOR
+# This variable determines how service checks are interleaved.
+# Interleaving the service checks allows for a more even
+# distribution of service checks and reduced load on remote
+# hosts. Setting this value to 1 is equivalent to how versions
+# of Nagios previous to 0.0.5 did service checks. Set this
+# value to s (smart) for automatic calculation of the interleave
+# factor unless you have a specific reason to change it.
+# s = Use "smart" interleave factor calculation
+# x = Use an interleave factor of x, where x is a
+# number greater than or equal to 1.
+
+service_interleave_factor=s
+
+
+
+# HOST INTER-CHECK DELAY METHOD
+# This is the method that Nagios should use when initially
+# "spreading out" host checks when it starts monitoring. The
+# default is to use smart delay calculation, which will try to
+# space all host checks out evenly to minimize CPU load.
+# Using the dumb setting will cause all checks to be scheduled
+# at the same time (with no delay between them)!
+# n = None - don't use any delay between checks
+# d = Use a "dumb" delay of 1 second between checks
+# s = Use "smart" inter-check delay calculation
+# x.xx = Use an inter-check delay of x.xx seconds
+
+host_inter_check_delay_method=s
+
+
+
+# MAXIMUM HOST CHECK SPREAD
+# This variable determines the timeframe (in minutes) from the
+# program start time that an initial check of all hosts should
+# be completed. Default is 30 minutes.
+
+max_host_check_spread=30
+
+
+
+# MAXIMUM CONCURRENT SERVICE CHECKS
+# This option allows you to specify the maximum number of
+# service checks that can be run in parallel at any given time.
+# Specifying a value of 1 for this variable essentially prevents
+# any service checks from being parallelized. A value of 0
+# will not restrict the number of concurrent checks that are
+# being executed.
+
+max_concurrent_checks=0
+
+
+
+# HOST AND SERVICE CHECK REAPER FREQUENCY
+# This is the frequency (in seconds!) that Nagios will process
+# the results of host and service checks.
+
+check_result_reaper_frequency=10
+
+
+
+
+# MAX CHECK RESULT REAPER TIME
+# This is the max amount of time (in seconds) that a single
+# check result reaper event will be allowed to run before
+# returning control back to Nagios so it can perform other
+# duties.
+
+max_check_result_reaper_time=30
+
+
+
+
+# CHECK RESULT PATH
+# This is directory where Nagios stores the results of host and
+# service checks that have not yet been processed.
+#
+# Note: Make sure that only one instance of Nagios has access
+# to this directory!
+
+check_result_path=/var/lib/nagios3/spool/checkresults
+
+
+
+
+# MAX CHECK RESULT FILE AGE
+# This option determines the maximum age (in seconds) which check
+# result files are considered to be valid. Files older than this
+# threshold will be mercilessly deleted without further processing.
+
+max_check_result_file_age=3600
+
+
+
+
+# CACHED HOST CHECK HORIZON
+# This option determines the maximum amount of time (in seconds)
+# that the state of a previous host check is considered current.
+# Cached host states (from host checks that were performed more
+# recently that the timeframe specified by this value) can immensely
+# improve performance in regards to the host check logic.
+# Too high of a value for this option may result in inaccurate host
+# states being used by Nagios, while a lower value may result in a
+# performance hit for host checks. Use a value of 0 to disable host
+# check caching.
+
+cached_host_check_horizon=15
+
+
+
+# CACHED SERVICE CHECK HORIZON
+# This option determines the maximum amount of time (in seconds)
+# that the state of a previous service check is considered current.
+# Cached service states (from service checks that were performed more
+# recently that the timeframe specified by this value) can immensely
+# improve performance in regards to predictive dependency checks.
+# Use a value of 0 to disable service check caching.
+
+cached_service_check_horizon=15
+
+
+
+# ENABLE PREDICTIVE HOST DEPENDENCY CHECKS
+# This option determines whether or not Nagios will attempt to execute
+# checks of hosts when it predicts that future dependency logic test
+# may be needed. These predictive checks can help ensure that your
+# host dependency logic works well.
+# Values:
+# 0 = Disable predictive checks
+# 1 = Enable predictive checks (default)
+
+enable_predictive_host_dependency_checks=1
+
+
+
+# ENABLE PREDICTIVE SERVICE DEPENDENCY CHECKS
+# This option determines whether or not Nagios will attempt to execute
+# checks of service when it predicts that future dependency logic test
+# may be needed. These predictive checks can help ensure that your
+# service dependency logic works well.
+# Values:
+# 0 = Disable predictive checks
+# 1 = Enable predictive checks (default)
+
+enable_predictive_service_dependency_checks=1
+
+
+
+# SOFT STATE DEPENDENCIES
+# This option determines whether or not Nagios will use soft state
+# information when checking host and service dependencies. Normally
+# Nagios will only use the latest hard host or service state when
+# checking dependencies. If you want it to use the latest state (regardless
+# of whether its a soft or hard state type), enable this option.
+# Values:
+# 0 = Don't use soft state dependencies (default)
+# 1 = Use soft state dependencies
+
+soft_state_dependencies=0
+
+
+
+# TIME CHANGE ADJUSTMENT THRESHOLDS
+# These options determine when Nagios will react to detected changes
+# in system time (either forward or backwards).
+
+#time_change_threshold=900
+
+
+
+# AUTO-RESCHEDULING OPTION
+# This option determines whether or not Nagios will attempt to
+# automatically reschedule active host and service checks to
+# "smooth" them out over time. This can help balance the load on
+# the monitoring server.
+# WARNING: THIS IS AN EXPERIMENTAL FEATURE - IT CAN DEGRADE
+# PERFORMANCE, RATHER THAN INCREASE IT, IF USED IMPROPERLY
+
+auto_reschedule_checks=0
+
+
+
+# AUTO-RESCHEDULING INTERVAL
+# This option determines how often (in seconds) Nagios will
+# attempt to automatically reschedule checks. This option only
+# has an effect if the auto_reschedule_checks option is enabled.
+# Default is 30 seconds.
+# WARNING: THIS IS AN EXPERIMENTAL FEATURE - IT CAN DEGRADE
+# PERFORMANCE, RATHER THAN INCREASE IT, IF USED IMPROPERLY
+
+auto_rescheduling_interval=30
+
+
+
+# AUTO-RESCHEDULING WINDOW
+# This option determines the "window" of time (in seconds) that
+# Nagios will look at when automatically rescheduling checks.
+# Only host and service checks that occur in the next X seconds
+# (determined by this variable) will be rescheduled. This option
+# only has an effect if the auto_reschedule_checks option is
+# enabled. Default is 180 seconds (3 minutes).
+# WARNING: THIS IS AN EXPERIMENTAL FEATURE - IT CAN DEGRADE
+# PERFORMANCE, RATHER THAN INCREASE IT, IF USED IMPROPERLY
+
+auto_rescheduling_window=180
+
+
+
+# SLEEP TIME
+# This is the number of seconds to sleep between checking for system
+# events and service checks that need to be run.
+
+sleep_time=0.25
+
+
+
+# TIMEOUT VALUES
+# These options control how much time Nagios will allow various
+# types of commands to execute before killing them off. Options
+# are available for controlling maximum time allotted for
+# service checks, host checks, event handlers, notifications, the
+# ocsp command, and performance data commands. All values are in
+# seconds.
+
+service_check_timeout=60
+host_check_timeout=30
+event_handler_timeout=30
+notification_timeout=30
+ocsp_timeout=5
+perfdata_timeout=5
+
+
+
+# RETAIN STATE INFORMATION
+# This setting determines whether or not Nagios will save state
+# information for services and hosts before it shuts down. Upon
+# startup Nagios will reload all saved service and host state
+# information before starting to monitor. This is useful for
+# maintaining long-term data on state statistics, etc, but will
+# slow Nagios down a bit when it (re)starts. Since its only
+# a one-time penalty, I think its well worth the additional
+# startup delay.
+
+retain_state_information=1
+
+
+
+# STATE RETENTION FILE
+# This is the file that Nagios should use to store host and
+# service state information before it shuts down. The state
+# information in this file is also read immediately prior to
+# starting to monitor the network when Nagios is restarted.
+# This file is used only if the preserve_state_information
+# variable is set to 1.
+
+state_retention_file=/var/lib/nagios3/retention.dat
+
+
+
+# RETENTION DATA UPDATE INTERVAL
+# This setting determines how often (in minutes) that Nagios
+# will automatically save retention data during normal operation.
+# If you set this value to 0, Nagios will not save retention
+# data at regular interval, but it will still save retention
+# data before shutting down or restarting. If you have disabled
+# state retention, this option has no effect.
+
+retention_update_interval=60
+
+
+
+# USE RETAINED PROGRAM STATE
+# This setting determines whether or not Nagios will set
+# program status variables based on the values saved in the
+# retention file. If you want to use retained program status
+# information, set this value to 1. If not, set this value
+# to 0.
+
+use_retained_program_state=1
+
+
+
+# USE RETAINED SCHEDULING INFO
+# This setting determines whether or not Nagios will retain
+# the scheduling info (next check time) for hosts and services
+# based on the values saved in the retention file. If you
+# If you want to use retained scheduling info, set this
+# value to 1. If not, set this value to 0.
+
+use_retained_scheduling_info=1
+
+
+
+# RETAINED ATTRIBUTE MASKS (ADVANCED FEATURE)
+# The following variables are used to specify specific host and
+# service attributes that should *not* be retained by Nagios during
+# program restarts.
+#
+# The values of the masks are bitwise ANDs of values specified
+# by the "MODATTR_" definitions found in include/common.h.
+# For example, if you do not want the current enabled/disabled state
+# of flap detection and event handlers for hosts to be retained, you
+# would use a value of 24 for the host attribute mask...
+# MODATTR_EVENT_HANDLER_ENABLED (8) + MODATTR_FLAP_DETECTION_ENABLED (16) = 24
+
+# This mask determines what host attributes are not retained
+retained_host_attribute_mask=0
+
+# This mask determines what service attributes are not retained
+retained_service_attribute_mask=0
+
+# These two masks determine what process attributes are not retained.
+# There are two masks, because some process attributes have host and service
+# options. For example, you can disable active host checks, but leave active
+# service checks enabled.
+retained_process_host_attribute_mask=0
+retained_process_service_attribute_mask=0
+
+# These two masks determine what contact attributes are not retained.
+# There are two masks, because some contact attributes have host and
+# service options. For example, you can disable host notifications for
+# a contact, but leave service notifications enabled for them.
+retained_contact_host_attribute_mask=0
+retained_contact_service_attribute_mask=0
+
+
+
+# INTERVAL LENGTH
+# This is the seconds per unit interval as used in the
+# host/contact/service configuration files. Setting this to 60 means
+# that each interval is one minute long (60 seconds). Other settings
+# have not been tested much, so your mileage is likely to vary...
+
+interval_length=60
+
+
+
+# AGGRESSIVE HOST CHECKING OPTION
+# If you don't want to turn on aggressive host checking features, set
+# this value to 0 (the default). Otherwise set this value to 1 to
+# enable the aggressive check option. Read the docs for more info
+# on what aggressive host check is or check out the source code in
+# base/checks.c
+
+use_aggressive_host_checking=0
+
+
+
+# SERVICE CHECK EXECUTION OPTION
+# This determines whether or not Nagios will actively execute
+# service checks when it initially starts. If this option is
+# disabled, checks are not actively made, but Nagios can still
+# receive and process passive check results that come in. Unless
+# you're implementing redundant hosts or have a special need for
+# disabling the execution of service checks, leave this enabled!
+# Values: 1 = enable checks, 0 = disable checks
+
+execute_service_checks=1
+
+
+
+# PASSIVE SERVICE CHECK ACCEPTANCE OPTION
+# This determines whether or not Nagios will accept passive
+# service checks results when it initially (re)starts.
+# Values: 1 = accept passive checks, 0 = reject passive checks
+
+accept_passive_service_checks=1
+
+
+
+# HOST CHECK EXECUTION OPTION
+# This determines whether or not Nagios will actively execute
+# host checks when it initially starts. If this option is
+# disabled, checks are not actively made, but Nagios can still
+# receive and process passive check results that come in. Unless
+# you're implementing redundant hosts or have a special need for
+# disabling the execution of host checks, leave this enabled!
+# Values: 1 = enable checks, 0 = disable checks
+
+execute_host_checks=1
+
+
+
+# PASSIVE HOST CHECK ACCEPTANCE OPTION
+# This determines whether or not Nagios will accept passive
+# host checks results when it initially (re)starts.
+# Values: 1 = accept passive checks, 0 = reject passive checks
+
+accept_passive_host_checks=1
+
+
+
+# NOTIFICATIONS OPTION
+# This determines whether or not Nagios will sent out any host or
+# service notifications when it is initially (re)started.
+# Values: 1 = enable notifications, 0 = disable notifications
+
+enable_notifications=1
+
+
+
+# EVENT HANDLER USE OPTION
+# This determines whether or not Nagios will run any host or
+# service event handlers when it is initially (re)started. Unless
+# you're implementing redundant hosts, leave this option enabled.
+# Values: 1 = enable event handlers, 0 = disable event handlers
+
+enable_event_handlers=1
+
+
+
+# PROCESS PERFORMANCE DATA OPTION
+# This determines whether or not Nagios will process performance
+# data returned from service and host checks. If this option is
+# enabled, host performance data will be processed using the
+# host_perfdata_command (defined below) and service performance
+# data will be processed using the service_perfdata_command (also
+# defined below). Read the HTML docs for more information on
+# performance data.
+# Values: 1 = process performance data, 0 = do not process performance data
+
+process_performance_data=0
+
+
+
+# HOST AND SERVICE PERFORMANCE DATA PROCESSING COMMANDS
+# These commands are run after every host and service check is
+# performed. These commands are executed only if the
+# enable_performance_data option (above) is set to 1. The command
+# argument is the short name of a command definition that you
+# define in your host configuration file. Read the HTML docs for
+# more information on performance data.
+
+#host_perfdata_command=process-host-perfdata
+#service_perfdata_command=process-service-perfdata
+
+
+
+# HOST AND SERVICE PERFORMANCE DATA FILES
+# These files are used to store host and service performance data.
+# Performance data is only written to these files if the
+# enable_performance_data option (above) is set to 1.
+
+#host_perfdata_file=/tmp/host-perfdata
+#service_perfdata_file=/tmp/service-perfdata
+
+
+
+# HOST AND SERVICE PERFORMANCE DATA FILE TEMPLATES
+# These options determine what data is written (and how) to the
+# performance data files. The templates may contain macros, special
+# characters (\t for tab, \r for carriage return, \n for newline)
+# and plain text. A newline is automatically added after each write
+# to the performance data file. Some examples of what you can do are
+# shown below.
+
+#host_perfdata_file_template=[HOSTPERFDATA]\t$TIMET$\t$HOSTNAME$\t$HOSTEXECUTIONTIME$\t$HOSTOUTPUT$\t$HOSTPERFDATA$
+#service_perfdata_file_template=[SERVICEPERFDATA]\t$TIMET$\t$HOSTNAME$\t$SERVICEDESC$\t$SERVICEEXECUTIONTIME$\t$SERVICELATENCY$\t$SERVICEOUTPUT$\t$SERVICEPERFDATA$
+
+
+
+# HOST AND SERVICE PERFORMANCE DATA FILE MODES
+# This option determines whether or not the host and service
+# performance data files are opened in write ("w") or append ("a")
+# mode. If you want to use named pipes, you should use the special
+# pipe ("p") mode which avoid blocking at startup, otherwise you will
+# likely want the defult append ("a") mode.
+
+#host_perfdata_file_mode=a
+#service_perfdata_file_mode=a
+
+
+
+# HOST AND SERVICE PERFORMANCE DATA FILE PROCESSING INTERVAL
+# These options determine how often (in seconds) the host and service
+# performance data files are processed using the commands defined
+# below. A value of 0 indicates the files should not be periodically
+# processed.
+
+#host_perfdata_file_processing_interval=0
+#service_perfdata_file_processing_interval=0
+
+
+
+# HOST AND SERVICE PERFORMANCE DATA FILE PROCESSING COMMANDS
+# These commands are used to periodically process the host and
+# service performance data files. The interval at which the
+# processing occurs is determined by the options above.
+
+#host_perfdata_file_processing_command=process-host-perfdata-file
+#service_perfdata_file_processing_command=process-service-perfdata-file
+
+
+
+# OBSESS OVER SERVICE CHECKS OPTION
+# This determines whether or not Nagios will obsess over service
+# checks and run the ocsp_command defined below. Unless you're
+# planning on implementing distributed monitoring, do not enable
+# this option. Read the HTML docs for more information on
+# implementing distributed monitoring.
+# Values: 1 = obsess over services, 0 = do not obsess (default)
+
+obsess_over_services=0
+
+
+
+# OBSESSIVE COMPULSIVE SERVICE PROCESSOR COMMAND
+# This is the command that is run for every service check that is
+# processed by Nagios. This command is executed only if the
+# obsess_over_services option (above) is set to 1. The command
+# argument is the short name of a command definition that you
+# define in your host configuration file. Read the HTML docs for
+# more information on implementing distributed monitoring.
+
+#ocsp_command=somecommand
+
+
+
+# OBSESS OVER HOST CHECKS OPTION
+# This determines whether or not Nagios will obsess over host
+# checks and run the ochp_command defined below. Unless you're
+# planning on implementing distributed monitoring, do not enable
+# this option. Read the HTML docs for more information on
+# implementing distributed monitoring.
+# Values: 1 = obsess over hosts, 0 = do not obsess (default)
+
+obsess_over_hosts=0
+
+
+
+# OBSESSIVE COMPULSIVE HOST PROCESSOR COMMAND
+# This is the command that is run for every host check that is
+# processed by Nagios. This command is executed only if the
+# obsess_over_hosts option (above) is set to 1. The command
+# argument is the short name of a command definition that you
+# define in your host configuration file. Read the HTML docs for
+# more information on implementing distributed monitoring.
+
+#ochp_command=somecommand
+
+
+
+# TRANSLATE PASSIVE HOST CHECKS OPTION
+# This determines whether or not Nagios will translate
+# DOWN/UNREACHABLE passive host check results into their proper
+# state for this instance of Nagios. This option is useful
+# if you have distributed or failover monitoring setup. In
+# these cases your other Nagios servers probably have a different
+# "view" of the network, with regards to the parent/child relationship
+# of hosts. If a distributed monitoring server thinks a host
+# is DOWN, it may actually be UNREACHABLE from the point of
+# this Nagios instance. Enabling this option will tell Nagios
+# to translate any DOWN or UNREACHABLE host states it receives
+# passively into the correct state from the view of this server.
+# Values: 1 = perform translation, 0 = do not translate (default)
+
+translate_passive_host_checks=0
+
+
+
+# PASSIVE HOST CHECKS ARE SOFT OPTION
+# This determines whether or not Nagios will treat passive host
+# checks as being HARD or SOFT. By default, a passive host check
+# result will put a host into a HARD state type. This can be changed
+# by enabling this option.
+# Values: 0 = passive checks are HARD, 1 = passive checks are SOFT
+
+passive_host_checks_are_soft=0
+
+
+
+# ORPHANED HOST/SERVICE CHECK OPTIONS
+# These options determine whether or not Nagios will periodically
+# check for orphaned host service checks. Since service checks are
+# not rescheduled until the results of their previous execution
+# instance are processed, there exists a possibility that some
+# checks may never get rescheduled. A similar situation exists for
+# host checks, although the exact scheduling details differ a bit
+# from service checks. Orphaned checks seem to be a rare
+# problem and should not happen under normal circumstances.
+# If you have problems with service checks never getting
+# rescheduled, make sure you have orphaned service checks enabled.
+# Values: 1 = enable checks, 0 = disable checks
+
+check_for_orphaned_services=1
+check_for_orphaned_hosts=1
+
+
+
+# SERVICE FRESHNESS CHECK OPTION
+# This option determines whether or not Nagios will periodically
+# check the "freshness" of service results. Enabling this option
+# is useful for ensuring passive checks are received in a timely
+# manner.
+# Values: 1 = enabled freshness checking, 0 = disable freshness checking
+
+check_service_freshness=1
+
+
+
+# SERVICE FRESHNESS CHECK INTERVAL
+# This setting determines how often (in seconds) Nagios will
+# check the "freshness" of service check results. If you have
+# disabled service freshness checking, this option has no effect.
+
+service_freshness_check_interval=60
+
+
+
+# HOST FRESHNESS CHECK OPTION
+# This option determines whether or not Nagios will periodically
+# check the "freshness" of host results. Enabling this option
+# is useful for ensuring passive checks are received in a timely
+# manner.
+# Values: 1 = enabled freshness checking, 0 = disable freshness checking
+
+check_host_freshness=0
+
+
+
+# HOST FRESHNESS CHECK INTERVAL
+# This setting determines how often (in seconds) Nagios will
+# check the "freshness" of host check results. If you have
+# disabled host freshness checking, this option has no effect.
+
+host_freshness_check_interval=60
+
+
+
+
+# ADDITIONAL FRESHNESS THRESHOLD LATENCY
+# This setting determines the number of seconds that Nagios
+# will add to any host and service freshness thresholds that
+# it calculates (those not explicitly specified by the user).
+
+additional_freshness_latency=15
+
+
+
+
+# FLAP DETECTION OPTION
+# This option determines whether or not Nagios will try
+# and detect hosts and services that are "flapping".
+# Flapping occurs when a host or service changes between
+# states too frequently. When Nagios detects that a
+# host or service is flapping, it will temporarily suppress
+# notifications for that host/service until it stops
+# flapping. Flap detection is very experimental, so read
+# the HTML documentation before enabling this feature!
+# Values: 1 = enable flap detection
+# 0 = disable flap detection (default)
+
+enable_flap_detection=1
+
+
+
+# FLAP DETECTION THRESHOLDS FOR HOSTS AND SERVICES
+# Read the HTML documentation on flap detection for
+# an explanation of what this option does. This option
+# has no effect if flap detection is disabled.
+
+low_service_flap_threshold=5.0
+high_service_flap_threshold=20.0
+low_host_flap_threshold=5.0
+high_host_flap_threshold=20.0
+
+
+
+# DATE FORMAT OPTION
+# This option determines how short dates are displayed. Valid options
+# include:
+# us (MM-DD-YYYY HH:MM:SS)
+# euro (DD-MM-YYYY HH:MM:SS)
+# iso8601 (YYYY-MM-DD HH:MM:SS)
+# strict-iso8601 (YYYY-MM-DDTHH:MM:SS)
+#
+
+date_format=iso8601
+
+
+
+
+# TIMEZONE OFFSET
+# This option is used to override the default timezone that this
+# instance of Nagios runs in. If not specified, Nagios will use
+# the system configured timezone.
+#
+# NOTE: In order to display the correct timezone in the CGIs, you
+# will also need to alter the Apache directives for the CGI path
+# to include your timezone. Example:
+#
+# <Directory "/usr/local/nagios/sbin/">
+# SetEnv TZ "Australia/Brisbane"
+# ...
+# </Directory>
+
+#use_timezone=US/Mountain
+#use_timezone=Australia/Brisbane
+
+
+
+
+# P1.PL FILE LOCATION
+# This value determines where the p1.pl perl script (used by the
+# embedded Perl interpreter) is located. If you didn't compile
+# Nagios with embedded Perl support, this option has no effect.
+
+p1_file=/usr/lib/nagios3/p1.pl
+
+
+
+# EMBEDDED PERL INTERPRETER OPTION
+# This option determines whether or not the embedded Perl interpreter
+# will be enabled during runtime. This option has no effect if Nagios
+# has not been compiled with support for embedded Perl.
+# Values: 0 = disable interpreter, 1 = enable interpreter
+
+enable_embedded_perl=1
+
+
+
+# EMBEDDED PERL USAGE OPTION
+# This option determines whether or not Nagios will process Perl plugins
+# and scripts with the embedded Perl interpreter if the plugins/scripts
+# do not explicitly indicate whether or not it is okay to do so. Read
+# the HTML documentation on the embedded Perl interpreter for more
+# information on how this option works.
+
+use_embedded_perl_implicitly=1
+
+
+
+# ILLEGAL OBJECT NAME CHARACTERS
+# This option allows you to specify illegal characters that cannot
+# be used in host names, service descriptions, or names of other
+# object types.
+
+illegal_object_name_chars=`~!$%^&*|'"<>?,()=
+
+
+
+# ILLEGAL MACRO OUTPUT CHARACTERS
+# This option allows you to specify illegal characters that are
+# stripped from macros before being used in notifications, event
+# handlers, etc. This DOES NOT affect macros used in service or
+# host check commands.
+# The following macros are stripped of the characters you specify:
+# $HOSTOUTPUT$
+# $HOSTPERFDATA$
+# $HOSTACKAUTHOR$
+# $HOSTACKCOMMENT$
+# $SERVICEOUTPUT$
+# $SERVICEPERFDATA$
+# $SERVICEACKAUTHOR$
+# $SERVICEACKCOMMENT$
+
+illegal_macro_output_chars=`~$&|'"<>
+
+
+
+# REGULAR EXPRESSION MATCHING
+# This option controls whether or not regular expression matching
+# takes place in the object config files. Regular expression
+# matching is used to match host, hostgroup, service, and service
+# group names/descriptions in some fields of various object types.
+# Values: 1 = enable regexp matching, 0 = disable regexp matching
+
+use_regexp_matching=0
+
+
+
+# "TRUE" REGULAR EXPRESSION MATCHING
+# This option controls whether or not "true" regular expression
+# matching takes place in the object config files. This option
+# only has an effect if regular expression matching is enabled
+# (see above). If this option is DISABLED, regular expression
+# matching only occurs if a string contains wildcard characters
+# (* and ?). If the option is ENABLED, regexp matching occurs
+# all the time (which can be annoying).
+# Values: 1 = enable true matching, 0 = disable true matching
+
+use_true_regexp_matching=0
+
+
+
+# ADMINISTRATOR EMAIL/PAGER ADDRESSES
+# The email and pager address of a global administrator (likely you).
+# Nagios never uses these values itself, but you can access them by
+# using the $ADMINEMAIL$ and $ADMINPAGER$ macros in your notification
+# commands.
+
+admin_email=root@localhost
+admin_pager=pageroot@localhost
+
+
+
+# DAEMON CORE DUMP OPTION
+# This option determines whether or not Nagios is allowed to create
+# a core dump when it runs as a daemon. Note that it is generally
+# considered bad form to allow this, but it may be useful for
+# debugging purposes. Enabling this option doesn't guarantee that
+# a core file will be produced, but that's just life...
+# Values: 1 - Allow core dumps
+# 0 - Do not allow core dumps (default)
+
+daemon_dumps_core=0
+
+
+
+# LARGE INSTALLATION TWEAKS OPTION
+# This option determines whether or not Nagios will take some shortcuts
+# which can save on memory and CPU usage in large Nagios installations.
+# Read the documentation for more information on the benefits/tradeoffs
+# of enabling this option.
+# Values: 1 - Enabled tweaks
+# 0 - Disable tweaks (default)
+
+use_large_installation_tweaks=0
+
+
+
+# ENABLE ENVIRONMENT MACROS
+# This option determines whether or not Nagios will make all standard
+# macros available as environment variables when host/service checks
+# and system commands (event handlers, notifications, etc.) are
+# executed. Enabling this option can cause performance issues in
+# large installations, as it will consume a bit more memory and (more
+# importantly) consume more CPU.
+# Values: 1 - Enable environment variable macros (default)
+# 0 - Disable environment variable macros
+
+enable_environment_macros=1
+
+
+
+# CHILD PROCESS MEMORY OPTION
+# This option determines whether or not Nagios will free memory in
+# child processes (processed used to execute system commands and host/
+# service checks). If you specify a value here, it will override
+# program defaults.
+# Value: 1 - Free memory in child processes
+# 0 - Do not free memory in child processes
+
+#free_child_process_memory=1
+
+
+
+# CHILD PROCESS FORKING BEHAVIOR
+# This option determines how Nagios will fork child processes
+# (used to execute system commands and host/service checks). Normally
+# child processes are fork()ed twice, which provides a very high level
+# of isolation from problems. Fork()ing once is probably enough and will
+# save a great deal on CPU usage (in large installs), so you might
+# want to consider using this. If you specify a value here, it will
+# program defaults.
+# Value: 1 - Child processes fork() twice
+# 0 - Child processes fork() just once
+
+#child_processes_fork_twice=1
+
+
+
+# DEBUG LEVEL
+# This option determines how much (if any) debugging information will
+# be written to the debug file. OR values together to log multiple
+# types of information.
+# Values:
+# -1 = Everything
+# 0 = Nothing
+# 1 = Functions
+# 2 = Configuration
+# 4 = Process information
+# 8 = Scheduled events
+# 16 = Host/service checks
+# 32 = Notifications
+# 64 = Event broker
+# 128 = External commands
+# 256 = Commands
+# 512 = Scheduled downtime
+# 1024 = Comments
+# 2048 = Macros
+
+debug_level=0
+
+
+
+# DEBUG VERBOSITY
+# This option determines how verbose the debug log out will be.
+# Values: 0 = Brief output
+# 1 = More detailed
+# 2 = Very detailed
+
+debug_verbosity=1
+
+
+
+# DEBUG FILE
+# This option determines where Nagios should write debugging information.
+
+debug_file=/var/lib/nagios3/nagios.debug
+
+
+
+# MAX DEBUG FILE SIZE
+# This option determines the maximum size (in bytes) of the debug file. If
+# the file grows larger than this size, it will be renamed with a .old
+# extension. If a file already exists with a .old extension it will
+# automatically be deleted. This helps ensure your disk space usage doesn't
+# get out of control when debugging Nagios.
+
+max_debug_file_size=1000000
+
+
diff --git a/puppet/modules/nagios/files/configs/Debian/private/resource.cfg.amd64 b/puppet/modules/nagios/files/configs/Debian/private/resource.cfg.amd64
new file mode 100644
index 00000000..3ed732bb
--- /dev/null
+++ b/puppet/modules/nagios/files/configs/Debian/private/resource.cfg.amd64
@@ -0,0 +1,31 @@
+###########################################################################
+#
+# RESOURCE.CFG - Resource File for Nagios
+#
+# You can define $USERx$ macros in this file, which can in turn be used
+# in command definitions in your host config file(s). $USERx$ macros are
+# useful for storing sensitive information such as usernames, passwords,
+# etc. They are also handy for specifying the path to plugins and
+# event handlers - if you decide to move the plugins or event handlers to
+# a different directory in the future, you can just update one or two
+# $USERx$ macros, instead of modifying a lot of command definitions.
+#
+# The CGIs will not attempt to read the contents of resource files, so
+# you can set restrictive permissions (600 or 660) on them.
+#
+# Nagios supports up to 32 $USERx$ macros ($USER1$ through $USER32$)
+#
+# Resource files may also be used to store configuration directives for
+# external data sources like MySQL...
+#
+###########################################################################
+
+# Sets $USER1$ to be the path to the plugins
+$USER1$=/usr/lib/nagios/plugins
+
+# Sets $USER2$ to be the path to event handlers
+#$USER2$=/usr/lib/nagios/plugins/eventhandlers
+
+# Store some usernames and passwords (hidden from the CGIs)
+#$USER3$=someuser
+#$USER4$=somepassword
diff --git a/puppet/modules/nagios/files/configs/Debian/private/resource.cfg.i386 b/puppet/modules/nagios/files/configs/Debian/private/resource.cfg.i386
new file mode 100644
index 00000000..3ed732bb
--- /dev/null
+++ b/puppet/modules/nagios/files/configs/Debian/private/resource.cfg.i386
@@ -0,0 +1,31 @@
+###########################################################################
+#
+# RESOURCE.CFG - Resource File for Nagios
+#
+# You can define $USERx$ macros in this file, which can in turn be used
+# in command definitions in your host config file(s). $USERx$ macros are
+# useful for storing sensitive information such as usernames, passwords,
+# etc. They are also handy for specifying the path to plugins and
+# event handlers - if you decide to move the plugins or event handlers to
+# a different directory in the future, you can just update one or two
+# $USERx$ macros, instead of modifying a lot of command definitions.
+#
+# The CGIs will not attempt to read the contents of resource files, so
+# you can set restrictive permissions (600 or 660) on them.
+#
+# Nagios supports up to 32 $USERx$ macros ($USER1$ through $USER32$)
+#
+# Resource files may also be used to store configuration directives for
+# external data sources like MySQL...
+#
+###########################################################################
+
+# Sets $USER1$ to be the path to the plugins
+$USER1$=/usr/lib/nagios/plugins
+
+# Sets $USER2$ to be the path to event handlers
+#$USER2$=/usr/lib/nagios/plugins/eventhandlers
+
+# Store some usernames and passwords (hidden from the CGIs)
+#$USER3$=someuser
+#$USER4$=somepassword
diff --git a/puppet/modules/nagios/files/configs/Debian/private/resource.cfg.x86_64 b/puppet/modules/nagios/files/configs/Debian/private/resource.cfg.x86_64
new file mode 100644
index 00000000..3ed732bb
--- /dev/null
+++ b/puppet/modules/nagios/files/configs/Debian/private/resource.cfg.x86_64
@@ -0,0 +1,31 @@
+###########################################################################
+#
+# RESOURCE.CFG - Resource File for Nagios
+#
+# You can define $USERx$ macros in this file, which can in turn be used
+# in command definitions in your host config file(s). $USERx$ macros are
+# useful for storing sensitive information such as usernames, passwords,
+# etc. They are also handy for specifying the path to plugins and
+# event handlers - if you decide to move the plugins or event handlers to
+# a different directory in the future, you can just update one or two
+# $USERx$ macros, instead of modifying a lot of command definitions.
+#
+# The CGIs will not attempt to read the contents of resource files, so
+# you can set restrictive permissions (600 or 660) on them.
+#
+# Nagios supports up to 32 $USERx$ macros ($USER1$ through $USER32$)
+#
+# Resource files may also be used to store configuration directives for
+# external data sources like MySQL...
+#
+###########################################################################
+
+# Sets $USER1$ to be the path to the plugins
+$USER1$=/usr/lib/nagios/plugins
+
+# Sets $USER2$ to be the path to event handlers
+#$USER2$=/usr/lib/nagios/plugins/eventhandlers
+
+# Store some usernames and passwords (hidden from the CGIs)
+#$USER3$=someuser
+#$USER4$=somepassword
diff --git a/puppet/modules/nagios/files/configs/apache2.conf b/puppet/modules/nagios/files/configs/apache2.conf
new file mode 100644
index 00000000..f0f8b2f3
--- /dev/null
+++ b/puppet/modules/nagios/files/configs/apache2.conf
@@ -0,0 +1,67 @@
+# apache configuration for nagios 3.x
+# note to users of nagios 1.x and 2.x:
+# throughout this file are commented out sections which preserve
+# backwards compatibility with bookmarks/config for older nagios versios.
+# simply look for lines following "nagios 1.x:" and "nagios 2.x" comments.
+
+ScriptAlias /cgi-bin/nagios3 /usr/lib/cgi-bin/nagios3
+ScriptAlias /nagios3/cgi-bin /usr/lib/cgi-bin/nagios3
+# nagios 1.x:
+#ScriptAlias /cgi-bin/nagios /usr/lib/cgi-bin/nagios3
+#ScriptAlias /nagios/cgi-bin /usr/lib/cgi-bin/nagios3
+# nagios 2.x:
+#ScriptAlias /cgi-bin/nagios2 /usr/lib/cgi-bin/nagios3
+#ScriptAlias /nagios2/cgi-bin /usr/lib/cgi-bin/nagios3
+
+# Where the stylesheets (config files) reside
+Alias /nagios3/stylesheets /etc/nagios3/stylesheets
+# nagios 1.x:
+#Alias /nagios/stylesheets /etc/nagios3/stylesheets
+# nagios 2.x:
+#Alias /nagios2/stylesheets /etc/nagios3/stylesheets
+
+# Where the HTML pages live
+Alias /nagios3 /usr/share/nagios3/htdocs
+# nagios 2.x:
+#Alias /nagios2 /usr/share/nagios3/htdocs
+# nagios 1.x:
+#Alias /nagios /usr/share/nagios3/htdocs
+
+<DirectoryMatch (/usr/share/nagios3/htdocs|/usr/lib/cgi-bin/nagios3|/etc/nagios3/stylesheets)>
+ Options FollowSymLinks
+
+ DirectoryIndex index.php index.html
+
+ AllowOverride AuthConfig
+
+
+ <IfVersion < 2.3>
+ Order Allow,Deny
+ Allow From All
+ </IfVersion>
+
+ <IfVersion >= 2.3>
+ Require all denied
+ </IfVersion>
+
+ AuthName "Nagios Access"
+ AuthType Basic
+ AuthUserFile /etc/nagios3/htpasswd.users
+ # nagios 1.x:
+ #AuthUserFile /etc/nagios/htpasswd.users
+ require valid-user
+</DirectoryMatch>
+
+<Directory /usr/share/nagios3/htdocs>
+ Options +ExecCGI
+</Directory>
+
+# Enable this ScriptAlias if you want to enable the grouplist patch.
+# See http://apan.sourceforge.net/download.html for more info
+# It allows you to see a clickable list of all hostgroups in the
+# left pane of the Nagios web interface
+# XXX This is not tested for nagios 2.x use at your own peril
+#ScriptAlias /nagios3/side.html /usr/lib/cgi-bin/nagios3/grouplist.cgi
+# nagios 1.x:
+#ScriptAlias /nagios/side.html /usr/lib/cgi-bin/nagios3/grouplist.cgi
+
diff --git a/puppet/modules/nagios/files/configs/cgi.cfg b/puppet/modules/nagios/files/configs/cgi.cfg
new file mode 120000
index 00000000..db9667b3
--- /dev/null
+++ b/puppet/modules/nagios/files/configs/cgi.cfg
@@ -0,0 +1 @@
+Debian/cgi.cfg \ No newline at end of file
diff --git a/puppet/modules/nagios/files/configs/nagios.cfg b/puppet/modules/nagios/files/configs/nagios.cfg
new file mode 120000
index 00000000..1409b9e8
--- /dev/null
+++ b/puppet/modules/nagios/files/configs/nagios.cfg
@@ -0,0 +1 @@
+Debian/nagios.cfg \ No newline at end of file
diff --git a/puppet/modules/nagios/files/configs/nagios_templates.cfg b/puppet/modules/nagios/files/configs/nagios_templates.cfg
new file mode 100644
index 00000000..98596713
--- /dev/null
+++ b/puppet/modules/nagios/files/configs/nagios_templates.cfg
@@ -0,0 +1,49 @@
+define host{
+ name generic-host ; The name of this host template
+ notifications_enabled 1 ; Host notifications are enabled
+ event_handler_enabled 1 ; Host event handler is enabled
+ flap_detection_enabled 1 ; Flap detection is enabled
+ failure_prediction_enabled 1 ; Failure prediction is enabled
+ process_perf_data 1 ; Process performance data
+ retain_status_information 1 ; Retain status information across program restarts
+ retain_nonstatus_information 1 ; Retain non-status information across program restarts
+ check_command check-host-alive
+ max_check_attempts 10
+ notification_interval 0
+ notification_period 24x7
+ notification_options d,u,r
+ contact_groups admins
+ register 0 ; DONT REGISTER THIS DEFINITION - ITS NOT A REAL HOST, JUST A TEMPLATE!
+}
+
+define service{
+ name generic-service ; The 'name' of this service template
+ active_checks_enabled 1 ; Active service checks are enabled
+ passive_checks_enabled 1 ; Passive service checks are enabled/accepted
+ parallelize_check 1 ; Active service checks should be parallelized (disabling this can lead to major performance problems)
+ obsess_over_service 1 ; We should obsess over this service (if necessary)
+ check_freshness 0 ; Default is to NOT check service 'freshness'
+ notifications_enabled 1 ; Service notifications are enabled
+ event_handler_enabled 1 ; Service event handler is enabled
+ flap_detection_enabled 1 ; Flap detection is enabled
+ failure_prediction_enabled 1 ; Failure prediction is enabled
+ process_perf_data 1 ; Process performance data
+ retain_status_information 1 ; Retain status information across program restarts
+ retain_nonstatus_information 1 ; Retain non-status information across program restarts
+ notification_interval 0 ; Only send notifications on status change by default.
+ is_volatile 0
+ check_period 24x7
+ normal_check_interval 5
+ retry_check_interval 1
+ max_check_attempts 4
+ notification_period 24x7
+ notification_options w,u,c,r
+ contact_groups admins
+ register 0 ; DONT REGISTER THIS DEFINITION - ITS NOT A REAL SERVICE, JUST A TEMPLATE!
+}
+
+define service{
+ name passive-service
+ use generic-service
+ active_checks_enabled 0
+}
diff --git a/puppet/modules/nagios/files/htpasswd.users b/puppet/modules/nagios/files/htpasswd.users
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/puppet/modules/nagios/files/htpasswd.users
diff --git a/puppet/modules/nagios/files/irc_bot/riseup-nagios-client.pl b/puppet/modules/nagios/files/irc_bot/riseup-nagios-client.pl
new file mode 100644
index 00000000..2467058d
--- /dev/null
+++ b/puppet/modules/nagios/files/irc_bot/riseup-nagios-client.pl
@@ -0,0 +1,72 @@
+#!/usr/bin/perl -w
+
+# ##############################################################################
+# Infrabot-Client - a simple Infrabot client which sends it's whole command
+# line arguments to a local UNIX domain socket.
+# ##############################################################################
+
+use strict;
+use IO::Socket;
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# >> CONFIGURATION >>
+
+# Read a configuration file
+# The arg can be a relative or full path, or
+# it can be a file located somewhere in @INC.
+sub ReadCfg
+{
+ my $file = $_[0];
+
+ our $err;
+
+ { # Put config data into a separate namespace
+ package CFG;
+
+ # Process the contents of the config file
+ my $rc = do($file);
+
+ # Check for errors
+ if ($@) {
+ $::err = "ERROR: Failure compiling '$file' - $@";
+ } elsif (! defined($rc)) {
+ $::err = "ERROR: Failure reading '$file' - $!";
+ } elsif (! $rc) {
+ $::err = "ERROR: Failure processing '$file'";
+ }
+ }
+
+ return ($err);
+}
+
+# Get our configuration information
+if (my $err = ReadCfg('/etc/nagios_nsa.cfg')) {
+ print(STDERR $err, "\n");
+ exit(1);
+}
+
+# << CONFIGURATION <<
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+if (@ARGV == 0) {
+ print "Hey - specify a message, sucker!\n";
+ exit(1);
+}
+
+unless (-S $CFG::Nsa{'socket'}) {
+ die "Socket '$CFG::Nsa{'socket'}' doesn't exist or isn't a socket!\n";
+}
+
+unless (-r $CFG::Nsa{'socket'}) {
+ die "Socket '$CFG::Nsa{'socket'}' can't be read!\n";
+}
+
+my $sock = IO::Socket::UNIX->new (
+ Peer => $CFG::Nsa{'socket'},
+ Type => SOCK_DGRAM,
+ Timeout => 10
+) || die "Can't open socket '$CFG::Nsa{'socket'}'!\n";
+
+print $sock "@ARGV";
+close($sock);
diff --git a/puppet/modules/nagios/files/irc_bot/riseup-nagios-server.pl b/puppet/modules/nagios/files/irc_bot/riseup-nagios-server.pl
new file mode 100644
index 00000000..5d65e6dd
--- /dev/null
+++ b/puppet/modules/nagios/files/irc_bot/riseup-nagios-server.pl
@@ -0,0 +1,239 @@
+#!/usr/bin/perl -w
+
+# ##############################################################################
+# a simple IRC bot which dispatches messages received via local domain sockets
+# ##############################################################################
+
+
+######
+## THIS NEEDS TO BE PORTED TO THE NEW FRAMEWORKS!
+##
+## STICKY POINTS: the addfh() function doesn't exist in BasicBot or POE::Component::IRC
+##
+## people suggested we use Anyevent::IRC and POE::Kernel ->select_read and POE::Wheel namespace
+##
+## in the meantime, inspiration for extensions can be found here: http://svn.foswiki.org/trunk/WikiBot/mozbot.pl
+
+use strict;
+use File::Basename;
+
+BEGIN {
+ unshift @INC, dirname($0);
+}
+
+my $VERSION = '0.2';
+my $running = 1;
+
+# Read a configuration file
+# The arg can be a relative or full path, or
+# it can be a file located somewhere in @INC.
+sub ReadCfg
+{
+ my $file = $_[0];
+
+ our $err;
+
+ { # Put config data into a separate namespace
+ package CFG;
+
+ # Process the contents of the config file
+ my $rc = do($file);
+
+ # Check for errors
+ if ($@) {
+ $::err = "ERROR: Failure compiling '$file' - $@";
+ } elsif (! defined($rc)) {
+ $::err = "ERROR: Failure reading '$file' - $!";
+ } elsif (! $rc) {
+ $::err = "ERROR: Failure processing '$file'";
+ }
+ }
+
+ return ($err);
+}
+
+# Get our configuration information
+if (my $err = ReadCfg('/etc/nagios_nsa.cfg')) {
+ print(STDERR $err, "\n");
+ exit(1);
+}
+
+use POSIX qw(setsid);
+use IO::Socket;
+use Net::IRC;
+
+sub new {
+ my $self = {
+ socket => undef,
+ irc => undef,
+ conn => undef,
+ commandfile => undef,
+ };
+
+ return bless($self, __PACKAGE__);
+}
+
+sub daemonize {
+ my $self = shift;
+ my $pid;
+
+ chdir '/' or die "Can't chdir to /: $!";
+
+ open STDIN, '/dev/null' or die "Can't read /dev/null: $!";
+ open STDOUT, '>/dev/null' or die "Can't write to /dev/null: $!";
+
+ defined ($pid = fork) or die "Can't fork: $!";
+
+ if ($pid && $CFG::Nsa{'pidfile'}) { # write pid of child
+ open PID, ">$CFG::Nsa{'pidfile'}" or die "Can't open pid file: $!";
+ print PID $pid;
+ close PID;
+ }
+ exit if $pid;
+ setsid or die "Can't start a new session: $!";
+
+ #open STDERR, '>&STDOUT' or die "Can't dup stdout: $!";
+}
+
+sub run {
+ my $self = shift;
+
+ $self->{irc}->do_one_loop();
+}
+
+sub shutdown {
+ my $sig = shift;
+
+ print STDERR "Received SIG$sig, shutting down...\n";
+ $running = 0;
+}
+
+sub socket_has_data {
+ my $self = shift;
+
+ $self->{socket}->recv(my $data, 1024);
+ if ($CFG::Nsa{'usenotices'}) {
+ $self->{conn}->notice($CFG::Nsa{'channel'}, $data);
+ } else {
+ $self->{conn}->privmsg($CFG::Nsa{'channel'}, $data);
+ }
+}
+
+sub irc_on_connect {
+ my $self = shift;
+
+ print STDERR "Joining channel '$CFG::Nsa{'channel'}'...\n";
+ $self->join($CFG::Nsa{'channel'});
+}
+
+sub irc_on_msg {
+ my ($self, $event) = @_;
+ my $data = join(' ', $event->args);
+ #my $nick = quotemeta($nicks[$nick]);
+ my $to = $event->to;
+ #my $channel = &toToChannel($self, @$to);
+ #my $cmdChar = $commandChar{default}; # FIXME: should look up for CURRENT channel first!
+ #if ( exists $commandChar{$channel} ) { $cmdChar = $commandChar{$channel}; }
+ my $msg = parse_msg($event);
+ if (defined($msg)) {
+ #$self->privmsg($event->nick, "alright, sending this message to nagios, hope it figures it out: $msg");
+ } else {
+ $self->privmsg($event->nick, "can't parse $data, you want 'ack host service comment'\n");
+ }
+}
+
+sub irc_on_public {
+ my ($self, $event) = @_;
+ my $data = join(' ', $event->args);
+ my $to = $event->to;
+ if ($data =~ s/([^:]*):\s+//) {
+ if ($1 eq $CFG::Nsa{'nickname'}) {
+ my $msg = parse_msg($event);
+ if (defined($msg)) {
+ #$self->privmsg($event->to, "alright, sending this message to nagios, hope it figures it out: $msg");
+ } else {
+ $self->privmsg($event->to, "can't parse $data, you want 'ack host service comment'\n");
+ }
+ } else {
+ #print STDERR "ignoring message $data, not for me (me being $1)\n";
+ }
+ } else {
+ #print STDERR "ignoring message $data\n";
+ }
+}
+
+sub parse_msg {
+ my $event= shift;
+ my $data = join(' ', $event->args);
+ my $msg;
+ if ($data =~ m/([^:]*:)?\s*ack(?:knowledge)?\s+([a-zA-Z0-9\-\.]+)(?:\s+([-\w\.]+)(?:\s+([\w\s]+))?)?/) {
+ #print STDERR "writing to nagios scoket ". $CFG::Nsa{'commandfile'} . "\n";
+ open(my $cmdfile, ">", $CFG::Nsa{'commandfile'}) || die "Can't open Nagios commandfile: $CFG::Nsa{'commandfile'}!\n";
+ my $host = $2;
+ my ($service, $comment) = (undef, "no comment (from irc)");
+ if ($4) {
+ $service = $3;
+ $comment = $4;
+ } elsif ($3) {
+ $service = $3;
+ }
+ my $user = $event->nick;
+ $msg = '[' . time() . '] ';
+ if (defined($service)) {
+ $msg .= "ACKNOWLEDGE_SVC_PROBLEM;$host;$service;1;1;1;$user;$comment\n";
+ } else {
+ $msg .= "ACKNOWLEDGE_HOST_PROBLEM;$host;1;1;1;$user;$comment\n";
+ }
+ print {$cmdfile} $msg;
+ close($cmdfile);
+ }
+ return $msg;
+}
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+my $bot = &new;
+
+if (-e $CFG::Nsa{'socket'}) {
+ die "Socket '$CFG::Nsa{'socket'}' exists!\n";
+}
+
+$bot->{socket} = IO::Socket::UNIX->new (
+ Local => $CFG::Nsa{'socket'},
+ Type => SOCK_DGRAM,
+ Listen => 5
+) || die "Can't create socket '$CFG::Nsa{'socket'}'!\n";
+
+$SIG{INT} = $SIG{TERM} = \&shutdown;
+
+$bot->daemonize();
+$bot->{irc} = new Net::IRC;
+
+$bot->{conn} = $bot->{irc}->newconn (
+ Server => $CFG::Nsa{'server'},
+ Port => $CFG::Nsa{'port'},
+ Nick => $CFG::Nsa{'nickname'},
+ Username => $CFG::Nsa{'nickname'},
+ Password => $CFG::Nsa{'password'},
+ Ircname => $CFG::Nsa{'realname'} . " (NSA $VERSION)",
+) || die "Can't connect to server '$CFG::Nsa{'server'}'!\n";
+
+$bot->{conn}->add_global_handler(376, \&irc_on_connect);
+$bot->{conn}->add_global_handler('nomotd', \&irc_on_connect);
+$bot->{conn}->add_global_handler('msg', \&irc_on_msg);
+$bot->{conn}->add_global_handler('public', \&irc_on_public);
+#$bot->{conn}->add_global_handler('notice', \&irc_on_msg);
+$bot->{irc}->addfh($bot->{socket}, \&socket_has_data, 'r', $bot);
+
+while ($running) {
+ $bot->run();
+}
+
+close($bot->{socket});
+unlink($CFG::Nsa{'socket'});
+
+exit(0);
+
+1;
+
+__END__
diff --git a/puppet/modules/nagios/files/munin/nagios_hosts b/puppet/modules/nagios/files/munin/nagios_hosts
new file mode 100644
index 00000000..3fa00f1d
--- /dev/null
+++ b/puppet/modules/nagios/files/munin/nagios_hosts
@@ -0,0 +1,32 @@
+#!/bin/bash
+
+if [ -z "$nag_stats_binary" ]; then
+ nag_stats_binary=/usr/sbin/nagios3stats
+fi
+
+if [ "$1" = "config" ]; then
+ echo 'graph_title Nagios host stats'
+ echo 'graph_args --base 1000 -l 0'
+ echo 'graph_vlabel hosts'
+ echo 'graph_category nagios'
+ echo 'graph_info The number of hosts checked by nagios'
+ echo 'up.label up'
+ echo 'up.draw AREA'
+ echo 'up.info number of hosts UP'
+ echo 'down.label down'
+ echo 'down.draw STACK'
+ echo 'down.info number of hosts DOWN'
+ echo 'unr.label unr'
+ echo 'unr.draw STACK'
+ echo 'unr.info number of hosts UNREACHABLE'
+
+ exit 0
+fi
+
+echo -n 'up.value '
+$nag_stats_binary --mrtg --data NUMHSTUP
+echo -n 'down.value '
+$nag_stats_binary --mrtg --data NUMHSTDOWN
+echo -n 'unr.value '
+$nag_stats_binary --mrtg --data NUMHSTUNR
+
diff --git a/puppet/modules/nagios/files/munin/nagios_perf b/puppet/modules/nagios/files/munin/nagios_perf
new file mode 100644
index 00000000..609d62f4
--- /dev/null
+++ b/puppet/modules/nagios/files/munin/nagios_perf
@@ -0,0 +1,38 @@
+#!/bin/bash
+
+if [ -z "$nag_stats_binary" ]; then
+ nag_stats_binary=/usr/sbin/nagios3stats
+fi
+
+NAME=$(basename $0)
+
+TYPE=${NAME##nagios_perf_}
+
+[ "x$TYPE" = 'xhosts' ] && TYPE_ABBR=HST
+[ "x$TYPE" = 'xsvc' ] && TYPE_ABBR=SVC
+[ -z "$TYPE_ABBR" ] && echo "unknown type $TYPE" >&2 && exit 1
+
+if [ "$1" = "config" ]; then
+ echo "graph_title Nagios $TYPE performance stats"
+ echo "graph_args --base 1000"
+ echo "graph_vlabel seconds"
+ echo "graph_category nagios"
+ echo "graph_info Check performance statistics"
+
+ echo "act_lat_$TYPE_ABBR.label act lat"
+ echo "act_lat_$TYPE_ABBR.type GAUGE"
+ echo "act_lat_$TYPE_ABBR.draw AREA"
+ echo "act_lat_$TYPE_ABBR.cdef act_lat_$TYPE_ABBR,1000,/"
+ echo "act_lat_$TYPE_ABBR.info average latency of active checks over the last 5 minutes"
+
+ echo "act_ext_$TYPE_ABBR.label act ext"
+ echo "act_ext_$TYPE_ABBR.type GAUGE"
+ echo "act_ext_$TYPE_ABBR.draw LINE1"
+ echo "act_ext_$TYPE_ABBR.cdef act_ext_$TYPE_ABBR,1000,/"
+ echo "act_ext_$TYPE_ABBR.info average execution time of active checks over the last 5 minutes"
+
+ exit 0
+fi
+
+echo "act_lat_$TYPE_ABBR.value " $($nag_stats_binary --mrtg --data AVGACT${TYPE_ABBR}LAT)
+echo "act_ext_$TYPE_ABBR.value " $($nag_stats_binary --mrtg --data AVGACT${TYPE_ABBR}EXT)
diff --git a/puppet/modules/nagios/files/munin/nagios_svc b/puppet/modules/nagios/files/munin/nagios_svc
new file mode 100644
index 00000000..97c8bfc1
--- /dev/null
+++ b/puppet/modules/nagios/files/munin/nagios_svc
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+if [ -z "$nag_stats_binary" ]; then
+ nag_stats_binary=/usr/sbin/nagios3stats
+fi
+
+if [ "$1" = "config" ]; then
+ echo 'graph_title Nagios service stats'
+ echo 'graph_args --base 1000 -l 0'
+ echo 'graph_vlabel services'
+ echo 'graph_category nagios'
+ echo 'graph_info The number of services checked by nagios'
+ echo 'ok.label ok'
+ echo 'ok.draw AREA'
+ echo 'ok.info number of services OK'
+ echo 'warn.label warn'
+ echo 'warn.draw STACK'
+ echo 'warn.info number of services WARNING'
+ echo 'crit.label crit'
+ echo 'crit.draw STACK'
+ echo 'crit.info number of services CRITICAL'
+ echo 'unkn.label unkn'
+ echo 'unkn.draw STACK'
+ echo 'unkn.info number of services UNKNOWN'
+
+ exit 0
+fi
+
+echo -n 'ok.value '
+$nag_stats_binary --mrtg --data NUMSVCOK
+echo -n 'warn.value '
+$nag_stats_binary --mrtg --data NUMSVCWARN
+echo -n 'crit.value '
+$nag_stats_binary --mrtg --data NUMSVCCRIT
+echo -n 'unkn.value '
+$nag_stats_binary --mrtg --data NUMSVCUNKN
+
diff --git a/puppet/modules/nagios/files/nrpe/nrpe_commands.cfg b/puppet/modules/nagios/files/nrpe/nrpe_commands.cfg
new file mode 100644
index 00000000..b725a36a
--- /dev/null
+++ b/puppet/modules/nagios/files/nrpe/nrpe_commands.cfg
@@ -0,0 +1,5 @@
+# default plugins
+command[check_users]=/usr/lib/nagios/plugins/check_users $ARG1$
+command[check_disk]=/usr/lib/nagios/plugins/check_disk $ARG1$ -x "/dev" -x "/dev/shm" -x "/lib/init/rw"
+command[check_apt]=sudo /usr/lib/nagios/plugins/check_apt -u $ARG1$
+command[check_swap]=/usr/lib/nagios/plugins/check_swap $ARG1$
diff --git a/puppet/modules/nagios/files/nsca/nsca.cfg b/puppet/modules/nagios/files/nsca/nsca.cfg
new file mode 100644
index 00000000..0b019ea1
--- /dev/null
+++ b/puppet/modules/nagios/files/nsca/nsca.cfg
@@ -0,0 +1,197 @@
+# This file is managed by Puppet.
+#
+####################################################
+# Sample NSCA Daemon Config File
+# Written by: Ethan Galstad (nagios@nagios.org)
+#
+# Last Modified: 04-03-2006
+####################################################
+
+
+# PID FILE
+# The name of the file in which the NSCA daemon should write it's process ID
+# number. The file is only written if the NSCA daemon is started by the root
+# user as a single- or multi-process daemon.
+
+pid_file=/var/run/nsca.pid
+
+
+
+# PORT NUMBER
+# Port number we should wait for connections on.
+# This must be a non-priveledged port (i.e. > 1024).
+
+server_port=5667
+
+
+
+# SERVER ADDRESS
+# Address that NSCA has to bind to in case there are
+# more as one interface and we do not want NSCA to bind
+# (thus listen) on all interfaces.
+
+#server_address=192.168.1.1
+
+
+
+# NSCA USER
+# This determines the effective user that the NSCA daemon should run as.
+# You can either supply a username or a UID.
+#
+# NOTE: This option is ignored if NSCA is running under either inetd or xinetd
+
+nsca_user=nagios
+
+
+
+# NSCA GROUP
+# This determines the effective group that the NSCA daemon should run as.
+# You can either supply a group name or a GID.
+#
+# NOTE: This option is ignored if NSCA is running under either inetd or xinetd
+
+nsca_group=nogroup
+
+
+
+# NSCA CHROOT
+# If specified, determines a directory into which the nsca daemon
+# will perform a chroot(2) operation before dropping its privileges.
+# for the security conscious this can add a layer of protection in
+# the event that the nagios daemon is compromised.
+#
+# NOTE: if you specify this option, the command file will be opened
+# relative to this directory.
+
+#nsca_chroot=/var/run/nagios/rw
+
+
+
+# DEBUGGING OPTION
+# This option determines whether or not debugging
+# messages are logged to the syslog facility.
+# Values: 0 = debugging off, 1 = debugging on
+
+debug=0
+
+
+
+# COMMAND FILE
+# This is the location of the Nagios command file that the daemon
+# should write all service check results that it receives.
+# Note to debian users: nagios 1.x and nagios 2.x have
+# different default locations for this file. this is the
+# default location for nagios 1.x:
+#command_file=/var/run/nagios/nagios.cmd
+# and this is the default location for nagios2:
+#command_file=/var/lib/nagios2/rw/nagios.cmd
+# and this is the default location for nagios3:
+command_file=/var/lib/nagios3/rw/nagios.cmd
+
+# ALTERNATE DUMP FILE
+# This is used to specify an alternate file the daemon should
+# write service check results to in the event the command file
+# does not exist. It is important to note that the command file
+# is implemented as a named pipe and only exists when Nagios is
+# running. You may want to modify the startup script for Nagios
+# to dump the contents of this file into the command file after
+# it starts Nagios. Or you may simply choose to ignore any
+# check results received while Nagios was not running...
+
+alternate_dump_file=/var/run/nagios/nsca.dump
+
+
+# AGGREGATED WRITES OPTION
+# This option determines whether or not the nsca daemon will
+# aggregate writes to the external command file for client
+# connections that contain multiple check results. If you
+# are queueing service check results on remote hosts and
+# sending them to the nsca daemon in bulk, you will probably
+# want to enable bulk writes, as this will be a bit more
+# efficient.
+# Values: 0 = do not aggregate writes, 1 = aggregate writes
+
+aggregate_writes=0
+
+
+
+# APPEND TO FILE OPTION
+# This option determines whether or not the nsca daemon will
+# will open the external command file for writing or appending.
+# This option should almost *always* be set to 0!
+# Values: 0 = open file for writing, 1 = open file for appending
+
+append_to_file=0
+
+
+
+# MAX PACKET AGE OPTION
+# This option is used by the nsca daemon to determine when client
+# data is too old to be valid. Keeping this value as small as
+# possible is recommended, as it helps prevent the possibility of
+# "replay" attacks. This value needs to be at least as long as
+# the time it takes your clients to send their data to the server.
+# Values are in seconds. The max packet age cannot exceed 15
+# minutes (900 seconds). If this variable is set to zero (0), no
+# packets will be rejected based on their age.
+
+max_packet_age=30
+
+
+
+# DECRYPTION PASSWORD
+# This is the password/passphrase that should be used to descrypt the
+# incoming packets. Note that all clients must encrypt the packets
+# they send using the same password!
+# IMPORTANT: You don't want all the users on this system to be able
+# to read the password you specify here, so make sure to set
+# restrictive permissions on this config file!
+
+#password=
+
+
+
+# DECRYPTION METHOD
+# This option determines the method by which the nsca daemon will
+# decrypt the packets it receives from the clients. The decryption
+# method you choose will be a balance between security and performance,
+# as strong encryption methods consume more processor resources.
+# You should evaluate your security needs when choosing a decryption
+# method.
+#
+# Note: The decryption method you specify here must match the
+# encryption method the nsca clients use (as specified in
+# the send_nsca.cfg file)!!
+# Values:
+#
+# 0 = None (Do NOT use this option)
+# 1 = Simple XOR (No security, just obfuscation, but very fast)
+#
+# 2 = DES
+# 3 = 3DES (Triple DES)
+# 4 = CAST-128
+# 5 = CAST-256
+# 6 = xTEA
+# 7 = 3WAY
+# 8 = BLOWFISH
+# 9 = TWOFISH
+# 10 = LOKI97
+# 11 = RC2
+# 12 = ARCFOUR
+#
+# 14 = RIJNDAEL-128
+# 15 = RIJNDAEL-192
+# 16 = RIJNDAEL-256
+#
+# 19 = WAKE
+# 20 = SERPENT
+#
+# 22 = ENIGMA (Unix crypt)
+# 23 = GOST
+# 24 = SAFER64
+# 25 = SAFER128
+# 26 = SAFER+
+#
+
+decryption_method=1
+
diff --git a/puppet/modules/nagios/files/nsca/send_nsca.cfg b/puppet/modules/nagios/files/nsca/send_nsca.cfg
new file mode 100644
index 00000000..8127226b
--- /dev/null
+++ b/puppet/modules/nagios/files/nsca/send_nsca.cfg
@@ -0,0 +1,65 @@
+# This file is managed by Puppet.
+#
+####################################################
+# Sample NSCA Client Config File
+# Written by: Ethan Galstad (nagios@nagios.org)
+#
+# Last Modified: 02-21-2002
+####################################################
+
+
+# ENCRYPTION PASSWORD
+# This is the password/passphrase that should be used to encrypt the
+# outgoing packets. Note that the nsca daemon must use the same
+# password when decrypting the packet!
+# IMPORTANT: You don't want all the users on this system to be able
+# to read the password you specify here, so make sure to set
+# restrictive permissions on this config file!
+
+#password=
+
+
+
+# ENCRYPTION METHOD
+# This option determines the method by which the send_nsca client will
+# encrypt the packets it sends to the nsca daemon. The encryption
+# method you choose will be a balance between security and performance,
+# as strong encryption methods consume more processor resources.
+# You should evaluate your security needs when choosing an encryption
+# method.
+#
+# Note: The encryption method you specify here must match the
+# decryption method the nsca daemon uses (as specified in
+# the nsca.cfg file)!!
+# Values:
+# 0 = None (Do NOT use this option)
+# 1 = Simple XOR (No security, just obfuscation, but very fast)
+#
+# 2 = DES
+# 3 = 3DES (Triple DES)
+# 4 = CAST-128
+# 5 = CAST-256
+# 6 = xTEA
+# 7 = 3WAY
+# 8 = BLOWFISH
+# 9 = TWOFISH
+# 10 = LOKI97
+# 11 = RC2
+# 12 = ARCFOUR
+#
+# 14 = RIJNDAEL-128
+# 15 = RIJNDAEL-192
+# 16 = RIJNDAEL-256
+#
+# 19 = WAKE
+# 20 = SERPENT
+#
+# 22 = ENIGMA (Unix crypt)
+# 23 = GOST
+# 24 = SAFER64
+# 25 = SAFER128
+# 26 = SAFER+
+#
+
+encryption_method=1
+
diff --git a/puppet/modules/nagios/files/plugin_data/sks-keyservers.netCA.pem b/puppet/modules/nagios/files/plugin_data/sks-keyservers.netCA.pem
new file mode 100644
index 00000000..24a2ad2e
--- /dev/null
+++ b/puppet/modules/nagios/files/plugin_data/sks-keyservers.netCA.pem
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIFizCCA3OgAwIBAgIJAK9zyLTPn4CPMA0GCSqGSIb3DQEBBQUAMFwxCzAJBgNV
+BAYTAk5PMQ0wCwYDVQQIDARPc2xvMR4wHAYDVQQKDBVza3Mta2V5c2VydmVycy5u
+ZXQgQ0ExHjAcBgNVBAMMFXNrcy1rZXlzZXJ2ZXJzLm5ldCBDQTAeFw0xMjEwMDkw
+MDMzMzdaFw0yMjEwMDcwMDMzMzdaMFwxCzAJBgNVBAYTAk5PMQ0wCwYDVQQIDARP
+c2xvMR4wHAYDVQQKDBVza3Mta2V5c2VydmVycy5uZXQgQ0ExHjAcBgNVBAMMFXNr
+cy1rZXlzZXJ2ZXJzLm5ldCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
+ggIBANdsWy4PXWNUCkS3L//nrd0GqN3dVwoBGZ6w94Tw2jPDPifegwxQozFXkG6I
+6A4TK1CJLXPvfz0UP0aBYyPmTNadDinaB9T4jIwd4rnxl+59GiEmqkN3IfPsv5Jj
+MkKUmJnvOT0DEVlEaO1UZIwx5WpfprB3mR81/qm4XkAgmYrmgnLXd/pJDAMk7y1F
+45b5zWofiD5l677lplcIPRbFhpJ6kDTODXh/XEdtF71EAeaOdEGOvyGDmCO0GWqS
+FDkMMPTlieLA/0rgFTcz4xwUYj/cD5e0ZBuSkYsYFAU3hd1cGfBue0cPZaQH2HYx
+Qk4zXD8S3F4690fRhr+tki5gyG6JDR67aKp3BIGLqm7f45WkX1hYp+YXywmEziM4
+aSbGYhx8hoFGfq9UcfPEvp2aoc8u5sdqjDslhyUzM1v3m3ZGbhwEOnVjljY6JJLx
+MxagxnZZSAY424ZZ3t71E/Mn27dm2w+xFRuoy8JEjv1d+BT3eChM5KaNwrj0IO/y
+u8kFIgWYA1vZ/15qMT+tyJTfyrNVV/7Df7TNeWyNqjJ5rBmt0M6NpHG7CrUSkBy9
+p8JhimgjP5r0FlEkgg+lyD+V79H98gQfVgP3pbJICz0SpBQf2F/2tyS4rLm+49rP
+fcOajiXEuyhpcmzgusAj/1FjrtlynH1r9mnNaX4e+rLWzvU5AgMBAAGjUDBOMB0G
+A1UdDgQWBBTkwyoJFGfYTVISTpM8E+igjdq28zAfBgNVHSMEGDAWgBTkwyoJFGfY
+TVISTpM8E+igjdq28zAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQAR
+OXnYwu3g1ZjHyley3fZI5aLPsaE17cOImVTehC8DcIphm2HOMR/hYTTL+V0G4P+u
+gH+6xeRLKSHMHZTtSBIa6GDL03434y9CBuwGvAFCMU2GV8w92/Z7apkAhdLToZA/
+X/iWP2jeaVJhxgEcH8uPrnSlqoPBcKC9PrgUzQYfSZJkLmB+3jEa3HKruy1abJP5
+gAdQvwvcPpvYRnIzUc9fZODsVmlHVFBCl2dlu/iHh2h4GmL4Da2rRkUMlbVTdioB
+UYIvMycdOkpH5wJftzw7cpjsudGas0PARDXCFfGyKhwBRFY7Xp7lbjtU5Rz0Gc04
+lPrhDf0pFE98Aw4jJRpFeWMjpXUEaG1cq7D641RpgcMfPFvOHY47rvDTS7XJOaUT
+BwRjmDt896s6vMDcaG/uXJbQjuzmmx3W2Idyh3s5SI0GTHb0IwMKYb4eBUIpQOnB
+cE77VnCYqKvN1NVYAqhWjXbY7XasZvszCRcOG+W3FqNaHOK/n/0ueb0uijdLan+U
+f4p1bjbAox8eAOQS/8a3bzkJzdyBNUKGx1BIK2IBL9bn/HravSDOiNRSnZ/R3l9G
+ZauX0tu7IIDlRCILXSyeazu0aj/vdT3YFQXPcvt5Fkf5wiNTo53f72/jYEJd6qph
+WrpoKqrwGwTpRUCMhYIUt65hsTxCiJJ5nKe39h46sg==
+-----END CERTIFICATE-----
diff --git a/puppet/modules/nagios/files/plugins/check_dns2 b/puppet/modules/nagios/files/plugins/check_dns2
new file mode 100644
index 00000000..21956315
--- /dev/null
+++ b/puppet/modules/nagios/files/plugins/check_dns2
@@ -0,0 +1,102 @@
+#!/bin/bash
+# Written by Damien Gy
+# damien.gy+nagiosexchange(AT)gmail.com
+# 2007-09-28
+
+PROGNAME=`basename $0`
+REVISION=1.00
+TMP=/tmp/tmpdig
+DIG=/usr/bin/dig
+
+print_revision() {
+ echo $PROGNAME $REVISION
+}
+
+print_usage() {
+ echo "Usage:"
+ echo " $PROGNAME -c|--check <host> <type> <server>"
+ echo " $PROGNAME -h|--help"
+ echo " $PROGNAME -v|--version"
+}
+
+print_help() {
+ print_revision
+ echo ""
+ print_usage
+ echo "Where:"
+ echo " host the name of the resource record to be looked up"
+ echo " type indicates the query required (any, a, mx, etc.)"
+ echo " server the name or IP address of the name server to query"
+ echo ""
+ echo " -h|--help prints this help screen"
+ echo ""
+ echo " -v|--version prints version and license information"
+ echo ""
+ echo " Created by Damien Gy, questions or problems e-mail damien.gy+nagiosexchange(AT)gmail.com"
+ echo ""
+}
+
+check_dns() {
+
+ if [ $# -ne 3 ]
+ then
+ echo "Number of arguments incorrect"
+ exit 3
+ fi
+ if [ ! -e $DIG ]
+ then
+ echo "$DIG not found"
+ exit 3
+ fi
+ $DIG $1 $2 @$3 > $TMP
+
+ if ( grep "status" $TMP > /dev/null )
+ then
+ # DNS server answered
+ if ( grep "NOERROR" $TMP > /dev/null )
+ then
+ echo "DNS OK "`grep "time:" $TMP`
+ rm -f $TMP
+ exit 0
+ else
+ echo "WARNING "`grep "time:" $TMP`
+ rm -f $TMP
+ exit 1
+ fi
+
+ else
+ # no answer
+ echo "CRITICAL - Connection timed out"
+ rm -f $TMP
+ exit 2
+ fi
+}
+
+case "$1" in
+--help)
+ print_help
+ exit 0
+ ;;
+-h)
+ print_help
+ exit 0
+ ;;
+--version)
+ print_revision
+ exit 0
+ ;;
+-v)
+ print_revision
+ exit 0
+ ;;
+--check)
+ check_dns $2 $3 $4
+ ;;
+-c)
+ check_dns $2 $3 $4
+ ;;
+*)
+ print_usage
+ exit 3
+
+esac
diff --git a/puppet/modules/nagios/files/plugins/check_dnsbl b/puppet/modules/nagios/files/plugins/check_dnsbl
new file mode 100644
index 00000000..93cea375
--- /dev/null
+++ b/puppet/modules/nagios/files/plugins/check_dnsbl
@@ -0,0 +1,107 @@
+#!/bin/sh
+#
+# dnsbl-check-nagios.sh
+#
+# (c) 2009 Damon Tajeddini & heise Netze
+#
+STATE_OK=0
+STATE_WARNING=1
+STATE_CRITICAL=2
+STATE_UNKNOWN=3
+STATE_DEPENDENT=4
+
+FOUND_ADRESS=0
+
+DNSBLlist=`grep -v ^# <<!
+cbl.abuseat.org
+dnsbl.ahbl.org
+ircbl.ahbl.org
+virbl.dnsbl.bit.nl
+blackholes.five-ten-sg.com
+dnsbl.inps.de
+ix.dnsbl.manitu.net
+no-more-funn.moensted.dk
+combined.njabl.org
+dnsbl.njabl.org
+dnsbl.sorbs.net
+bl.spamcannibal.org
+bl.spamcop.net
+sbl.spamhaus.org
+xbl.spamhaus.org
+pbl.spamhaus.org
+dnsbl-1.uceprotect.net
+# dnsbl-2.uceprotect.net
+# dnsbl-3.uceprotect.net
+psbl.surriel.com
+l2.apews.org
+dnsrbl.swinog.ch
+db.wpbl.info
+!`
+
+# reverse IP address
+convertIP()
+{
+ set `IFS=".";echo $1`
+ echo $4.$3.$2.$1
+}
+
+usage()
+{
+ echo "Usage: $0 [-H] <host>] [-p]"
+ echo " -H check Host "
+ echo " -p print list of DNSBLs"
+ exit 3
+}
+
+# Checks the IP with list of DNSBL servers
+check()
+{
+ count=0;
+ for i in $DNSBLlist
+ do
+ count=$(($count + 1))
+ if nslookup $ip_arpa.$i | grep -q "127.0.0." ;
+ then
+ FOUND_ADRESS=$(($FOUND_ADRESS + 1))
+ echo "DNSBL-Alarm: $ip is listed on $i"
+ fi
+ done
+ if [ $FOUND_ADRESS -ge 1 ]
+ then
+ exit 1
+ fi
+ echo "OK - $ip not on $count DNSBLs"
+ exit 0
+}
+
+case $1 in
+ -H)
+ if [ -z "$2" ]
+ then
+ echo "ip address missing"
+ exit
+ fi
+ ip=$2
+ ip_arpa=`convertIP $ip`
+ check;;
+
+ -p)
+ for i in $DNSBLlist
+ do
+ echo $i
+ done
+ exit $STATE_WARNING
+ exit;;
+
+ --help)
+ usage
+ exit;;
+
+ *)
+ if [ -z "$1" ]
+ then
+ usage
+ fi
+ echo "unknown command: $1"
+ exit;;
+esac
diff --git a/puppet/modules/nagios/files/plugins/check_gpg b/puppet/modules/nagios/files/plugins/check_gpg
new file mode 100644
index 00000000..eb9fa51f
--- /dev/null
+++ b/puppet/modules/nagios/files/plugins/check_gpg
@@ -0,0 +1,115 @@
+#!/bin/bash
+#
+# Nagios plugin that checks whether a key ID has expired, or will expire within
+# a certain time.
+#
+# note: the plugin will issue a critical state if the required key has been
+# revoked.
+#
+# usage: check_gpg [-w <num_days>] [--gnupg-homedir <path>] <key_id>
+#
+# <key_id> is any PGP key ID that GnuPG accepts with "gpg --list-key <key_id>"
+#
+# The option -w parameter lets you specify the number of days within which key
+# expiry will trigger a warning. e.g. if <key_id> expires within <num_days>
+# days, make nagios issue a warning.
+#
+# num_days must be an integer value
+#
+# optionally, if the keyring directory you want GPG to use is not located in
+# the user's ~/.gnupg, you can specify the path to the keyring directory with
+# the --gnupg-homedir parameter.
+#
+# Thanks a bunch to Daniel Kahn Gillmor for providing example commands that
+# made up most of the core of this plugin.
+#
+# Copyleft Gabriel Filion
+#
+# This plugin is released under the GPL v3+ license. To get a copy of the
+# license text visit: https://www.gnu.org/licenses/gpl-3.0.txt
+#
+SECS_IN_DAY=86400
+
+function debug () {
+ if [ -n "$DEBUG" ]; then
+ echo "$1" >&2
+ fi
+}
+
+debug "got args: $*"
+
+now=$(date +%s)
+debug "current timestamp: $now"
+
+warning_threshold=
+homedir=
+homedir_path=~/.gnupg
+for arg in $*; do
+ case $arg in
+ "-w")
+ if [ -z "$2" ]; then
+ echo "UNKNOWN: argument -w got no value. integer needed"
+ exit 3
+ fi
+ if [ "`echo $2 | egrep ^[[:digit:]]+$`" = "" ]; then
+ echo "UNKNOWN: invalid value '$2' passed to -w. integer needed"
+ exit 3
+ fi
+ warning_threshold=$(( $now + ($2*$SECS_IN_DAY) ))
+ debug "setting warning_threshold to '$warning_threshold'"
+
+ shift 2
+ ;;
+ "--gnupg-homedir")
+ if [ -z "$2" ]; then
+ echo "UNKNOWN: argument --gnupg-homedir got no value. path needed"
+ exit 3
+ fi
+ if [ ! -d "$2" ]; then
+ echo "UNKNOWN: homedir '$2' does not exist or is not a directory"
+ exit 3
+ fi
+ homedir_path=$2
+ homedir="--homedir ${homedir_path}"
+ debug "setting homedir to '$homedir_path'"
+
+ shift 2
+ ;;
+ esac
+done
+
+if [ -z "$1" ]; then
+ echo "UNKNOWN: must provide a key ID"
+ exit 3
+fi
+key="$1"
+
+# GPG is too stupid to error out when asked to refresh a key that's not in the
+# local keyring so we need to perform another call to verify this first.
+output=$( { gpg $homedir --list-key "$key" >/dev/null && gpg $homedir --refresh --keyserver hkps://hkps.pool.sks-keyservers.net --keyserver-options ca-cert-file=$homedir_path/sks-keyservers.netCA.pem "$key" >/dev/null; } 2>&1 )
+if [ $? -ne 0 ]; then
+ echo "UNKNOWN: $output"
+ exit 3
+fi
+
+if [ "$(gpg $homedir --check-sig "$key" | grep "^rev!")" != "" ]; then
+ echo "CRITICAL: key '$key' has been revoked!"
+ exit 1
+fi
+
+for expiry in $(gpg $homedir --with-colons --fixed-list-mode --list-key "$key" 2>/dev/null | awk -F: '/^pub:/{ print $7 }');
+do
+ debug "expiry value: $expiry"
+
+ if [ "$now" -gt "$expiry" ] ; then
+ printf "CRITICAL: %s has expired on %s\n" "$key" "$(date -d "$expiry seconds")";
+ exit 1;
+ fi;
+ if [ -n "$warning_threshold" ] && [ "$warning_threshold" -gt "$expiry" ]; then
+ remaining=$(( ($expiry-$now) / $SECS_IN_DAY ))
+ printf "WARNING: %s expires in %s days\n" "$key" "$remaining";
+ exit 2;
+ fi
+done
+
+echo "OK: key '$key' has not expired."
diff --git a/puppet/modules/nagios/files/plugins/check_horde_login b/puppet/modules/nagios/files/plugins/check_horde_login
new file mode 100644
index 00000000..8c821e48
--- /dev/null
+++ b/puppet/modules/nagios/files/plugins/check_horde_login
@@ -0,0 +1,94 @@
+#!/bin/env python
+# vi:si:et:sw=4:sts=4:ts=4
+# -*- coding: UTF-8 -*-
+# -*- Mode: Python -*-
+#
+# Copyright (C) 2015 mh <mh@immerda.ch>
+
+# This file may be distributed and/or modified under the terms of
+# the GNU General Public License version 2 as published by
+# the Free Software Foundation.
+# This file is distributed without any warranty; without even the implied
+# warranty of merchantability or fitness for a particular purpose.
+#
+
+import sys, os, requests, getopt
+from time import time
+
+def usage():
+ print sys.argv[0] + " -u username "+ \
+ "-p password " + \
+ "-s server path" + \
+ "[-w warning_in_s] " + \
+ "[-c critical_in_s]"
+ sys.exit(1)
+
+def main():
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], "u:p:s:h:w:c")
+ except getopt.GetoptError:
+ usage()
+ return 3
+
+ user = url = password = None
+ warning = 5
+ critical = 10
+
+ for o, a in opts:
+ if o == "-u":
+ user = a
+ elif o == "-p":
+ password = a
+ elif o == "-w":
+ warning = a
+ elif o == "-c":
+ critical = a
+ elif o == "-s":
+ url = a + "/login.php"
+ elif o == '-h':
+ usage()
+
+ if user == None or password == None or url == None:
+ usage()
+
+ params = { 'horde_user': user,
+ 'horde_pass': password,
+ 'horde_select_view': 'auto',
+ 'anchor_string': '',
+ 'app': '',
+ 'login_post': 1,
+ 'new_lang': 'en_US',
+ 'url': '',
+ }
+
+
+ timestamp = time()
+ try:
+ r = requests.post(url, data=params, allow_redirects=False)
+ except Exception, e:
+ print "CRITICAL Horde Login Failed: %s" % e
+ sys.exit(2)
+
+ timestamp = time() - timestamp
+ if r.status_code == 302:
+ if timestamp < warning:
+ status = "OK"
+ exitcode = 0
+ if timestamp >= warning:
+ status = "WARNING"
+ exitcode = 1
+ if timestamp >= critical:
+ status = "CRITICAL"
+ exitcode = 2
+ else:
+ status = "ERROR"
+ exitcode = 2
+ # on a successfully login we are redirected to the mailbox
+ print '%s Horde Login | response_time=%.3fs;%.3f;%.3f' % (status, timestamp, warning, critical)
+ sys.exit(exitcode)
+
+
+if __name__ == "__main__":
+ sys.exit(main())
+
+
diff --git a/puppet/modules/nagios/files/plugins/check_imap_login b/puppet/modules/nagios/files/plugins/check_imap_login
new file mode 100644
index 00000000..d059822b
--- /dev/null
+++ b/puppet/modules/nagios/files/plugins/check_imap_login
@@ -0,0 +1,80 @@
+#!/usr/bin/python
+# -*- coding: UTF-8 -*-
+# -*- Mode: Python -*-
+#
+# Copyright (C) 2006 Bertera Pietro <pietro@bertera.it>
+# Response time monitoring with perfdata modification by Ivan Savcic <isavcic@gmail.com> and Milos Buncic, 2012.
+# From: https://github.com/isavcic/check_imap_login
+
+# This file may be distributed and/or modified under the terms of
+# the GNU General Public License version 2 as published by
+# the Free Software Foundation.
+# This file is distributed without any warranty; without even the implied
+# warranty of merchantability or fitness for a particular purpose.
+
+import sys, os, imaplib, getopt
+from time import time
+
+def usage():
+ print sys.argv[0] + " -u <user> -p <password> -H <host> [-s] -w <warning threshold (sec)> -c <critical threshold (sec)>\n -s is for using IMAPS"
+
+def main():
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], "u:p:sH:w:c:")
+ except getopt.GetoptError:
+ usage()
+ return 3
+
+ user = host = password = use_ssl = warning = critical = None
+
+ for o, a in opts:
+ if o == "-u":
+ user = a
+ elif o == "-p":
+ password = a
+ elif o == "-s":
+ use_ssl = True
+ elif o == "-H":
+ host = a
+ elif o == "-w":
+ warning = float(a)
+ elif o == "-c":
+ critical = float(a)
+
+ if user == None or password == None or host == None or warning == None or critical == None:
+ usage()
+ return 1
+
+ if use_ssl:
+ M = imaplib.IMAP4_SSL(host=host)
+ else:
+ M = imaplib.IMAP4(host)
+
+ timestamp = time()
+
+ try:
+ M.login(user, password)
+ except Exception, e:
+ print "CRITICAL IMAP Login Failed: %s" % e
+ return 2
+
+ M.logout()
+
+ timestamp = time() - timestamp
+
+ if timestamp < warning:
+ status = "OK"
+ exitcode = 0
+ if timestamp >= warning:
+ status = "WARNING"
+ exitcode = 1
+ if timestamp >= critical:
+ status = "CRITICAL"
+ exitcode = 2
+
+ print '%s IMAP Login | response_time=%.3fs;%.3f;%.3f' % (status, timestamp, warning, critical)
+
+ return exitcode
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/puppet/modules/nagios/files/plugins/check_jabber_login b/puppet/modules/nagios/files/plugins/check_jabber_login
new file mode 100644
index 00000000..dac0e1fe
--- /dev/null
+++ b/puppet/modules/nagios/files/plugins/check_jabber_login
@@ -0,0 +1,30 @@
+#!/usr/bin/env ruby
+require 'rubygems'
+require 'xmpp4r'
+
+
+def usage
+ puts "#{$0} jabberid password"
+ exit 3
+end
+
+usage unless ARGV.size == 2
+
+begin
+ my_client = Jabber::Client.new(ARGV[0])
+ my_client.connect
+ my_client.auth(ARGV[1])
+rescue Jabber::ClientAuthenticationFailure => detail
+ puts "CRITICAL: Login Error"
+ exit 2
+rescue Errno::ECONNREFUSED => detail
+ puts "CRITICAL: Connection refused"
+ exit 2
+rescue SocketError => detail
+ puts "CRITICAL: Socket Error"
+ exit 2
+#rescue
+# puts "CRITICAL: Unknown Error"
+# exit 2
+end
+puts "OK: Login for #{ARGV[0]} successfull"
diff --git a/puppet/modules/nagios/files/plugins/check_mysql_health b/puppet/modules/nagios/files/plugins/check_mysql_health
new file mode 100755
index 00000000..9292ae09
--- /dev/null
+++ b/puppet/modules/nagios/files/plugins/check_mysql_health
@@ -0,0 +1,3780 @@
+#! /usr/bin/perl -w
+# nagios: -epn
+
+my %ERRORS=( OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 );
+my %ERRORCODES=( 0 => 'OK', 1 => 'WARNING', 2 => 'CRITICAL', 3 => 'UNKNOWN' );
+package DBD::MySQL::Server::Instance::Innodb;
+
+use strict;
+
+our @ISA = qw(DBD::MySQL::Server::Instance);
+
+
+sub new {
+ my $class = shift;
+ my %params = @_;
+ my $self = {
+ handle => $params{handle},
+ internals => undef,
+ warningrange => $params{warningrange},
+ criticalrange => $params{criticalrange},
+ };
+ bless $self, $class;
+ $self->init(%params);
+ return $self;
+}
+
+sub init {
+ my $self = shift;
+ my %params = @_;
+ $self->init_nagios();
+ if ($params{mode} =~ /server::instance::innodb/) {
+ $self->{internals} =
+ DBD::MySQL::Server::Instance::Innodb::Internals->new(%params);
+ }
+}
+
+sub nagios {
+ my $self = shift;
+ my %params = @_;
+ if ($params{mode} =~ /server::instance::innodb/) {
+ $self->{internals}->nagios(%params);
+ $self->merge_nagios($self->{internals});
+ }
+}
+
+
+package DBD::MySQL::Server::Instance::Innodb::Internals;
+
+use strict;
+
+our @ISA = qw(DBD::MySQL::Server::Instance::Innodb);
+
+our $internals; # singleton, nur ein einziges mal instantiierbar
+
+sub new {
+ my $class = shift;
+ my %params = @_;
+ unless ($internals) {
+ $internals = {
+ handle => $params{handle},
+ bufferpool_hitrate => undef,
+ wait_free => undef,
+ log_waits => undef,
+ have_innodb => undef,
+ warningrange => $params{warningrange},
+ criticalrange => $params{criticalrange},
+ };
+ bless($internals, $class);
+ $internals->init(%params);
+ }
+ return($internals);
+}
+
+sub init {
+ my $self = shift;
+ my %params = @_;
+ my $dummy;
+ $self->debug("enter init");
+ $self->init_nagios();
+ ($dummy, $self->{have_innodb})
+ = $self->{handle}->fetchrow_array(q{
+ SHOW VARIABLES LIKE 'have_innodb'
+ });
+ if ($self->{have_innodb} eq "NO") {
+ $self->add_nagios_critical("the innodb engine has a problem (have_innodb=no)");
+ } elsif ($self->{have_innodb} eq "DISABLED") {
+ # add_nagios_ok later
+ } elsif ($params{mode} =~ /server::instance::innodb::bufferpool::hitrate/) {
+ ($dummy, $self->{bufferpool_reads})
+ = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Innodb_buffer_pool_reads'
+ });
+ ($dummy, $self->{bufferpool_read_requests})
+ = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Innodb_buffer_pool_read_requests'
+ });
+ if (! defined $self->{bufferpool_reads}) {
+ $self->add_nagios_critical("no innodb buffer pool info available");
+ } else {
+ $self->valdiff(\%params, qw(bufferpool_reads
+ bufferpool_read_requests));
+ $self->{bufferpool_hitrate_now} =
+ $self->{delta_bufferpool_read_requests} > 0 ?
+ 100 - (100 * $self->{delta_bufferpool_reads} /
+ $self->{delta_bufferpool_read_requests}) : 100;
+ $self->{bufferpool_hitrate} =
+ $self->{bufferpool_read_requests} > 0 ?
+ 100 - (100 * $self->{bufferpool_reads} /
+ $self->{bufferpool_read_requests}) : 100;
+ }
+ } elsif ($params{mode} =~ /server::instance::innodb::bufferpool::waitfree/) {
+ ($dummy, $self->{bufferpool_wait_free})
+ = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Innodb_buffer_pool_wait_free'
+ });
+ if (! defined $self->{bufferpool_wait_free}) {
+ $self->add_nagios_critical("no innodb buffer pool info available");
+ } else {
+ $self->valdiff(\%params, qw(bufferpool_wait_free));
+ $self->{bufferpool_wait_free_rate} =
+ $self->{delta_bufferpool_wait_free} / $self->{delta_timestamp};
+ }
+ } elsif ($params{mode} =~ /server::instance::innodb::logwaits/) {
+ ($dummy, $self->{log_waits})
+ = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Innodb_log_waits'
+ });
+ if (! defined $self->{log_waits}) {
+ $self->add_nagios_critical("no innodb log info available");
+ } else {
+ $self->valdiff(\%params, qw(log_waits));
+ $self->{log_waits_rate} =
+ $self->{delta_log_waits} / $self->{delta_timestamp};
+ }
+ } elsif ($params{mode} =~ /server::instance::innodb::needoptimize/) {
+#fragmentation=$(($datafree * 100 / $datalength))
+
+#http://www.electrictoolbox.com/optimize-tables-mysql-php/
+ my @result = $self->{handle}->fetchall_array(q{
+SHOW TABLE STATUS WHERE Data_free / Data_length > 0.1 AND Data_free > 102400
+});
+printf "%s\n", Data::Dumper::Dumper(\@result);
+
+ }
+}
+
+sub nagios {
+ my $self = shift;
+ my %params = @_;
+ my $now = $params{lookback} ? '_now' : '';
+ if ($self->{have_innodb} eq "DISABLED") {
+ $self->add_nagios_ok("the innodb engine has been disabled");
+ } elsif (! $self->{nagios_level}) {
+ if ($params{mode} =~ /server::instance::innodb::bufferpool::hitrate/) {
+ my $refkey = 'bufferpool_hitrate'.($params{lookback} ? '_now' : '');
+ $self->add_nagios(
+ $self->check_thresholds($self->{$refkey}, "99:", "95:"),
+ sprintf "innodb buffer pool hitrate at %.2f%%", $self->{$refkey});
+ $self->add_perfdata(sprintf "bufferpool_hitrate=%.2f%%;%s;%s;0;100",
+ $self->{bufferpool_hitrate},
+ $self->{warningrange}, $self->{criticalrange});
+ $self->add_perfdata(sprintf "bufferpool_hitrate_now=%.2f%%",
+ $self->{bufferpool_hitrate_now});
+ } elsif ($params{mode} =~ /server::instance::innodb::bufferpool::waitfree/) {
+ $self->add_nagios(
+ $self->check_thresholds($self->{bufferpool_wait_free_rate}, "1", "10"),
+ sprintf "%ld innodb buffer pool waits in %ld seconds (%.4f/sec)",
+ $self->{delta_bufferpool_wait_free}, $self->{delta_timestamp},
+ $self->{bufferpool_wait_free_rate});
+ $self->add_perfdata(sprintf "bufferpool_free_waits_rate=%.4f;%s;%s;0;100",
+ $self->{bufferpool_wait_free_rate},
+ $self->{warningrange}, $self->{criticalrange});
+ } elsif ($params{mode} =~ /server::instance::innodb::logwaits/) {
+ $self->add_nagios(
+ $self->check_thresholds($self->{log_waits_rate}, "1", "10"),
+ sprintf "%ld innodb log waits in %ld seconds (%.4f/sec)",
+ $self->{delta_log_waits}, $self->{delta_timestamp},
+ $self->{log_waits_rate});
+ $self->add_perfdata(sprintf "innodb_log_waits_rate=%.4f;%s;%s;0;100",
+ $self->{log_waits_rate},
+ $self->{warningrange}, $self->{criticalrange});
+ }
+ }
+}
+
+
+
+
+package DBD::MySQL::Server::Instance::MyISAM;
+
+use strict;
+
+our @ISA = qw(DBD::MySQL::Server::Instance);
+
+
+sub new {
+ my $class = shift;
+ my %params = @_;
+ my $self = {
+ handle => $params{handle},
+ internals => undef,
+ warningrange => $params{warningrange},
+ criticalrange => $params{criticalrange},
+ };
+ bless $self, $class;
+ $self->init(%params);
+ return $self;
+}
+
+sub init {
+ my $self = shift;
+ my %params = @_;
+ $self->init_nagios();
+ if ($params{mode} =~ /server::instance::myisam/) {
+ $self->{internals} =
+ DBD::MySQL::Server::Instance::MyISAM::Internals->new(%params);
+ }
+}
+
+sub nagios {
+ my $self = shift;
+ my %params = @_;
+ if ($params{mode} =~ /server::instance::myisam/) {
+ $self->{internals}->nagios(%params);
+ $self->merge_nagios($self->{internals});
+ }
+}
+
+
+package DBD::MySQL::Server::Instance::MyISAM::Internals;
+
+use strict;
+
+our @ISA = qw(DBD::MySQL::Server::Instance::MyISAM);
+
+our $internals; # singleton, nur ein einziges mal instantiierbar
+
+sub new {
+ my $class = shift;
+ my %params = @_;
+ unless ($internals) {
+ $internals = {
+ handle => $params{handle},
+ keycache_hitrate => undef,
+ warningrange => $params{warningrange},
+ criticalrange => $params{criticalrange},
+ };
+ bless($internals, $class);
+ $internals->init(%params);
+ }
+ return($internals);
+}
+
+sub init {
+ my $self = shift;
+ my %params = @_;
+ my $dummy;
+ $self->debug("enter init");
+ $self->init_nagios();
+ if ($params{mode} =~ /server::instance::myisam::keycache::hitrate/) {
+ ($dummy, $self->{key_reads})
+ = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Key_reads'
+ });
+ ($dummy, $self->{key_read_requests})
+ = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Key_read_requests'
+ });
+ if (! defined $self->{key_read_requests}) {
+ $self->add_nagios_critical("no myisam keycache info available");
+ } else {
+ $self->valdiff(\%params, qw(key_reads key_read_requests));
+ $self->{keycache_hitrate} =
+ $self->{key_read_requests} > 0 ?
+ 100 - (100 * $self->{key_reads} /
+ $self->{key_read_requests}) : 100;
+ $self->{keycache_hitrate_now} =
+ $self->{delta_key_read_requests} > 0 ?
+ 100 - (100 * $self->{delta_key_reads} /
+ $self->{delta_key_read_requests}) : 100;
+ }
+ } elsif ($params{mode} =~ /server::instance::myisam::sonstnochwas/) {
+ }
+}
+
+sub nagios {
+ my $self = shift;
+ my %params = @_;
+ if (! $self->{nagios_level}) {
+ if ($params{mode} =~ /server::instance::myisam::keycache::hitrate/) {
+ my $refkey = 'keycache_hitrate'.($params{lookback} ? '_now' : '');
+ $self->add_nagios(
+ $self->check_thresholds($self->{$refkey}, "99:", "95:"),
+ sprintf "myisam keycache hitrate at %.2f%%", $self->{$refkey});
+ $self->add_perfdata(sprintf "keycache_hitrate=%.2f%%;%s;%s",
+ $self->{keycache_hitrate},
+ $self->{warningrange}, $self->{criticalrange});
+ $self->add_perfdata(sprintf "keycache_hitrate_now=%.2f%%;%s;%s",
+ $self->{keycache_hitrate_now},
+ $self->{warningrange}, $self->{criticalrange});
+ }
+ }
+}
+
+
+package DBD::MySQL::Server::Instance::Replication;
+
+use strict;
+
+our @ISA = qw(DBD::MySQL::Server::Instance);
+
+
+sub new {
+ my $class = shift;
+ my %params = @_;
+ my $self = {
+ handle => $params{handle},
+ internals => undef,
+ warningrange => $params{warningrange},
+ criticalrange => $params{criticalrange},
+ };
+ bless $self, $class;
+ $self->init(%params);
+ return $self;
+}
+
+sub init {
+ my $self = shift;
+ my %params = @_;
+ $self->init_nagios();
+ if ($params{mode} =~ /server::instance::replication/) {
+ $self->{internals} =
+ DBD::MySQL::Server::Instance::Replication::Internals->new(%params);
+ }
+}
+
+sub nagios {
+ my $self = shift;
+ my %params = @_;
+ if ($params{mode} =~ /server::instance::replication/) {
+ $self->{internals}->nagios(%params);
+ $self->merge_nagios($self->{internals});
+ }
+}
+
+
+package DBD::MySQL::Server::Instance::Replication::Internals;
+
+use strict;
+
+our @ISA = qw(DBD::MySQL::Server::Instance::Replication);
+
+our $internals; # singleton, nur ein einziges mal instantiierbar
+
+sub new {
+ my $class = shift;
+ my %params = @_;
+ unless ($internals) {
+ $internals = {
+ handle => $params{handle},
+ seconds_behind_master => undef,
+ slave_io_running => undef,
+ slave_sql_running => undef,
+ warningrange => $params{warningrange},
+ criticalrange => $params{criticalrange},
+ };
+ bless($internals, $class);
+ $internals->init(%params);
+ }
+ return($internals);
+}
+
+sub init {
+ my $self = shift;
+ my %params = @_;
+ $self->debug("enter init");
+ $self->init_nagios();
+ if ($params{mode} =~ /server::instance::replication::slavelag/) {
+ # "show slave status", "Seconds_Behind_Master"
+ my $slavehash = $self->{handle}->selectrow_hashref(q{
+ SHOW SLAVE STATUS
+ });
+ if ((! defined $slavehash->{Seconds_Behind_Master}) &&
+ (lc $slavehash->{Slave_IO_Running} eq 'no')) {
+ $self->add_nagios_critical(
+ "unable to get slave lag, because io thread is not running");
+ } elsif (! defined $slavehash->{Seconds_Behind_Master}) {
+ $self->add_nagios_critical(sprintf "unable to get replication info%s",
+ $self->{handle}->{errstr} ? $self->{handle}->{errstr} : "");
+ } else {
+ $self->{seconds_behind_master} = $slavehash->{Seconds_Behind_Master};
+ }
+ } elsif ($params{mode} =~ /server::instance::replication::slaveiorunning/) {
+ # "show slave status", "Slave_IO_Running"
+ my $slavehash = $self->{handle}->selectrow_hashref(q{
+ SHOW SLAVE STATUS
+ });
+ if (! defined $slavehash->{Slave_IO_Running}) {
+ $self->add_nagios_critical(sprintf "unable to get replication info%s",
+ $self->{handle}->{errstr} ? $self->{handle}->{errstr} : "");
+ } else {
+ $self->{slave_io_running} = $slavehash->{Slave_IO_Running};
+ }
+ } elsif ($params{mode} =~ /server::instance::replication::slavesqlrunning/) {
+ # "show slave status", "Slave_SQL_Running"
+ my $slavehash = $self->{handle}->selectrow_hashref(q{
+ SHOW SLAVE STATUS
+ });
+ if (! defined $slavehash->{Slave_SQL_Running}) {
+ $self->add_nagios_critical(sprintf "unable to get replication info%s",
+ $self->{handle}->{errstr} ? $self->{handle}->{errstr} : "");
+ } else {
+ $self->{slave_sql_running} = $slavehash->{Slave_SQL_Running};
+ }
+ }
+}
+
+sub nagios {
+ my $self = shift;
+ my %params = @_;
+ if (! $self->{nagios_level}) {
+ if ($params{mode} =~ /server::instance::replication::slavelag/) {
+ $self->add_nagios(
+ $self->check_thresholds($self->{seconds_behind_master}, "10", "20"),
+ sprintf "Slave is %d seconds behind master",
+ $self->{seconds_behind_master});
+ $self->add_perfdata(sprintf "slave_lag=%d;%s;%s",
+ $self->{seconds_behind_master},
+ $self->{warningrange}, $self->{criticalrange});
+ } elsif ($params{mode} =~ /server::instance::replication::slaveiorunning/) {
+ if (lc $self->{slave_io_running} eq "yes") {
+ $self->add_nagios_ok("Slave io is running");
+ } else {
+ $self->add_nagios_critical("Slave io is not running");
+ }
+ } elsif ($params{mode} =~ /server::instance::replication::slavesqlrunning/) {
+ if (lc $self->{slave_sql_running} eq "yes") {
+ $self->add_nagios_ok("Slave sql is running");
+ } else {
+ $self->add_nagios_critical("Slave sql is not running");
+ }
+ }
+ }
+}
+
+
+
+package DBD::MySQL::Server::Instance;
+
+use strict;
+
+our @ISA = qw(DBD::MySQL::Server);
+
+
+sub new {
+ my $class = shift;
+ my %params = @_;
+ my $self = {
+ handle => $params{handle},
+ uptime => $params{uptime},
+ warningrange => $params{warningrange},
+ criticalrange => $params{criticalrange},
+ threads_connected => undef,
+ threads_created => undef,
+ connections => undef,
+ threadcache_hitrate => undef,
+ querycache_hitrate => undef,
+ lowmem_prunes_per_sec => undef,
+ slow_queries_per_sec => undef,
+ longrunners => undef,
+ tablecache_hitrate => undef,
+ index_usage => undef,
+ engine_innodb => undef,
+ engine_myisam => undef,
+ replication => undef,
+ };
+ bless $self, $class;
+ $self->init(%params);
+ return $self;
+}
+
+sub init {
+ my $self = shift;
+ my %params = @_;
+ my $dummy;
+ $self->init_nagios();
+ if ($params{mode} =~ /server::instance::connectedthreads/) {
+ ($dummy, $self->{threads_connected}) = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Threads_connected'
+ });
+ } elsif ($params{mode} =~ /server::instance::createdthreads/) {
+ ($dummy, $self->{threads_created}) = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Threads_created'
+ });
+ $self->valdiff(\%params, qw(threads_created));
+ $self->{threads_created_per_sec} = $self->{delta_threads_created} /
+ $self->{delta_timestamp};
+ } elsif ($params{mode} =~ /server::instance::runningthreads/) {
+ ($dummy, $self->{threads_running}) = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Threads_running'
+ });
+ } elsif ($params{mode} =~ /server::instance::cachedthreads/) {
+ ($dummy, $self->{threads_cached}) = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Threads_cached'
+ });
+ } elsif ($params{mode} =~ /server::instance::abortedconnects/) {
+ ($dummy, $self->{connects_aborted}) = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Aborted_connects'
+ });
+ $self->valdiff(\%params, qw(connects_aborted));
+ $self->{connects_aborted_per_sec} = $self->{delta_connects_aborted} /
+ $self->{delta_timestamp};
+ } elsif ($params{mode} =~ /server::instance::abortedclients/) {
+ ($dummy, $self->{clients_aborted}) = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Aborted_clients'
+ });
+ $self->valdiff(\%params, qw(clients_aborted));
+ $self->{clients_aborted_per_sec} = $self->{delta_clients_aborted} /
+ $self->{delta_timestamp};
+ } elsif ($params{mode} =~ /server::instance::threadcachehitrate/) {
+ ($dummy, $self->{threads_created}) = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Threads_created'
+ });
+ ($dummy, $self->{connections}) = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Connections'
+ });
+ $self->valdiff(\%params, qw(threads_created connections));
+ if ($self->{delta_connections} > 0) {
+ $self->{threadcache_hitrate_now} =
+ 100 - ($self->{delta_threads_created} * 100.0 /
+ $self->{delta_connections});
+ } else {
+ $self->{threadcache_hitrate_now} = 100;
+ }
+ $self->{threadcache_hitrate} = 100 -
+ ($self->{threads_created} * 100.0 / $self->{connections});
+ $self->{connections_per_sec} = $self->{delta_connections} /
+ $self->{delta_timestamp};
+ } elsif ($params{mode} =~ /server::instance::querycachehitrate/) {
+ ($dummy, $self->{com_select}) = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Com_select'
+ });
+ ($dummy, $self->{qcache_hits}) = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Qcache_hits'
+ });
+ # SHOW VARIABLES WHERE Variable_name = 'have_query_cache' for 5.x, but LIKE is compatible
+ ($dummy, $self->{have_query_cache}) = $self->{handle}->fetchrow_array(q{
+ SHOW VARIABLES LIKE 'have_query_cache'
+ });
+ # SHOW VARIABLES WHERE Variable_name = 'query_cache_size'
+ ($dummy, $self->{query_cache_size}) = $self->{handle}->fetchrow_array(q{
+ SHOW VARIABLES LIKE 'query_cache_size'
+ });
+ $self->valdiff(\%params, qw(com_select qcache_hits));
+ $self->{querycache_hitrate_now} =
+ ($self->{delta_com_select} + $self->{delta_qcache_hits}) > 0 ?
+ 100 * $self->{delta_qcache_hits} /
+ ($self->{delta_com_select} + $self->{delta_qcache_hits}) :
+ 0;
+ $self->{querycache_hitrate} =
+ 100 * $self->{qcache_hits} / ($self->{com_select} + $self->{qcache_hits});
+ $self->{selects_per_sec} =
+ $self->{delta_com_select} / $self->{delta_timestamp};
+ } elsif ($params{mode} =~ /server::instance::querycachelowmemprunes/) {
+ ($dummy, $self->{lowmem_prunes}) = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Qcache_lowmem_prunes'
+ });
+ $self->valdiff(\%params, qw(lowmem_prunes));
+ $self->{lowmem_prunes_per_sec} = $self->{delta_lowmem_prunes} /
+ $self->{delta_timestamp};
+ } elsif ($params{mode} =~ /server::instance::slowqueries/) {
+ ($dummy, $self->{slow_queries}) = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Slow_queries'
+ });
+ $self->valdiff(\%params, qw(slow_queries));
+ $self->{slow_queries_per_sec} = $self->{delta_slow_queries} /
+ $self->{delta_timestamp};
+ } elsif ($params{mode} =~ /server::instance::longprocs/) {
+ if (DBD::MySQL::Server::return_first_server()->version_is_minimum("5.1")) {
+ ($self->{longrunners}) = $self->{handle}->fetchrow_array(q{
+ SELECT
+ COUNT(*)
+ FROM
+ information_schema.processlist
+ WHERE user <> 'replication'
+ AND id <> CONNECTION_ID()
+ AND time > 60
+ AND command <> 'Sleep'
+ });
+ } else {
+ $self->{longrunners} = 0 if ! defined $self->{longrunners};
+ foreach ($self->{handle}->fetchall_array(q{
+ SHOW PROCESSLIST
+ })) {
+ my($id, $user, $host, $db, $command, $tme, $state, $info) = @{$_};
+ if (($user ne 'replication') &&
+ ($tme > 60) &&
+ ($command ne 'Sleep')) {
+ $self->{longrunners}++;
+ }
+ }
+ }
+ } elsif ($params{mode} =~ /server::instance::tablecachehitrate/) {
+ ($dummy, $self->{open_tables}) = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Open_tables'
+ });
+ ($dummy, $self->{opened_tables}) = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Opened_tables'
+ });
+ if (DBD::MySQL::Server::return_first_server()->version_is_minimum("5.1.3")) {
+ # SHOW VARIABLES WHERE Variable_name = 'table_open_cache'
+ ($dummy, $self->{table_cache}) = $self->{handle}->fetchrow_array(q{
+ SHOW VARIABLES LIKE 'table_open_cache'
+ });
+ } else {
+ # SHOW VARIABLES WHERE Variable_name = 'table_cache'
+ ($dummy, $self->{table_cache}) = $self->{handle}->fetchrow_array(q{
+ SHOW VARIABLES LIKE 'table_cache'
+ });
+ }
+ $self->{table_cache} ||= 0;
+ #$self->valdiff(\%params, qw(open_tables opened_tables table_cache));
+ # _now ist hier sinnlos, da opened_tables waechst, aber open_tables wieder
+ # schrumpfen kann weil tabellen geschlossen werden.
+ if ($self->{opened_tables} != 0 && $self->{table_cache} != 0) {
+ $self->{tablecache_hitrate} =
+ 100 * $self->{open_tables} / $self->{opened_tables};
+ $self->{tablecache_fillrate} =
+ 100 * $self->{open_tables} / $self->{table_cache};
+ } elsif ($self->{opened_tables} == 0 && $self->{table_cache} != 0) {
+ $self->{tablecache_hitrate} = 100;
+ $self->{tablecache_fillrate} =
+ 100 * $self->{open_tables} / $self->{table_cache};
+ } else {
+ $self->{tablecache_hitrate} = 0;
+ $self->{tablecache_fillrate} = 0;
+ $self->add_nagios_critical("no table cache");
+ }
+ } elsif ($params{mode} =~ /server::instance::tablelockcontention/) {
+ ($dummy, $self->{table_locks_waited}) = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Table_locks_waited'
+ });
+ ($dummy, $self->{table_locks_immediate}) = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Table_locks_immediate'
+ });
+ $self->valdiff(\%params, qw(table_locks_waited table_locks_immediate));
+ $self->{table_lock_contention} =
+ ($self->{table_locks_waited} + $self->{table_locks_immediate}) > 0 ?
+ 100 * $self->{table_locks_waited} /
+ ($self->{table_locks_waited} + $self->{table_locks_immediate}) :
+ 100;
+ $self->{table_lock_contention_now} =
+ ($self->{delta_table_locks_waited} + $self->{delta_table_locks_immediate}) > 0 ?
+ 100 * $self->{delta_table_locks_waited} /
+ ($self->{delta_table_locks_waited} + $self->{delta_table_locks_immediate}) :
+ 100;
+ } elsif ($params{mode} =~ /server::instance::tableindexusage/) {
+ # http://johnjacobm.wordpress.com/2007/06/
+ # formula for calculating the percentage of full table scans
+ ($dummy, $self->{handler_read_first}) = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Handler_read_first'
+ });
+ ($dummy, $self->{handler_read_key}) = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Handler_read_key'
+ });
+ ($dummy, $self->{handler_read_next}) = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Handler_read_next'
+ });
+ ($dummy, $self->{handler_read_prev}) = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Handler_read_prev'
+ });
+ ($dummy, $self->{handler_read_rnd}) = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Handler_read_rnd'
+ });
+ ($dummy, $self->{handler_read_rnd_next}) = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Handler_read_rnd_next'
+ });
+ $self->valdiff(\%params, qw(handler_read_first handler_read_key
+ handler_read_next handler_read_prev handler_read_rnd
+ handler_read_rnd_next));
+ my $delta_reads = $self->{delta_handler_read_first} +
+ $self->{delta_handler_read_key} +
+ $self->{delta_handler_read_next} +
+ $self->{delta_handler_read_prev} +
+ $self->{delta_handler_read_rnd} +
+ $self->{delta_handler_read_rnd_next};
+ my $reads = $self->{handler_read_first} +
+ $self->{handler_read_key} +
+ $self->{handler_read_next} +
+ $self->{handler_read_prev} +
+ $self->{handler_read_rnd} +
+ $self->{handler_read_rnd_next};
+ $self->{index_usage_now} = ($delta_reads == 0) ? 0 :
+ 100 - (100.0 * ($self->{delta_handler_read_rnd} +
+ $self->{delta_handler_read_rnd_next}) /
+ $delta_reads);
+ $self->{index_usage} = ($reads == 0) ? 0 :
+ 100 - (100.0 * ($self->{handler_read_rnd} +
+ $self->{handler_read_rnd_next}) /
+ $reads);
+ } elsif ($params{mode} =~ /server::instance::tabletmpondisk/) {
+ ($dummy, $self->{created_tmp_tables}) = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Created_tmp_tables'
+ });
+ ($dummy, $self->{created_tmp_disk_tables}) = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Created_tmp_disk_tables'
+ });
+ $self->valdiff(\%params, qw(created_tmp_tables created_tmp_disk_tables));
+ $self->{pct_tmp_on_disk} = $self->{created_tmp_tables} > 0 ?
+ 100 * $self->{created_tmp_disk_tables} / $self->{created_tmp_tables} :
+ 100;
+ $self->{pct_tmp_on_disk_now} = $self->{delta_created_tmp_tables} > 0 ?
+ 100 * $self->{delta_created_tmp_disk_tables} / $self->{delta_created_tmp_tables} :
+ 100;
+ } elsif ($params{mode} =~ /server::instance::openfiles/) {
+ ($dummy, $self->{open_files_limit}) = $self->{handle}->fetchrow_array(q{
+ SHOW VARIABLES LIKE 'open_files_limit'
+ });
+ ($dummy, $self->{open_files}) = $self->{handle}->fetchrow_array(q{
+ SHOW /*!50000 global */ STATUS LIKE 'Open_files'
+ });
+ $self->{pct_open_files} = 100 * $self->{open_files} / $self->{open_files_limit};
+ } elsif ($params{mode} =~ /server::instance::needoptimize/) {
+ $self->{fragmented} = [];
+ #http://www.electrictoolbox.com/optimize-tables-mysql-php/
+ my @result = $self->{handle}->fetchall_array(q{
+ SHOW TABLE STATUS
+ });
+ foreach (@result) {
+ my ($name, $engine, $data_length, $data_free) =
+ ($_->[0], $_->[1], $_->[6 ], $_->[9]);
+ next if ($params{name} && $params{name} ne $name);
+ my $fragmentation = $data_length ? $data_free * 100 / $data_length : 0;
+ push(@{$self->{fragmented}},
+ [$name, $fragmentation, $data_length, $data_free]);
+ }
+ } elsif ($params{mode} =~ /server::instance::myisam/) {
+ $self->{engine_myisam} = DBD::MySQL::Server::Instance::MyISAM->new(
+ %params
+ );
+ } elsif ($params{mode} =~ /server::instance::innodb/) {
+ $self->{engine_innodb} = DBD::MySQL::Server::Instance::Innodb->new(
+ %params
+ );
+ } elsif ($params{mode} =~ /server::instance::replication/) {
+ $self->{replication} = DBD::MySQL::Server::Instance::Replication->new(
+ %params
+ );
+ }
+}
+
+sub nagios {
+ my $self = shift;
+ my %params = @_;
+ if (! $self->{nagios_level}) {
+ if ($params{mode} =~ /server::instance::connectedthreads/) {
+ $self->add_nagios(
+ $self->check_thresholds($self->{threads_connected}, 10, 20),
+ sprintf "%d client connection threads", $self->{threads_connected});
+ $self->add_perfdata(sprintf "threads_connected=%d;%d;%d",
+ $self->{threads_connected},
+ $self->{warningrange}, $self->{criticalrange});
+ } elsif ($params{mode} =~ /server::instance::createdthreads/) {
+ $self->add_nagios(
+ $self->check_thresholds($self->{threads_created_per_sec}, 10, 20),
+ sprintf "%.2f threads created/sec", $self->{threads_created_per_sec});
+ $self->add_perfdata(sprintf "threads_created_per_sec=%.2f;%.2f;%.2f",
+ $self->{threads_created_per_sec},
+ $self->{warningrange}, $self->{criticalrange});
+ } elsif ($params{mode} =~ /server::instance::runningthreads/) {
+ $self->add_nagios(
+ $self->check_thresholds($self->{threads_running}, 10, 20),
+ sprintf "%d running threads", $self->{threads_running});
+ $self->add_perfdata(sprintf "threads_running=%d;%d;%d",
+ $self->{threads_running},
+ $self->{warningrange}, $self->{criticalrange});
+ } elsif ($params{mode} =~ /server::instance::cachedthreads/) {
+ $self->add_nagios(
+ $self->check_thresholds($self->{threads_cached}, 10, 20),
+ sprintf "%d cached threads", $self->{threads_cached});
+ $self->add_perfdata(sprintf "threads_cached=%d;%d;%d",
+ $self->{threads_cached},
+ $self->{warningrange}, $self->{criticalrange});
+ } elsif ($params{mode} =~ /server::instance::abortedconnects/) {
+ $self->add_nagios(
+ $self->check_thresholds($self->{connects_aborted_per_sec}, 1, 5),
+ sprintf "%.2f aborted connections/sec", $self->{connects_aborted_per_sec});
+ $self->add_perfdata(sprintf "connects_aborted_per_sec=%.2f;%.2f;%.2f",
+ $self->{connects_aborted_per_sec},
+ $self->{warningrange}, $self->{criticalrange});
+ } elsif ($params{mode} =~ /server::instance::abortedclients/) {
+ $self->add_nagios(
+ $self->check_thresholds($self->{clients_aborted_per_sec}, 1, 5),
+ sprintf "%.2f aborted (client died) connections/sec", $self->{clients_aborted_per_sec});
+ $self->add_perfdata(sprintf "clients_aborted_per_sec=%.2f;%.2f;%.2f",
+ $self->{clients_aborted_per_sec},
+ $self->{warningrange}, $self->{criticalrange});
+ } elsif ($params{mode} =~ /server::instance::threadcachehitrate/) {
+ my $refkey = 'threadcache_hitrate'.($params{lookback} ? '_now' : '');
+ $self->add_nagios(
+ $self->check_thresholds($self->{$refkey}, "90:", "80:"),
+ sprintf "thread cache hitrate %.2f%%", $self->{$refkey});
+ $self->add_perfdata(sprintf "thread_cache_hitrate=%.2f%%;%s;%s",
+ $self->{threadcache_hitrate},
+ $self->{warningrange}, $self->{criticalrange});
+ $self->add_perfdata(sprintf "thread_cache_hitrate_now=%.2f%%",
+ $self->{threadcache_hitrate_now});
+ $self->add_perfdata(sprintf "connections_per_sec=%.2f",
+ $self->{connections_per_sec});
+ } elsif ($params{mode} =~ /server::instance::querycachehitrate/) {
+ my $refkey = 'querycache_hitrate'.($params{lookback} ? '_now' : '');
+ if ((lc $self->{have_query_cache} eq 'yes') && ($self->{query_cache_size})) {
+ $self->add_nagios(
+ $self->check_thresholds($self->{$refkey}, "90:", "80:"),
+ sprintf "query cache hitrate %.2f%%", $self->{$refkey});
+ } else {
+ $self->check_thresholds($self->{$refkey}, "90:", "80:");
+ $self->add_nagios_ok(
+ sprintf "query cache hitrate %.2f%% (because it's turned off)",
+ $self->{querycache_hitrate});
+ }
+ $self->add_perfdata(sprintf "qcache_hitrate=%.2f%%;%s;%s",
+ $self->{querycache_hitrate},
+ $self->{warningrange}, $self->{criticalrange});
+ $self->add_perfdata(sprintf "qcache_hitrate_now=%.2f%%",
+ $self->{querycache_hitrate_now});
+ $self->add_perfdata(sprintf "selects_per_sec=%.2f",
+ $self->{selects_per_sec});
+ } elsif ($params{mode} =~ /server::instance::querycachelowmemprunes/) {
+ $self->add_nagios(
+ $self->check_thresholds($self->{lowmem_prunes_per_sec}, "1", "10"),
+ sprintf "%d query cache lowmem prunes in %d seconds (%.2f/sec)",
+ $self->{delta_lowmem_prunes}, $self->{delta_timestamp},
+ $self->{lowmem_prunes_per_sec});
+ $self->add_perfdata(sprintf "qcache_lowmem_prunes_rate=%.2f;%s;%s",
+ $self->{lowmem_prunes_per_sec},
+ $self->{warningrange}, $self->{criticalrange});
+ } elsif ($params{mode} =~ /server::instance::slowqueries/) {
+ $self->add_nagios(
+ $self->check_thresholds($self->{slow_queries_per_sec}, "0.1", "1"),
+ sprintf "%d slow queries in %d seconds (%.2f/sec)",
+ $self->{delta_slow_queries}, $self->{delta_timestamp},
+ $self->{slow_queries_per_sec});
+ $self->add_perfdata(sprintf "slow_queries_rate=%.2f%%;%s;%s",
+ $self->{slow_queries_per_sec},
+ $self->{warningrange}, $self->{criticalrange});
+ } elsif ($params{mode} =~ /server::instance::longprocs/) {
+ $self->add_nagios(
+ $self->check_thresholds($self->{longrunners}, 10, 20),
+ sprintf "%d long running processes", $self->{longrunners});
+ $self->add_perfdata(sprintf "long_running_procs=%d;%d;%d",
+ $self->{longrunners},
+ $self->{warningrange}, $self->{criticalrange});
+ } elsif ($params{mode} =~ /server::instance::tablecachehitrate/) {
+ if ($self->{tablecache_fillrate} < 95) {
+ $self->add_nagios_ok(
+ sprintf "table cache hitrate %.2f%%, %.2f%% filled",
+ $self->{tablecache_hitrate},
+ $self->{tablecache_fillrate});
+ $self->check_thresholds($self->{tablecache_hitrate}, "99:", "95:");
+ } else {
+ $self->add_nagios(
+ $self->check_thresholds($self->{tablecache_hitrate}, "99:", "95:"),
+ sprintf "table cache hitrate %.2f%%", $self->{tablecache_hitrate});
+ }
+ $self->add_perfdata(sprintf "tablecache_hitrate=%.2f%%;%s;%s",
+ $self->{tablecache_hitrate},
+ $self->{warningrange}, $self->{criticalrange});
+ $self->add_perfdata(sprintf "tablecache_fillrate=%.2f%%",
+ $self->{tablecache_fillrate});
+ } elsif ($params{mode} =~ /server::instance::tablelockcontention/) {
+ my $refkey = 'table_lock_contention'.($params{lookback} ? '_now' : '');
+ if ($self->{uptime} > 10800) { # MySQL Bug #30599
+ $self->add_nagios(
+ $self->check_thresholds($self->{$refkey}, "1", "2"),
+ sprintf "table lock contention %.2f%%", $self->{$refkey});
+ } else {
+ $self->check_thresholds($self->{$refkey}, "1", "2");
+ $self->add_nagios_ok(
+ sprintf "table lock contention %.2f%% (uptime < 10800)",
+ $self->{$refkey});
+ }
+ $self->add_perfdata(sprintf "tablelock_contention=%.2f%%;%s;%s",
+ $self->{table_lock_contention},
+ $self->{warningrange}, $self->{criticalrange});
+ $self->add_perfdata(sprintf "tablelock_contention_now=%.2f%%",
+ $self->{table_lock_contention_now});
+ } elsif ($params{mode} =~ /server::instance::tableindexusage/) {
+ my $refkey = 'index_usage'.($params{lookback} ? '_now' : '');
+ $self->add_nagios(
+ $self->check_thresholds($self->{$refkey}, "90:", "80:"),
+ sprintf "index usage %.2f%%", $self->{$refkey});
+ $self->add_perfdata(sprintf "index_usage=%.2f%%;%s;%s",
+ $self->{index_usage},
+ $self->{warningrange}, $self->{criticalrange});
+ $self->add_perfdata(sprintf "index_usage_now=%.2f%%",
+ $self->{index_usage_now});
+ } elsif ($params{mode} =~ /server::instance::tabletmpondisk/) {
+ my $refkey = 'pct_tmp_on_disk'.($params{lookback} ? '_now' : '');
+ $self->add_nagios(
+ $self->check_thresholds($self->{$refkey}, "25", "50"),
+ sprintf "%.2f%% of %d tables were created on disk",
+ $self->{$refkey}, $self->{delta_created_tmp_tables});
+ $self->add_perfdata(sprintf "pct_tmp_table_on_disk=%.2f%%;%s;%s",
+ $self->{pct_tmp_on_disk},
+ $self->{warningrange}, $self->{criticalrange});
+ $self->add_perfdata(sprintf "pct_tmp_table_on_disk_now=%.2f%%",
+ $self->{pct_tmp_on_disk_now});
+ } elsif ($params{mode} =~ /server::instance::openfiles/) {
+ $self->add_nagios(
+ $self->check_thresholds($self->{pct_open_files}, 80, 95),
+ sprintf "%.2f%% of the open files limit reached (%d of max. %d)",
+ $self->{pct_open_files},
+ $self->{open_files}, $self->{open_files_limit});
+ $self->add_perfdata(sprintf "pct_open_files=%.3f%%;%.3f;%.3f",
+ $self->{pct_open_files},
+ $self->{warningrange},
+ $self->{criticalrange});
+ $self->add_perfdata(sprintf "open_files=%d;%d;%d",
+ $self->{open_files},
+ $self->{open_files_limit} * $self->{warningrange} / 100,
+ $self->{open_files_limit} * $self->{criticalrange} / 100);
+ } elsif ($params{mode} =~ /server::instance::needoptimize/) {
+ foreach (@{$self->{fragmented}}) {
+ $self->add_nagios(
+ $self->check_thresholds($_->[1], 10, 25),
+ sprintf "table %s is %.2f%% fragmented", $_->[0], $_->[1]);
+ if ($params{name}) {
+ $self->add_perfdata(sprintf "'%s_frag'=%.2f%%;%d;%d",
+ $_->[0], $_->[1], $self->{warningrange}, $self->{criticalrange});
+ }
+ }
+ } elsif ($params{mode} =~ /server::instance::myisam/) {
+ $self->{engine_myisam}->nagios(%params);
+ $self->merge_nagios($self->{engine_myisam});
+ } elsif ($params{mode} =~ /server::instance::innodb/) {
+ $self->{engine_innodb}->nagios(%params);
+ $self->merge_nagios($self->{engine_innodb});
+ } elsif ($params{mode} =~ /server::instance::replication/) {
+ $self->{replication}->nagios(%params);
+ $self->merge_nagios($self->{replication});
+ }
+ }
+}
+
+
+
+package DBD::MySQL::Server;
+
+use strict;
+use Time::HiRes;
+use IO::File;
+use File::Copy 'cp';
+use Data::Dumper;
+
+
+{
+ our $verbose = 0;
+ our $scream = 0; # scream if something is not implemented
+ our $access = "dbi"; # how do we access the database.
+ our $my_modules_dyn_dir = ""; # where we look for self-written extensions
+
+ my @servers = ();
+ my $initerrors = undef;
+
+ sub add_server {
+ push(@servers, shift);
+ }
+
+ sub return_servers {
+ return @servers;
+ }
+
+ sub return_first_server() {
+ return $servers[0];
+ }
+
+}
+
+sub new {
+ my $class = shift;
+ my %params = @_;
+ my $self = {
+ access => $params{method} || 'dbi',
+ hostname => $params{hostname},
+ database => $params{database} || 'information_schema',
+ port => $params{port},
+ socket => $params{socket},
+ username => $params{username},
+ password => $params{password},
+ timeout => $params{timeout},
+ warningrange => $params{warningrange},
+ criticalrange => $params{criticalrange},
+ verbose => $params{verbose},
+ report => $params{report},
+ labelformat => $params{labelformat},
+ version => 'unknown',
+ instance => undef,
+ handle => undef,
+ };
+ bless $self, $class;
+ $self->init_nagios();
+ if ($self->dbconnect(%params)) {
+ ($self->{dummy}, $self->{version}) = $self->{handle}->fetchrow_array(
+ #q{ SHOW VARIABLES WHERE Variable_name = 'version' }
+ q{ SHOW VARIABLES LIKE 'version' }
+ );
+ $self->{version} = (split "-", $self->{version})[0];
+ ($self->{dummy}, $self->{uptime}) = $self->{handle}->fetchrow_array(
+ q{ SHOW STATUS LIKE 'Uptime' }
+ );
+ DBD::MySQL::Server::add_server($self);
+ $self->init(%params);
+ }
+ return $self;
+}
+
+sub init {
+ my $self = shift;
+ my %params = @_;
+ $params{handle} = $self->{handle};
+ $params{uptime} = $self->{uptime};
+ $self->set_global_db_thresholds(\%params);
+ if ($params{mode} =~ /^server::instance/) {
+ $self->{instance} = DBD::MySQL::Server::Instance->new(%params);
+ } elsif ($params{mode} =~ /^server::sql/) {
+ $self->set_local_db_thresholds(%params);
+ if ($params{regexp}) {
+ # sql output is treated as text
+ if ($params{name2} eq $params{name}) {
+ $self->add_nagios_unknown(sprintf "where's the regexp????");
+ } else {
+ $self->{genericsql} =
+ $self->{handle}->fetchrow_array($params{selectname});
+ if (! defined $self->{genericsql}) {
+ $self->add_nagios_unknown(sprintf "got no valid response for %s",
+ $params{selectname});
+ }
+ }
+ } else {
+ # sql output must be a number (or array of numbers)
+ @{$self->{genericsql}} =
+ $self->{handle}->fetchrow_array($params{selectname});
+ if (! (defined $self->{genericsql} &&
+ (scalar(grep { /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/ } @{$self->{genericsql}})) ==
+ scalar(@{$self->{genericsql}}))) {
+ $self->add_nagios_unknown(sprintf "got no valid response for %s",
+ $params{selectname});
+ } else {
+ # name2 in array
+ # units in array
+ }
+ }
+ } elsif ($params{mode} =~ /^server::uptime/) {
+ # already set with the connection. but use minutes here
+ } elsif ($params{mode} =~ /^server::connectiontime/) {
+ $self->{connection_time} = $self->{tac} - $self->{tic};
+ } elsif ($params{mode} =~ /^my::([^:.]+)/) {
+ my $class = $1;
+ my $loaderror = undef;
+ substr($class, 0, 1) = uc substr($class, 0, 1);
+ foreach my $libpath (split(":", $DBD::MySQL::Server::my_modules_dyn_dir)) {
+ foreach my $extmod (glob $libpath."/CheckMySQLHealth*.pm") {
+ eval {
+ $self->trace(sprintf "loading module %s", $extmod);
+ require $extmod;
+ };
+ if ($@) {
+ $loaderror = $extmod;
+ $self->trace(sprintf "failed loading module %s: %s", $extmod, $@);
+ }
+ }
+ }
+ my $obj = {
+ handle => $params{handle},
+ warningrange => $params{warningrange},
+ criticalrange => $params{criticalrange},
+ };
+ bless $obj, "My$class";
+ $self->{my} = $obj;
+ if ($self->{my}->isa("DBD::MySQL::Server")) {
+ my $dos_init = $self->can("init");
+ my $dos_nagios = $self->can("nagios");
+ my $my_init = $self->{my}->can("init");
+ my $my_nagios = $self->{my}->can("nagios");
+ if ($my_init == $dos_init) {
+ $self->add_nagios_unknown(
+ sprintf "Class %s needs an init() method", ref($self->{my}));
+ } elsif ($my_nagios == $dos_nagios) {
+ $self->add_nagios_unknown(
+ sprintf "Class %s needs a nagios() method", ref($self->{my}));
+ } else {
+ $self->{my}->init_nagios(%params);
+ $self->{my}->init(%params);
+ }
+ } else {
+ $self->add_nagios_unknown(
+ sprintf "Class %s is not a subclass of DBD::MySQL::Server%s",
+ ref($self->{my}),
+ $loaderror ? sprintf " (syntax error in %s?)", $loaderror : "" );
+ }
+ } else {
+ printf "broken mode %s\n", $params{mode};
+ }
+}
+
+sub dump {
+ my $self = shift;
+ my $message = shift || "";
+ printf "%s %s\n", $message, Data::Dumper::Dumper($self);
+}
+
+sub nagios {
+ my $self = shift;
+ my %params = @_;
+ if (! $self->{nagios_level}) {
+ if ($params{mode} =~ /^server::instance/) {
+ $self->{instance}->nagios(%params);
+ $self->merge_nagios($self->{instance});
+ } elsif ($params{mode} =~ /^server::database/) {
+ $self->{database}->nagios(%params);
+ $self->merge_nagios($self->{database});
+ } elsif ($params{mode} =~ /^server::uptime/) {
+ $self->add_nagios(
+ $self->check_thresholds($self->{uptime} / 60, "10:", "5:"),
+ sprintf "database is up since %d minutes", $self->{uptime} / 60);
+ $self->add_perfdata(sprintf "uptime=%ds",
+ $self->{uptime});
+ } elsif ($params{mode} =~ /^server::connectiontime/) {
+ $self->add_nagios(
+ $self->check_thresholds($self->{connection_time}, 1, 5),
+ sprintf "%.2f seconds to connect as %s",
+ $self->{connection_time}, ($self->{username} || getpwuid($<)));
+ $self->add_perfdata(sprintf "connection_time=%.4fs;%d;%d",
+ $self->{connection_time},
+ $self->{warningrange}, $self->{criticalrange});
+ } elsif ($params{mode} =~ /^server::sql/) {
+ if ($params{regexp}) {
+ if (substr($params{name2}, 0, 1) eq '!') {
+ $params{name2} =~ s/^!//;
+ if ($self->{genericsql} !~ /$params{name2}/) {
+ $self->add_nagios_ok(
+ sprintf "output %s does not match pattern %s",
+ $self->{genericsql}, $params{name2});
+ } else {
+ $self->add_nagios_critical(
+ sprintf "output %s matches pattern %s",
+ $self->{genericsql}, $params{name2});
+ }
+ } else {
+ if ($self->{genericsql} =~ /$params{name2}/) {
+ $self->add_nagios_ok(
+ sprintf "output %s matches pattern %s",
+ $self->{genericsql}, $params{name2});
+ } else {
+ $self->add_nagios_critical(
+ sprintf "output %s does not match pattern %s",
+ $self->{genericsql}, $params{name2});
+ }
+ }
+ } else {
+ $self->add_nagios(
+ # the first item in the list will trigger the threshold values
+ $self->check_thresholds($self->{genericsql}[0], 1, 5),
+ sprintf "%s: %s%s",
+ $params{name2} ? lc $params{name2} : lc $params{selectname},
+ # float as float, integers as integers
+ join(" ", map {
+ (sprintf("%d", $_) eq $_) ? $_ : sprintf("%f", $_)
+ } @{$self->{genericsql}}),
+ $params{units} ? $params{units} : "");
+ my $i = 0;
+ # workaround... getting the column names from the database would be nicer
+ my @names2_arr = split(/\s+/, $params{name2});
+ foreach my $t (@{$self->{genericsql}}) {
+ $self->add_perfdata(sprintf "\'%s\'=%s%s;%s;%s",
+ $names2_arr[$i] ? lc $names2_arr[$i] : lc $params{selectname},
+ # float as float, integers as integers
+ (sprintf("%d", $t) eq $t) ? $t : sprintf("%f", $t),
+ $params{units} ? $params{units} : "",
+ ($i == 0) ? $self->{warningrange} : "",
+ ($i == 0) ? $self->{criticalrange} : ""
+ );
+ $i++;
+ }
+ }
+ } elsif ($params{mode} =~ /^my::([^:.]+)/) {
+ $self->{my}->nagios(%params);
+ $self->merge_nagios($self->{my});
+ }
+ }
+}
+
+
+sub init_nagios {
+ my $self = shift;
+ no strict 'refs';
+ if (! ref($self)) {
+ my $nagiosvar = $self."::nagios";
+ my $nagioslevelvar = $self."::nagios_level";
+ $$nagiosvar = {
+ messages => {
+ 0 => [],
+ 1 => [],
+ 2 => [],
+ 3 => [],
+ },
+ perfdata => [],
+ };
+ $$nagioslevelvar = $ERRORS{OK},
+ } else {
+ $self->{nagios} = {
+ messages => {
+ 0 => [],
+ 1 => [],
+ 2 => [],
+ 3 => [],
+ },
+ perfdata => [],
+ };
+ $self->{nagios_level} = $ERRORS{OK},
+ }
+}
+
+sub check_thresholds {
+ my $self = shift;
+ my $value = shift;
+ my $defaultwarningrange = shift;
+ my $defaultcriticalrange = shift;
+ my $level = $ERRORS{OK};
+ $self->{warningrange} = defined $self->{warningrange} ?
+ $self->{warningrange} : $defaultwarningrange;
+ $self->{criticalrange} = defined $self->{criticalrange} ?
+ $self->{criticalrange} : $defaultcriticalrange;
+ if ($self->{warningrange} !~ /:/ && $self->{criticalrange} !~ /:/) {
+ # warning = 10, critical = 20, warn if > 10, crit if > 20
+ $level = $ERRORS{WARNING} if $value > $self->{warningrange};
+ $level = $ERRORS{CRITICAL} if $value > $self->{criticalrange};
+ } elsif ($self->{warningrange} =~ /([\d\.]+):/ &&
+ $self->{criticalrange} =~ /([\d\.]+):/) {
+ # warning = 98:, critical = 95:, warn if < 98, crit if < 95
+ $self->{warningrange} =~ /([\d\.]+):/;
+ $level = $ERRORS{WARNING} if $value < $1;
+ $self->{criticalrange} =~ /([\d\.]+):/;
+ $level = $ERRORS{CRITICAL} if $value < $1;
+ }
+ return $level;
+ #
+ # syntax error must be reported with returncode -1
+ #
+}
+
+sub add_nagios {
+ my $self = shift;
+ my $level = shift;
+ my $message = shift;
+ push(@{$self->{nagios}->{messages}->{$level}}, $message);
+ # recalc current level
+ foreach my $llevel qw(CRITICAL WARNING UNKNOWN OK) {
+ if (scalar(@{$self->{nagios}->{messages}->{$ERRORS{$llevel}}})) {
+ $self->{nagios_level} = $ERRORS{$llevel};
+ }
+ }
+}
+
+sub add_nagios_ok {
+ my $self = shift;
+ my $message = shift;
+ $self->add_nagios($ERRORS{OK}, $message);
+}
+
+sub add_nagios_warning {
+ my $self = shift;
+ my $message = shift;
+ $self->add_nagios($ERRORS{WARNING}, $message);
+}
+
+sub add_nagios_critical {
+ my $self = shift;
+ my $message = shift;
+ $self->add_nagios($ERRORS{CRITICAL}, $message);
+}
+
+sub add_nagios_unknown {
+ my $self = shift;
+ my $message = shift;
+ $self->add_nagios($ERRORS{UNKNOWN}, $message);
+}
+
+sub add_perfdata {
+ my $self = shift;
+ my $data = shift;
+ push(@{$self->{nagios}->{perfdata}}, $data);
+}
+
+sub merge_nagios {
+ my $self = shift;
+ my $child = shift;
+ foreach my $level (0..3) {
+ foreach (@{$child->{nagios}->{messages}->{$level}}) {
+ $self->add_nagios($level, $_);
+ }
+ #push(@{$self->{nagios}->{messages}->{$level}},
+ # @{$child->{nagios}->{messages}->{$level}});
+ }
+ push(@{$self->{nagios}->{perfdata}}, @{$child->{nagios}->{perfdata}});
+}
+
+sub calculate_result {
+ my $self = shift;
+ my $labels = shift || {};
+ my $multiline = 0;
+ map {
+ $self->{nagios_level} = $ERRORS{$_} if
+ (scalar(@{$self->{nagios}->{messages}->{$ERRORS{$_}}}));
+ } ("OK", "UNKNOWN", "WARNING", "CRITICAL");
+ if ($ENV{NRPE_MULTILINESUPPORT} &&
+ length join(" ", @{$self->{nagios}->{perfdata}}) > 200) {
+ $multiline = 1;
+ }
+ my $all_messages = join(($multiline ? "\n" : ", "), map {
+ join(($multiline ? "\n" : ", "), @{$self->{nagios}->{messages}->{$ERRORS{$_}}})
+ } grep {
+ scalar(@{$self->{nagios}->{messages}->{$ERRORS{$_}}})
+ } ("CRITICAL", "WARNING", "UNKNOWN", "OK"));
+ my $bad_messages = join(($multiline ? "\n" : ", "), map {
+ join(($multiline ? "\n" : ", "), @{$self->{nagios}->{messages}->{$ERRORS{$_}}})
+ } grep {
+ scalar(@{$self->{nagios}->{messages}->{$ERRORS{$_}}})
+ } ("CRITICAL", "WARNING", "UNKNOWN"));
+ my $all_messages_short = $bad_messages ? $bad_messages : 'no problems';
+ my $all_messages_html = "<table style=\"border-collapse: collapse;\">".
+ join("", map {
+ my $level = $_;
+ join("", map {
+ sprintf "<tr valign=\"top\"><td class=\"service%s\">%s</td></tr>",
+ $level, $_;
+ } @{$self->{nagios}->{messages}->{$ERRORS{$_}}});
+ } grep {
+ scalar(@{$self->{nagios}->{messages}->{$ERRORS{$_}}})
+ } ("CRITICAL", "WARNING", "UNKNOWN", "OK")).
+ "</table>";
+ if (exists $self->{identstring}) {
+ $self->{nagios_message} .= $self->{identstring};
+ }
+ if ($self->{report} eq "long") {
+ $self->{nagios_message} .= $all_messages;
+ } elsif ($self->{report} eq "short") {
+ $self->{nagios_message} .= $all_messages_short;
+ } elsif ($self->{report} eq "html") {
+ $self->{nagios_message} .= $all_messages_short."\n".$all_messages_html;
+ }
+ if ($self->{labelformat} eq "pnp4nagios") {
+ $self->{perfdata} = join(" ", @{$self->{nagios}->{perfdata}});
+ } else {
+ $self->{perfdata} = join(" ", map {
+ my $perfdata = $_;
+ if ($perfdata =~ /^(.*?)=(.*)/) {
+ my $label = $1;
+ my $data = $2;
+ if (exists $labels->{$label} &&
+ exists $labels->{$label}->{$self->{labelformat}}) {
+ $labels->{$label}->{$self->{labelformat}}."=".$data;
+ } else {
+ $perfdata;
+ }
+ } else {
+ $perfdata;
+ }
+ } @{$self->{nagios}->{perfdata}});
+ }
+}
+
+sub set_global_db_thresholds {
+ my $self = shift;
+ my $params = shift;
+ my $warning = undef;
+ my $critical = undef;
+ return unless defined $params->{dbthresholds};
+ $params->{name0} = $params->{dbthresholds};
+ # :pluginmode :name :warning :critical
+ # mode empty
+ #
+ eval {
+ if ($self->{handle}->fetchrow_array(q{
+ SELECT table_name
+ FROM information_schema.tables
+ WHERE table_schema = ?
+ AND table_name = 'CHECK_MYSQL_HEALTH_THRESHOLDS';
+ }, $self->{database})) { # either --database... or information_schema
+ my @dbthresholds = $self->{handle}->fetchall_array(q{
+ SELECT * FROM check_mysql_health_thresholds
+ });
+ $params->{dbthresholds} = \@dbthresholds;
+ foreach (@dbthresholds) {
+ if (($_->[0] eq $params->{cmdlinemode}) &&
+ (! defined $_->[1] || ! $_->[1])) {
+ ($warning, $critical) = ($_->[2], $_->[3]);
+ }
+ }
+ }
+ };
+ if (! $@) {
+ if ($warning) {
+ $params->{warningrange} = $warning;
+ $self->trace("read warningthreshold %s from database", $warning);
+ }
+ if ($critical) {
+ $params->{criticalrange} = $critical;
+ $self->trace("read criticalthreshold %s from database", $critical);
+ }
+ }
+}
+
+sub set_local_db_thresholds {
+ my $self = shift;
+ my %params = @_;
+ my $warning = undef;
+ my $critical = undef;
+ # :pluginmode :name :warning :critical
+ # mode name0
+ # mode name2
+ # mode name
+ #
+ # first: argument of --dbthresholds, it it exists
+ # second: --name2
+ # third: --name
+ if (ref($params{dbthresholds}) eq 'ARRAY') {
+ my $marker;
+ foreach (@{$params{dbthresholds}}) {
+ if ($_->[0] eq $params{cmdlinemode}) {
+ if (defined $_->[1] && $params{name0} && $_->[1] eq $params{name0}) {
+ ($warning, $critical) = ($_->[2], $_->[3]);
+ $marker = $params{name0};
+ last;
+ } elsif (defined $_->[1] && $params{name2} && $_->[1] eq $params{name2}) {
+ ($warning, $critical) = ($_->[2], $_->[3]);
+ $marker = $params{name2};
+ last;
+ } elsif (defined $_->[1] && $params{name} && $_->[1] eq $params{name}) {
+ ($warning, $critical) = ($_->[2], $_->[3]);
+ $marker = $params{name};
+ last;
+ }
+ }
+ }
+ if ($warning) {
+ $self->{warningrange} = $warning;
+ $self->trace("read warningthreshold %s for %s from database",
+ $marker, $warning);
+ }
+ if ($critical) {
+ $self->{criticalrange} = $critical;
+ $self->trace("read criticalthreshold %s for %s from database",
+ $marker, $critical);
+ }
+ }
+}
+
+sub debug {
+ my $self = shift;
+ my $msg = shift;
+ if ($DBD::MySQL::Server::verbose) {
+ printf "%s %s\n", $msg, ref($self);
+ }
+}
+
+sub dbconnect {
+ my $self = shift;
+ my %params = @_;
+ my $retval = undef;
+ $self->{tic} = Time::HiRes::time();
+ $self->{handle} = DBD::MySQL::Server::Connection->new(%params);
+ if ($self->{handle}->{errstr}) {
+ if ($params{mode} =~ /^server::tnsping/ &&
+ $self->{handle}->{errstr} =~ /ORA-01017/) {
+ $self->add_nagios($ERRORS{OK},
+ sprintf "connection established to %s.", $self->{connect});
+ $retval = undef;
+ } elsif ($self->{handle}->{errstr} eq "alarm\n") {
+ $self->add_nagios($ERRORS{CRITICAL},
+ sprintf "connection could not be established within %d seconds",
+ $self->{timeout});
+ } else {
+ $self->add_nagios($ERRORS{CRITICAL},
+ sprintf "cannot connect to %s. %s",
+ $self->{database}, $self->{handle}->{errstr});
+ $retval = undef;
+ }
+ } else {
+ $retval = $self->{handle};
+ }
+ $self->{tac} = Time::HiRes::time();
+ return $retval;
+}
+
+sub trace {
+ my $self = shift;
+ my $format = shift;
+ $self->{trace} = -f "/tmp/check_mysql_health.trace" ? 1 : 0;
+ if ($self->{verbose}) {
+ printf("%s: ", scalar localtime);
+ printf($format, @_);
+ }
+ if ($self->{trace}) {
+ my $logfh = new IO::File;
+ $logfh->autoflush(1);
+ if ($logfh->open("/tmp/check_mysql_health.trace", "a")) {
+ $logfh->printf("%s: ", scalar localtime);
+ $logfh->printf($format, @_);
+ $logfh->printf("\n");
+ $logfh->close();
+ }
+ }
+}
+
+sub DESTROY {
+ my $self = shift;
+ my $handle1 = "null";
+ my $handle2 = "null";
+ if (defined $self->{handle}) {
+ $handle1 = ref($self->{handle});
+ if (defined $self->{handle}->{handle}) {
+ $handle2 = ref($self->{handle}->{handle});
+ }
+ }
+ $self->trace(sprintf "DESTROY %s with handle %s %s", ref($self), $handle1, $handle2);
+ if (ref($self) eq "DBD::MySQL::Server") {
+ }
+ $self->trace(sprintf "DESTROY %s exit with handle %s %s", ref($self), $handle1, $handle2);
+ if (ref($self) eq "DBD::MySQL::Server") {
+ #printf "humpftata\n";
+ }
+}
+
+sub save_state {
+ my $self = shift;
+ my %params = @_;
+ my $extension = "";
+ my $mode = $params{mode};
+ if ($params{connect} && $params{connect} =~ /(\w+)\/(\w+)@(\w+)/) {
+ $params{connect} = $3;
+ } elsif ($params{connect}) {
+ # just to be sure
+ $params{connect} =~ s/\//_/g;
+ }
+ if ($^O =~ /MSWin/) {
+ $mode =~ s/::/_/g;
+ $params{statefilesdir} = $self->system_vartmpdir();
+ }
+ if (! -d $params{statefilesdir}) {
+ eval {
+ use File::Path;
+ mkpath $params{statefilesdir};
+ };
+ }
+ if ($@ || ! -w $params{statefilesdir}) {
+ $self->add_nagios($ERRORS{CRITICAL},
+ sprintf "statefilesdir %s does not exist or is not writable\n",
+ $params{statefilesdir});
+ return;
+ }
+ my $statefile = sprintf "%s_%s", $params{hostname}, $mode;
+ $extension .= $params{differenciator} ? "_".$params{differenciator} : "";
+ $extension .= $params{socket} ? "_".$params{socket} : "";
+ $extension .= $params{port} ? "_".$params{port} : "";
+ $extension .= $params{database} ? "_".$params{database} : "";
+ $extension .= $params{tablespace} ? "_".$params{tablespace} : "";
+ $extension .= $params{datafile} ? "_".$params{datafile} : "";
+ $extension .= $params{name} ? "_".$params{name} : "";
+ $extension =~ s/\//_/g;
+ $extension =~ s/\(/_/g;
+ $extension =~ s/\)/_/g;
+ $extension =~ s/\*/_/g;
+ $extension =~ s/\s/_/g;
+ $statefile .= $extension;
+ $statefile = lc $statefile;
+ $statefile = sprintf "%s/%s", $params{statefilesdir}, $statefile;
+ if (open(STATE, ">$statefile")) {
+ if ((ref($params{save}) eq "HASH") && exists $params{save}->{timestamp}) {
+ $params{save}->{localtime} = scalar localtime $params{save}->{timestamp};
+ }
+ printf STATE Data::Dumper::Dumper($params{save});
+ close STATE;
+ } else {
+ $self->add_nagios($ERRORS{CRITICAL},
+ sprintf "statefile %s is not writable", $statefile);
+ }
+ $self->debug(sprintf "saved %s to %s",
+ Data::Dumper::Dumper($params{save}), $statefile);
+}
+
+sub load_state {
+ my $self = shift;
+ my %params = @_;
+ my $extension = "";
+ my $mode = $params{mode};
+ if ($params{connect} && $params{connect} =~ /(\w+)\/(\w+)@(\w+)/) {
+ $params{connect} = $3;
+ } elsif ($params{connect}) {
+ # just to be sure
+ $params{connect} =~ s/\//_/g;
+ }
+ if ($^O =~ /MSWin/) {
+ $mode =~ s/::/_/g;
+ $params{statefilesdir} = $self->system_vartmpdir();
+ }
+ my $statefile = sprintf "%s_%s", $params{hostname}, $mode;
+ $extension .= $params{differenciator} ? "_".$params{differenciator} : "";
+ $extension .= $params{socket} ? "_".$params{socket} : "";
+ $extension .= $params{port} ? "_".$params{port} : "";
+ $extension .= $params{database} ? "_".$params{database} : "";
+ $extension .= $params{tablespace} ? "_".$params{tablespace} : "";
+ $extension .= $params{datafile} ? "_".$params{datafile} : "";
+ $extension .= $params{name} ? "_".$params{name} : "";
+ $extension =~ s/\//_/g;
+ $extension =~ s/\(/_/g;
+ $extension =~ s/\)/_/g;
+ $extension =~ s/\*/_/g;
+ $extension =~ s/\s/_/g;
+ $statefile .= $extension;
+ $statefile = lc $statefile;
+ $statefile = sprintf "%s/%s", $params{statefilesdir}, $statefile;
+ if ( -f $statefile) {
+ our $VAR1;
+ eval {
+ require $statefile;
+ };
+ if($@) {
+ $self->add_nagios($ERRORS{CRITICAL},
+ sprintf "statefile %s is corrupt", $statefile);
+ }
+ $self->debug(sprintf "load %s", Data::Dumper::Dumper($VAR1));
+ return $VAR1;
+ } else {
+ return undef;
+ }
+}
+
+sub valdiff {
+ my $self = shift;
+ my $pparams = shift;
+ my %params = %{$pparams};
+ my @keys = @_;
+ my $now = time;
+ my $last_values = $self->load_state(%params) || eval {
+ my $empty_events = {};
+ foreach (@keys) {
+ $empty_events->{$_} = 0;
+ }
+ $empty_events->{timestamp} = 0;
+ if ($params{lookback}) {
+ $empty_events->{lookback_history} = {};
+ }
+ $empty_events;
+ };
+ foreach (@keys) {
+ if ($params{lookback}) {
+ # find a last_value in the history which fits lookback best
+ # and overwrite $last_values->{$_} with historic data
+ if (exists $last_values->{lookback_history}->{$_}) {
+ foreach my $date (sort {$a <=> $b} keys %{$last_values->{lookback_history}->{$_}}) {
+ if ($date >= ($now - $params{lookback})) {
+ $last_values->{$_} = $last_values->{lookback_history}->{$_}->{$date};
+ $last_values->{timestamp} = $date;
+ last;
+ } else {
+ delete $last_values->{lookback_history}->{$_}->{$date};
+ }
+ }
+ }
+ }
+ $last_values->{$_} = 0 if ! exists $last_values->{$_};
+ if ($self->{$_} >= $last_values->{$_}) {
+ $self->{'delta_'.$_} = $self->{$_} - $last_values->{$_};
+ } else {
+ # vermutlich db restart und zaehler alle auf null
+ $self->{'delta_'.$_} = $self->{$_};
+ }
+ $self->debug(sprintf "delta_%s %f", $_, $self->{'delta_'.$_});
+ }
+ $self->{'delta_timestamp'} = $now - $last_values->{timestamp};
+ $params{save} = eval {
+ my $empty_events = {};
+ foreach (@keys) {
+ $empty_events->{$_} = $self->{$_};
+ }
+ $empty_events->{timestamp} = $now;
+ if ($params{lookback}) {
+ $empty_events->{lookback_history} = $last_values->{lookback_history};
+ foreach (@keys) {
+ $empty_events->{lookback_history}->{$_}->{$now} = $self->{$_};
+ }
+ }
+ $empty_events;
+ };
+ $self->save_state(%params);
+}
+
+sub requires_version {
+ my $self = shift;
+ my $version = shift;
+ my @instances = DBD::MySQL::Server::return_servers();
+ my $instversion = $instances[0]->{version};
+ if (! $self->version_is_minimum($version)) {
+ $self->add_nagios($ERRORS{UNKNOWN},
+ sprintf "not implemented/possible for MySQL release %s", $instversion);
+ }
+}
+
+sub version_is_minimum {
+ # the current version is newer or equal
+ my $self = shift;
+ my $version = shift;
+ my $newer = 1;
+ my @instances = DBD::MySQL::Server::return_servers();
+ my @v1 = map { $_ eq "x" ? 0 : $_ } split(/\./, $version);
+ my @v2 = split(/\./, $instances[0]->{version});
+ if (scalar(@v1) > scalar(@v2)) {
+ push(@v2, (0) x (scalar(@v1) - scalar(@v2)));
+ } elsif (scalar(@v2) > scalar(@v1)) {
+ push(@v1, (0) x (scalar(@v2) - scalar(@v1)));
+ }
+ foreach my $pos (0..$#v1) {
+ if ($v2[$pos] > $v1[$pos]) {
+ $newer = 1;
+ last;
+ } elsif ($v2[$pos] < $v1[$pos]) {
+ $newer = 0;
+ last;
+ }
+ }
+ #printf STDERR "check if %s os minimum %s\n", join(".", @v2), join(".", @v1);
+ return $newer;
+}
+
+sub instance_thread {
+ my $self = shift;
+ my @instances = DBD::MySQL::Server::return_servers();
+ return $instances[0]->{thread};
+}
+
+sub windows_server {
+ my $self = shift;
+ my @instances = DBD::MySQL::Server::return_servers();
+ if ($instances[0]->{os} =~ /Win/i) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+sub system_vartmpdir {
+ my $self = shift;
+ if ($^O =~ /MSWin/) {
+ return $self->system_tmpdir();
+ } else {
+ return "/var/tmp/check_mysql_health";
+ }
+}
+
+sub system_oldvartmpdir {
+ my $self = shift;
+ return "/tmp";
+}
+
+sub system_tmpdir {
+ my $self = shift;
+ if ($^O =~ /MSWin/) {
+ return $ENV{TEMP} if defined $ENV{TEMP};
+ return $ENV{TMP} if defined $ENV{TMP};
+ return File::Spec->catfile($ENV{windir}, 'Temp')
+ if defined $ENV{windir};
+ return 'C:\Temp';
+ } else {
+ return "/tmp";
+ }
+}
+
+
+package DBD::MySQL::Server::Connection;
+
+use strict;
+
+our @ISA = qw(DBD::MySQL::Server);
+
+
+sub new {
+ my $class = shift;
+ my %params = @_;
+ my $self = {
+ mode => $params{mode},
+ timeout => $params{timeout},
+ access => $params{method} || "dbi",
+ hostname => $params{hostname},
+ database => $params{database} || "information_schema",
+ port => $params{port},
+ socket => $params{socket},
+ username => $params{username},
+ password => $params{password},
+ handle => undef,
+ };
+ bless $self, $class;
+ if ($params{method} eq "dbi") {
+ bless $self, "DBD::MySQL::Server::Connection::Dbi";
+ } elsif ($params{method} eq "mysql") {
+ bless $self, "DBD::MySQL::Server::Connection::Mysql";
+ } elsif ($params{method} eq "sqlrelay") {
+ bless $self, "DBD::MySQL::Server::Connection::Sqlrelay";
+ }
+ $self->init(%params);
+ return $self;
+}
+
+
+package DBD::MySQL::Server::Connection::Dbi;
+
+use strict;
+use Net::Ping;
+
+our @ISA = qw(DBD::MySQL::Server::Connection);
+
+
+sub init {
+ my $self = shift;
+ my %params = @_;
+ my $retval = undef;
+ if ($self->{mode} =~ /^server::tnsping/) {
+ if (! $self->{connect}) {
+ $self->{errstr} = "Please specify a database";
+ } else {
+ $self->{sid} = $self->{connect};
+ $self->{username} ||= time; # prefer an existing user
+ $self->{password} = time;
+ }
+ } else {
+ if (($self->{hostname} ne 'localhost') && (! $self->{username} || ! $self->{password})) {
+ $self->{errstr} = "Please specify hostname, username and password";
+ return undef;
+ }
+ $self->{dsn} = "DBI:mysql:";
+ $self->{dsn} .= sprintf "database=%s", $self->{database};
+ $self->{dsn} .= sprintf ";host=%s", $self->{hostname};
+ $self->{dsn} .= sprintf ";port=%s", $self->{port}
+ unless $self->{socket} || $self->{hostname} eq 'localhost';
+ $self->{dsn} .= sprintf ";mysql_socket=%s", $self->{socket}
+ if $self->{socket};
+ }
+ if (! exists $self->{errstr}) {
+ eval {
+ require DBI;
+ use POSIX ':signal_h';
+ local $SIG{'ALRM'} = sub {
+ die "alarm\n";
+ };
+ my $mask = POSIX::SigSet->new( SIGALRM );
+ my $action = POSIX::SigAction->new(
+ sub { die "alarm\n" ; }, $mask);
+ my $oldaction = POSIX::SigAction->new();
+ sigaction(SIGALRM ,$action ,$oldaction );
+ alarm($self->{timeout} - 1); # 1 second before the global unknown timeout
+ if ($self->{handle} = DBI->connect(
+ $self->{dsn},
+ $self->{username},
+ $self->{password},
+ { RaiseError => 0, AutoCommit => 0, PrintError => 0 })) {
+# $self->{handle}->do(q{
+# ALTER SESSION SET NLS_NUMERIC_CHARACTERS=".," });
+ $retval = $self;
+ } else {
+ $self->{errstr} = DBI::errstr();
+ }
+ };
+ if ($@) {
+ $self->{errstr} = $@;
+ $retval = undef;
+ }
+ }
+ $self->{tac} = Time::HiRes::time();
+ return $retval;
+}
+
+sub selectrow_hashref {
+ my $self = shift;
+ my $sql = shift;
+ my @arguments = @_;
+ my $sth = undef;
+ my $hashref = undef;
+ eval {
+ $self->trace(sprintf "SQL:\n%s\nARGS:\n%s\n",
+ $sql, Data::Dumper::Dumper(\@arguments));
+ # helm auf! jetzt wirds dreckig.
+ if ($sql =~ /^\s*SHOW/) {
+ $hashref = $self->{handle}->selectrow_hashref($sql);
+ } else {
+ $sth = $self->{handle}->prepare($sql);
+ if (scalar(@arguments)) {
+ $sth->execute(@arguments);
+ } else {
+ $sth->execute();
+ }
+ $hashref = $sth->selectrow_hashref();
+ }
+ $self->trace(sprintf "RESULT:\n%s\n",
+ Data::Dumper::Dumper($hashref));
+ };
+ if ($@) {
+ $self->debug(sprintf "bumm %s", $@);
+ }
+ if (-f "/tmp/check_mysql_health_simulation/".$self->{mode}) {
+ my $simulation = do { local (@ARGV, $/) =
+ "/tmp/check_mysql_health_simulation/".$self->{mode}; <> };
+ # keine lust auf den scheiss
+ }
+ return $hashref;
+}
+
+sub fetchrow_array {
+ my $self = shift;
+ my $sql = shift;
+ my @arguments = @_;
+ my $sth = undef;
+ my @row = ();
+ eval {
+ $self->trace(sprintf "SQL:\n%s\nARGS:\n%s\n",
+ $sql, Data::Dumper::Dumper(\@arguments));
+ $sth = $self->{handle}->prepare($sql);
+ if (scalar(@arguments)) {
+ $sth->execute(@arguments);
+ } else {
+ $sth->execute();
+ }
+ @row = $sth->fetchrow_array();
+ $self->trace(sprintf "RESULT:\n%s\n",
+ Data::Dumper::Dumper(\@row));
+ };
+ if ($@) {
+ $self->debug(sprintf "bumm %s", $@);
+ }
+ if (-f "/tmp/check_mysql_health_simulation/".$self->{mode}) {
+ my $simulation = do { local (@ARGV, $/) =
+ "/tmp/check_mysql_health_simulation/".$self->{mode}; <> };
+ @row = split(/\s+/, (split(/\n/, $simulation))[0]);
+ }
+ return $row[0] unless wantarray;
+ return @row;
+}
+
+sub fetchall_array {
+ my $self = shift;
+ my $sql = shift;
+ my @arguments = @_;
+ my $sth = undef;
+ my $rows = undef;
+ eval {
+ $self->trace(sprintf "SQL:\n%s\nARGS:\n%s\n",
+ $sql, Data::Dumper::Dumper(\@arguments));
+ $sth = $self->{handle}->prepare($sql);
+ if (scalar(@arguments)) {
+ $sth->execute(@arguments);
+ } else {
+ $sth->execute();
+ }
+ $rows = $sth->fetchall_arrayref();
+ $self->trace(sprintf "RESULT:\n%s\n",
+ Data::Dumper::Dumper($rows));
+ };
+ if ($@) {
+ printf STDERR "bumm %s\n", $@;
+ }
+ if (-f "/tmp/check_mysql_health_simulation/".$self->{mode}) {
+ my $simulation = do { local (@ARGV, $/) =
+ "/tmp/check_mysql_health_simulation/".$self->{mode}; <> };
+ @{$rows} = map { [ split(/\s+/, $_) ] } split(/\n/, $simulation);
+ }
+ return @{$rows};
+}
+
+sub func {
+ my $self = shift;
+ $self->{handle}->func(@_);
+}
+
+
+sub execute {
+ my $self = shift;
+ my $sql = shift;
+ eval {
+ my $sth = $self->{handle}->prepare($sql);
+ $sth->execute();
+ };
+ if ($@) {
+ printf STDERR "bumm %s\n", $@;
+ }
+}
+
+sub errstr {
+ my $self = shift;
+ return $self->{errstr};
+}
+
+sub DESTROY {
+ my $self = shift;
+ $self->trace(sprintf "disconnecting DBD %s",
+ $self->{handle} ? "with handle" : "without handle");
+ $self->{handle}->disconnect() if $self->{handle};
+}
+
+package DBD::MySQL::Server::Connection::Mysql;
+
+use strict;
+use File::Temp qw/tempfile/;
+
+our @ISA = qw(DBD::MySQL::Server::Connection);
+
+
+sub init {
+ my $self = shift;
+ my %params = @_;
+ my $retval = undef;
+ $self->{loginstring} = "traditional";
+ ($self->{sql_commandfile_handle}, $self->{sql_commandfile}) =
+ tempfile($self->{mode}."XXXXX", SUFFIX => ".sql",
+ DIR => $self->system_tmpdir() );
+ close $self->{sql_commandfile_handle};
+ ($self->{sql_resultfile_handle}, $self->{sql_resultfile}) =
+ tempfile($self->{mode}."XXXXX", SUFFIX => ".out",
+ DIR => $self->system_tmpdir() );
+ close $self->{sql_resultfile_handle};
+ if ($self->{mode} =~ /^server::tnsping/) {
+ if (! $self->{connect}) {
+ $self->{errstr} = "Please specify a database";
+ } else {
+ $self->{sid} = $self->{connect};
+ $self->{username} ||= time; # prefer an existing user
+ $self->{password} = time;
+ }
+ } else {
+ if (! $self->{username} || ! $self->{password}) {
+ $self->{errstr} = "Please specify database, username and password";
+ return undef;
+ } elsif (! (($self->{hostname} && $self->{port}) || $self->{socket})) {
+ $self->{errstr} = "Please specify hostname and port or socket";
+ return undef;
+ }
+ }
+ if (! exists $self->{errstr}) {
+ eval {
+ my $mysql = '/'.'usr'.'/'.'bin'.'/'.'mysql';
+ if (! -x $mysql) {
+ die "nomysql\n";
+ }
+ if ($self->{loginstring} eq "traditional") {
+ $self->{sqlplus} = sprintf "%s ", $mysql;
+ $self->{sqlplus} .= sprintf "--batch --raw --skip-column-names ";
+ $self->{sqlplus} .= sprintf "--database=%s ", $self->{database};
+ $self->{sqlplus} .= sprintf "--host=%s ", $self->{hostname};
+ $self->{sqlplus} .= sprintf "--port=%s ", $self->{port}
+ unless $self->{socket} || $self->{hostname} eq "localhost";
+ $self->{sqlplus} .= sprintf "--socket=%s ", $self->{socket}
+ if $self->{socket};
+ $self->{sqlplus} .= sprintf "--user=%s --password=%s < %s > %s",
+ $self->{username}, $self->{password},
+ $self->{sql_commandfile}, $self->{sql_resultfile};
+ }
+
+ use POSIX ':signal_h';
+ local $SIG{'ALRM'} = sub {
+ die "alarm\n";
+ };
+ my $mask = POSIX::SigSet->new( SIGALRM );
+ my $action = POSIX::SigAction->new(
+ sub { die "alarm\n" ; }, $mask);
+ my $oldaction = POSIX::SigAction->new();
+ sigaction(SIGALRM ,$action ,$oldaction );
+ alarm($self->{timeout} - 1); # 1 second before the global unknown timeout
+
+ my $answer = $self->fetchrow_array(
+ q{ SELECT 42 FROM dual});
+ die unless defined $answer and $answer == 42;
+ $retval = $self;
+ };
+ if ($@) {
+ $self->{errstr} = $@;
+ $self->{errstr} =~ s/at $0 .*//g;
+ chomp $self->{errstr};
+ $retval = undef;
+ }
+ }
+ $self->{tac} = Time::HiRes::time();
+ return $retval;
+}
+
+sub selectrow_hashref {
+ my $self = shift;
+ my $sql = shift;
+ my @arguments = @_;
+ my $sth = undef;
+ my $hashref = undef;
+ foreach (@arguments) {
+ # replace the ? by the parameters
+ if (/^\d+$/) {
+ $sql =~ s/\?/$_/;
+ } else {
+ $sql =~ s/\?/'$_'/;
+ }
+ }
+ if ($sql =~ /^\s*SHOW/) {
+ $sql .= '\G'; # http://dev.mysql.com/doc/refman/5.1/de/show-slave-status.html
+ }
+ $self->trace(sprintf "SQL (? resolved):\n%s\nARGS:\n%s\n",
+ $sql, Data::Dumper::Dumper(\@arguments));
+ $self->create_commandfile($sql);
+ my $exit_output = `$self->{sqlplus}`;
+ if ($?) {
+ printf STDERR "fetchrow_array exit bumm \n";
+ my $output = do { local (@ARGV, $/) = $self->{sql_resultfile}; <> };
+ my @oerrs = map {
+ /((ERROR \d+).*)/ ? $1 : ();
+ } split(/\n/, $output);
+ $self->{errstr} = join(" ", @oerrs);
+ } else {
+ my $output = do { local (@ARGV, $/) = $self->{sql_resultfile}; <> };
+ if ($sql =~ /^\s*SHOW/) {
+ map {
+ if (/^\s*([\w_]+):\s*(.*)/) {
+ $hashref->{$1} = $2;
+ }
+ } split(/\n/, $output);
+ } else {
+ # i dont mess around here and you shouldn't either
+ }
+ $self->trace(sprintf "RESULT:\n%s\n",
+ Data::Dumper::Dumper($hashref));
+ }
+ unlink $self->{sql_commandfile};
+ unlink $self->{sql_resultfile};
+ return $hashref;
+}
+
+sub fetchrow_array {
+ my $self = shift;
+ my $sql = shift;
+ my @arguments = @_;
+ my $sth = undef;
+ my @row = ();
+ foreach (@arguments) {
+ # replace the ? by the parameters
+ if (/^\d+$/) {
+ $sql =~ s/\?/$_/;
+ } else {
+ $sql =~ s/\?/'$_'/;
+ }
+ }
+ $self->trace(sprintf "SQL (? resolved):\n%s\nARGS:\n%s\n",
+ $sql, Data::Dumper::Dumper(\@arguments));
+ $self->create_commandfile($sql);
+ my $exit_output = `$self->{sqlplus}`;
+ if ($?) {
+ printf STDERR "fetchrow_array exit bumm \n";
+ my $output = do { local (@ARGV, $/) = $self->{sql_resultfile}; <> };
+ my @oerrs = map {
+ /((ERROR \d+).*)/ ? $1 : ();
+ } split(/\n/, $output);
+ $self->{errstr} = join(" ", @oerrs);
+ } else {
+ my $output = do { local (@ARGV, $/) = $self->{sql_resultfile}; <> };
+ @row = map { convert($_) }
+ map { s/^\s+([\.\d]+)$/$1/g; $_ } # strip leading space from numbers
+ map { s/\s+$//g; $_ } # strip trailing space
+ split(/\t/, (split(/\n/, $output))[0]);
+ $self->trace(sprintf "RESULT:\n%s\n",
+ Data::Dumper::Dumper(\@row));
+ }
+ if ($@) {
+ $self->debug(sprintf "bumm %s", $@);
+ }
+ unlink $self->{sql_commandfile};
+ unlink $self->{sql_resultfile};
+ return $row[0] unless wantarray;
+ return @row;
+}
+
+sub fetchall_array {
+ my $self = shift;
+ my $sql = shift;
+ my @arguments = @_;
+ my $sth = undef;
+ my $rows = undef;
+ foreach (@arguments) {
+ # replace the ? by the parameters
+ if (/^\d+$/) {
+ $sql =~ s/\?/$_/;
+ } else {
+ $sql =~ s/\?/'$_'/;
+ }
+ }
+ $self->trace(sprintf "SQL (? resolved):\n%s\nARGS:\n%s\n",
+ $sql, Data::Dumper::Dumper(\@arguments));
+ $self->create_commandfile($sql);
+ my $exit_output = `$self->{sqlplus}`;
+ if ($?) {
+ printf STDERR "fetchrow_array exit bumm %s\n", $exit_output;
+ my $output = do { local (@ARGV, $/) = $self->{sql_resultfile}; <> };
+ my @oerrs = map {
+ /((ERROR \d+).*)/ ? $1 : ();
+ } split(/\n/, $output);
+ $self->{errstr} = join(" ", @oerrs);
+ } else {
+ my $output = do { local (@ARGV, $/) = $self->{sql_resultfile}; <> };
+ my @rows = map { [
+ map { convert($_) }
+ map { s/^\s+([\.\d]+)$/$1/g; $_ }
+ map { s/\s+$//g; $_ }
+ split /\t/
+ ] } grep { ! /^\d+ rows selected/ }
+ grep { ! /^Elapsed: / }
+ grep { ! /^\s*$/ } split(/\n/, $output);
+ $rows = \@rows;
+ $self->trace(sprintf "RESULT:\n%s\n",
+ Data::Dumper::Dumper($rows));
+ }
+ if ($@) {
+ $self->debug(sprintf "bumm %s", $@);
+ }
+ unlink $self->{sql_commandfile};
+ unlink $self->{sql_resultfile};
+ return @{$rows};
+}
+
+sub func {
+ my $self = shift;
+ my $function = shift;
+ $self->{handle}->func(@_);
+}
+
+sub convert {
+ my $n = shift;
+ # mostly used to convert numbers in scientific notation
+ if ($n =~ /^\s*\d+\s*$/) {
+ return $n;
+ } elsif ($n =~ /^\s*([-+]?)(\d*[\.,]*\d*)[eE]{1}([-+]?)(\d+)\s*$/) {
+ my ($vor, $num, $sign, $exp) = ($1, $2, $3, $4);
+ $n =~ s/E/e/g;
+ $n =~ s/,/\./g;
+ $num =~ s/,/\./g;
+ my $sig = $sign eq '-' ? "." . ($exp - 1 + length $num) : '';
+ my $dec = sprintf "%${sig}f", $n;
+ $dec =~ s/\.[0]+$//g;
+ return $dec;
+ } elsif ($n =~ /^\s*([-+]?)(\d+)[\.,]*(\d*)\s*$/) {
+ return $1.$2.".".$3;
+ } elsif ($n =~ /^\s*(.*?)\s*$/) {
+ return $1;
+ } else {
+ return $n;
+ }
+}
+
+
+sub execute {
+ my $self = shift;
+ my $sql = shift;
+ eval {
+ my $sth = $self->{handle}->prepare($sql);
+ $sth->execute();
+ };
+ if ($@) {
+ printf STDERR "bumm %s\n", $@;
+ }
+}
+
+sub errstr {
+ my $self = shift;
+ return $self->{errstr};
+}
+
+sub DESTROY {
+ my $self = shift;
+ $self->trace("try to clean up command and result files");
+ unlink $self->{sql_commandfile} if -f $self->{sql_commandfile};
+ unlink $self->{sql_resultfile} if -f $self->{sql_resultfile};
+}
+
+sub create_commandfile {
+ my $self = shift;
+ my $sql = shift;
+ open CMDCMD, "> $self->{sql_commandfile}";
+ printf CMDCMD "%s\n", $sql;
+ close CMDCMD;
+}
+
+
+package DBD::MySQL::Server::Connection::Sqlrelay;
+
+use strict;
+use Net::Ping;
+
+our @ISA = qw(DBD::MySQL::Server::Connection);
+
+
+sub init {
+ my $self = shift;
+ my %params = @_;
+ my $retval = undef;
+ if ($self->{mode} =~ /^server::tnsping/) {
+ if (! $self->{connect}) {
+ $self->{errstr} = "Please specify a database";
+ } else {
+ if ($self->{connect} =~ /([\.\w]+):(\d+)/) {
+ $self->{host} = $1;
+ $self->{port} = $2;
+ $self->{socket} = "";
+ } elsif ($self->{connect} =~ /([\.\w]+):([\w\/]+)/) {
+ $self->{host} = $1;
+ $self->{socket} = $2;
+ $self->{port} = "";
+ }
+ }
+ } else {
+ if (! $self->{hostname} || ! $self->{username} || ! $self->{password}) {
+ if ($self->{hostname} && $self->{hostname} =~ /(\w+)\/(\w+)@([\.\w]+):(\d+)/) {
+ $self->{username} = $1;
+ $self->{password} = $2;
+ $self->{hostname} = $3;
+ $self->{port} = $4;
+ $self->{socket} = "";
+ } elsif ($self->{hostname} && $self->{hostname} =~ /(\w+)\/(\w+)@([\.\w]+):([\w\/]+)/) {
+ $self->{username} = $1;
+ $self->{password} = $2;
+ $self->{hostname} = $3;
+ $self->{socket} = $4;
+ $self->{port} = "";
+ } else {
+ $self->{errstr} = "Please specify database, username and password";
+ return undef;
+ }
+ } else {
+ if ($self->{hostname} =~ /([\.\w]+):(\d+)/) {
+ $self->{hostname} = $1;
+ $self->{port} = $2;
+ $self->{socket} = "";
+ } elsif ($self->{hostname} =~ /([\.\w]+):([\w\/]+)/) {
+ $self->{hostname} = $1;
+ $self->{socket} = $2;
+ $self->{port} = "";
+ } else {
+ $self->{errstr} = "Please specify hostname, username, password and port/socket";
+ return undef;
+ }
+ }
+ }
+ if (! exists $self->{errstr}) {
+ eval {
+ require DBI;
+ use POSIX ':signal_h';
+ local $SIG{'ALRM'} = sub {
+ die "alarm\n";
+ };
+ my $mask = POSIX::SigSet->new( SIGALRM );
+ my $action = POSIX::SigAction->new(
+ sub { die "alarm\n" ; }, $mask);
+ my $oldaction = POSIX::SigAction->new();
+ sigaction(SIGALRM ,$action ,$oldaction );
+ alarm($self->{timeout} - 1); # 1 second before the global unknown timeout
+ if ($self->{handle} = DBI->connect(
+ sprintf("DBI:SQLRelay:host=%s;port=%d;socket=%s",
+ $self->{hostname}, $self->{port}, $self->{socket}),
+ $self->{username},
+ $self->{password},
+ { RaiseError => 1, AutoCommit => 0, PrintError => 1 })) {
+ $retval = $self;
+ if ($self->{mode} =~ /^server::tnsping/ && $self->{handle}->ping()) {
+ # database connected. fake a "unknown user"
+ $self->{errstr} = "ORA-01017";
+ }
+ } else {
+ $self->{errstr} = DBI::errstr();
+ }
+ };
+ if ($@) {
+ $self->{errstr} = $@;
+ $self->{errstr} =~ s/at [\w\/\.]+ line \d+.*//g;
+ $retval = undef;
+ }
+ }
+ $self->{tac} = Time::HiRes::time();
+ return $retval;
+}
+
+sub fetchrow_array {
+ my $self = shift;
+ my $sql = shift;
+ my @arguments = @_;
+ my $sth = undef;
+ my @row = ();
+ $self->trace(sprintf "fetchrow_array: %s", $sql);
+ eval {
+ $sth = $self->{handle}->prepare($sql);
+ if (scalar(@arguments)) {
+ $sth->execute(@arguments);
+ } else {
+ $sth->execute();
+ }
+ @row = $sth->fetchrow_array();
+ };
+ if ($@) {
+ $self->debug(sprintf "bumm %s", $@);
+ }
+ if (-f "/tmp/check_mysql_health_simulation/".$self->{mode}) {
+ my $simulation = do { local (@ARGV, $/) =
+ "/tmp/check_mysql_health_simulation/".$self->{mode}; <> };
+ @row = split(/\s+/, (split(/\n/, $simulation))[0]);
+ }
+ return $row[0] unless wantarray;
+ return @row;
+}
+
+sub fetchall_array {
+ my $self = shift;
+ my $sql = shift;
+ my @arguments = @_;
+ my $sth = undef;
+ my $rows = undef;
+ $self->trace(sprintf "fetchall_array: %s", $sql);
+ eval {
+ $sth = $self->{handle}->prepare($sql);
+ if (scalar(@arguments)) {
+ $sth->execute(@arguments);
+ } else {
+ $sth->execute();
+ }
+ $rows = $sth->fetchall_arrayref();
+ };
+ if ($@) {
+ printf STDERR "bumm %s\n", $@;
+ }
+ if (-f "/tmp/check_mysql_health_simulation/".$self->{mode}) {
+ my $simulation = do { local (@ARGV, $/) =
+ "/tmp/check_mysql_health_simulation/".$self->{mode}; <> };
+ @{$rows} = map { [ split(/\s+/, $_) ] } split(/\n/, $simulation);
+ }
+ return @{$rows};
+}
+
+sub func {
+ my $self = shift;
+ $self->{handle}->func(@_);
+}
+
+sub execute {
+ my $self = shift;
+ my $sql = shift;
+ eval {
+ my $sth = $self->{handle}->prepare($sql);
+ $sth->execute();
+ };
+ if ($@) {
+ printf STDERR "bumm %s\n", $@;
+ }
+}
+
+sub DESTROY {
+ my $self = shift;
+ #$self->trace(sprintf "disconnecting DBD %s",
+ # $self->{handle} ? "with handle" : "without handle");
+ #$self->{handle}->disconnect() if $self->{handle};
+}
+
+
+
+
+package DBD::MySQL::Cluster;
+
+use strict;
+use Time::HiRes;
+use IO::File;
+use Data::Dumper;
+
+
+{
+ our $verbose = 0;
+ our $scream = 0; # scream if something is not implemented
+ our $access = "dbi"; # how do we access the database.
+ our $my_modules_dyn_dir = ""; # where we look for self-written extensions
+
+ my @clusters = ();
+ my $initerrors = undef;
+
+ sub add_cluster {
+ push(@clusters, shift);
+ }
+
+ sub return_clusters {
+ return @clusters;
+ }
+
+ sub return_first_cluster() {
+ return $clusters[0];
+ }
+
+}
+
+sub new {
+ my $class = shift;
+ my %params = @_;
+ my $self = {
+ hostname => $params{hostname},
+ port => $params{port},
+ username => $params{username},
+ password => $params{password},
+ timeout => $params{timeout},
+ warningrange => $params{warningrange},
+ criticalrange => $params{criticalrange},
+ version => 'unknown',
+ nodes => [],
+ ndbd_nodes => 0,
+ ndb_mgmd_nodes => 0,
+ mysqld_nodes => 0,
+ };
+ bless $self, $class;
+ $self->init_nagios();
+ if ($self->connect(%params)) {
+ DBD::MySQL::Cluster::add_cluster($self);
+ $self->init(%params);
+ }
+ return $self;
+}
+
+sub init {
+ my $self = shift;
+ my %params = @_;
+ if ($self->{show}) {
+ my $type = undef;
+ foreach (split /\n/, $self->{show}) {
+ if (/\[(\w+)\((\w+)\)\]\s+(\d+) node/) {
+ $type = uc $2;
+ } elsif (/id=(\d+)(.*)/) {
+ push(@{$self->{nodes}}, DBD::MySQL::Cluster::Node->new(
+ type => $type,
+ id => $1,
+ status => $2,
+ ));
+ }
+ }
+ } else {
+ }
+ if ($params{mode} =~ /^cluster::ndbdrunning/) {
+ foreach my $node (@{$self->{nodes}}) {
+ $node->{type} eq "NDB" && $node->{status} eq "running" && $self->{ndbd_nodes}++;
+ $node->{type} eq "MGM" && $node->{status} eq "running" && $self->{ndb_mgmd_nodes}++;
+ $node->{type} eq "API" && $node->{status} eq "running" && $self->{mysqld_nodes}++;
+ }
+ } else {
+ printf "broken mode %s\n", $params{mode};
+ }
+}
+
+sub dump {
+ my $self = shift;
+ my $message = shift || "";
+ printf "%s %s\n", $message, Data::Dumper::Dumper($self);
+}
+
+sub nagios {
+ my $self = shift;
+ my %params = @_;
+ my $dead_ndb = 0;
+ my $dead_api = 0;
+ if (! $self->{nagios_level}) {
+ if ($params{mode} =~ /^cluster::ndbdrunning/) {
+ foreach my $node (grep { $_->{type} eq "NDB"} @{$self->{nodes}}) {
+ next if $params{selectname} && $params{selectname} ne $_->{id};
+ if (! $node->{connected}) {
+ $self->add_nagios_critical(
+ sprintf "ndb node %d is not connected", $node->{id});
+ $dead_ndb++;
+ }
+ }
+ foreach my $node (grep { $_->{type} eq "API"} @{$self->{nodes}}) {
+ next if $params{selectname} && $params{selectname} ne $_->{id};
+ if (! $node->{connected}) {
+ $self->add_nagios_critical(
+ sprintf "api node %d is not connected", $node->{id});
+ $dead_api++;
+ }
+ }
+ if (! $dead_ndb) {
+ $self->add_nagios_ok("all ndb nodes are connected");
+ }
+ if (! $dead_api) {
+ $self->add_nagios_ok("all api nodes are connected");
+ }
+ }
+ }
+ $self->add_perfdata(sprintf "ndbd_nodes=%d ndb_mgmd_nodes=%d mysqld_nodes=%d",
+ $self->{ndbd_nodes}, $self->{ndb_mgmd_nodes}, $self->{mysqld_nodes});
+}
+
+
+sub init_nagios {
+ my $self = shift;
+ no strict 'refs';
+ if (! ref($self)) {
+ my $nagiosvar = $self."::nagios";
+ my $nagioslevelvar = $self."::nagios_level";
+ $$nagiosvar = {
+ messages => {
+ 0 => [],
+ 1 => [],
+ 2 => [],
+ 3 => [],
+ },
+ perfdata => [],
+ };
+ $$nagioslevelvar = $ERRORS{OK},
+ } else {
+ $self->{nagios} = {
+ messages => {
+ 0 => [],
+ 1 => [],
+ 2 => [],
+ 3 => [],
+ },
+ perfdata => [],
+ };
+ $self->{nagios_level} = $ERRORS{OK},
+ }
+}
+
+sub check_thresholds {
+ my $self = shift;
+ my $value = shift;
+ my $defaultwarningrange = shift;
+ my $defaultcriticalrange = shift;
+ my $level = $ERRORS{OK};
+ $self->{warningrange} = $self->{warningrange} ?
+ $self->{warningrange} : $defaultwarningrange;
+ $self->{criticalrange} = $self->{criticalrange} ?
+ $self->{criticalrange} : $defaultcriticalrange;
+ if ($self->{warningrange} !~ /:/ && $self->{criticalrange} !~ /:/) {
+ # warning = 10, critical = 20, warn if > 10, crit if > 20
+ $level = $ERRORS{WARNING} if $value > $self->{warningrange};
+ $level = $ERRORS{CRITICAL} if $value > $self->{criticalrange};
+ } elsif ($self->{warningrange} =~ /([\d\.]+):/ &&
+ $self->{criticalrange} =~ /([\d\.]+):/) {
+ # warning = 98:, critical = 95:, warn if < 98, crit if < 95
+ $self->{warningrange} =~ /([\d\.]+):/;
+ $level = $ERRORS{WARNING} if $value < $1;
+ $self->{criticalrange} =~ /([\d\.]+):/;
+ $level = $ERRORS{CRITICAL} if $value < $1;
+ }
+ return $level;
+ #
+ # syntax error must be reported with returncode -1
+ #
+}
+
+sub add_nagios {
+ my $self = shift;
+ my $level = shift;
+ my $message = shift;
+ push(@{$self->{nagios}->{messages}->{$level}}, $message);
+ # recalc current level
+ foreach my $llevel qw(CRITICAL WARNING UNKNOWN OK) {
+ if (scalar(@{$self->{nagios}->{messages}->{$ERRORS{$llevel}}})) {
+ $self->{nagios_level} = $ERRORS{$llevel};
+ }
+ }
+}
+
+sub add_nagios_ok {
+ my $self = shift;
+ my $message = shift;
+ $self->add_nagios($ERRORS{OK}, $message);
+}
+
+sub add_nagios_warning {
+ my $self = shift;
+ my $message = shift;
+ $self->add_nagios($ERRORS{WARNING}, $message);
+}
+
+sub add_nagios_critical {
+ my $self = shift;
+ my $message = shift;
+ $self->add_nagios($ERRORS{CRITICAL}, $message);
+}
+
+sub add_nagios_unknown {
+ my $self = shift;
+ my $message = shift;
+ $self->add_nagios($ERRORS{UNKNOWN}, $message);
+}
+
+sub add_perfdata {
+ my $self = shift;
+ my $data = shift;
+ push(@{$self->{nagios}->{perfdata}}, $data);
+}
+
+sub merge_nagios {
+ my $self = shift;
+ my $child = shift;
+ foreach my $level (0..3) {
+ foreach (@{$child->{nagios}->{messages}->{$level}}) {
+ $self->add_nagios($level, $_);
+ }
+ #push(@{$self->{nagios}->{messages}->{$level}},
+ # @{$child->{nagios}->{messages}->{$level}});
+ }
+ push(@{$self->{nagios}->{perfdata}}, @{$child->{nagios}->{perfdata}});
+}
+
+
+sub calculate_result {
+ my $self = shift;
+ if ($ENV{NRPE_MULTILINESUPPORT} &&
+ length join(" ", @{$self->{nagios}->{perfdata}}) > 200) {
+ foreach my $level ("CRITICAL", "WARNING", "UNKNOWN", "OK") {
+ # first the bad news
+ if (scalar(@{$self->{nagios}->{messages}->{$ERRORS{$level}}})) {
+ $self->{nagios_message} .=
+ "\n".join("\n", @{$self->{nagios}->{messages}->{$ERRORS{$level}}});
+ }
+ }
+ $self->{nagios_message} =~ s/^\n//g;
+ $self->{perfdata} = join("\n", @{$self->{nagios}->{perfdata}});
+ } else {
+ foreach my $level ("CRITICAL", "WARNING", "UNKNOWN", "OK") {
+ # first the bad news
+ if (scalar(@{$self->{nagios}->{messages}->{$ERRORS{$level}}})) {
+ $self->{nagios_message} .=
+ join(", ", @{$self->{nagios}->{messages}->{$ERRORS{$level}}}).", ";
+ }
+ }
+ $self->{nagios_message} =~ s/, $//g;
+ $self->{perfdata} = join(" ", @{$self->{nagios}->{perfdata}});
+ }
+ foreach my $level ("OK", "UNKNOWN", "WARNING", "CRITICAL") {
+ if (scalar(@{$self->{nagios}->{messages}->{$ERRORS{$level}}})) {
+ $self->{nagios_level} = $ERRORS{$level};
+ }
+ }
+}
+
+sub debug {
+ my $self = shift;
+ my $msg = shift;
+ if ($DBD::MySQL::Cluster::verbose) {
+ printf "%s %s\n", $msg, ref($self);
+ }
+}
+
+sub connect {
+ my $self = shift;
+ my %params = @_;
+ my $retval = undef;
+ $self->{tic} = Time::HiRes::time();
+ eval {
+ use POSIX ':signal_h';
+ local $SIG{'ALRM'} = sub {
+ die "alarm\n";
+ };
+ my $mask = POSIX::SigSet->new( SIGALRM );
+ my $action = POSIX::SigAction->new(
+ sub { die "connection timeout\n" ; }, $mask);
+ my $oldaction = POSIX::SigAction->new();
+ sigaction(SIGALRM ,$action ,$oldaction );
+ alarm($self->{timeout} - 1); # 1 second before the global unknown timeout
+ my $ndb_mgm = "ndb_mgm";
+ $params{hostname} = "127.0.0.1" if ! $params{hostname};
+ $ndb_mgm .= sprintf " --ndb-connectstring=%s", $params{hostname}
+ if $params{hostname};
+ $ndb_mgm .= sprintf ":%d", $params{port}
+ if $params{port};
+ $self->{show} = `$ndb_mgm -e show 2>&1`;
+ if ($? == -1) {
+ $self->add_nagios_critical("ndb_mgm failed to execute $!");
+ } elsif ($? & 127) {
+ $self->add_nagios_critical("ndb_mgm failed to execute $!");
+ } elsif ($? >> 8 != 0) {
+ $self->add_nagios_critical("ndb_mgm unable to connect");
+ } else {
+ if ($self->{show} !~ /Cluster Configuration/) {
+ $self->add_nagios_critical("got no cluster configuration");
+ } else {
+ $retval = 1;
+ }
+ }
+ };
+ if ($@) {
+ $self->{errstr} = $@;
+ $self->{errstr} =~ s/at $0 .*//g;
+ chomp $self->{errstr};
+ $self->add_nagios_critical($self->{errstr});
+ $retval = undef;
+ }
+ $self->{tac} = Time::HiRes::time();
+ return $retval;
+}
+
+sub trace {
+ my $self = shift;
+ my $format = shift;
+ $self->{trace} = -f "/tmp/check_mysql_health.trace" ? 1 : 0;
+ if ($self->{verbose}) {
+ printf("%s: ", scalar localtime);
+ printf($format, @_);
+ }
+ if ($self->{trace}) {
+ my $logfh = new IO::File;
+ $logfh->autoflush(1);
+ if ($logfh->open("/tmp/check_mysql_health.trace", "a")) {
+ $logfh->printf("%s: ", scalar localtime);
+ $logfh->printf($format, @_);
+ $logfh->printf("\n");
+ $logfh->close();
+ }
+ }
+}
+
+sub DESTROY {
+ my $self = shift;
+ my $handle1 = "null";
+ my $handle2 = "null";
+ if (defined $self->{handle}) {
+ $handle1 = ref($self->{handle});
+ if (defined $self->{handle}->{handle}) {
+ $handle2 = ref($self->{handle}->{handle});
+ }
+ }
+ $self->trace(sprintf "DESTROY %s with handle %s %s", ref($self), $handle1, $handle2);
+ if (ref($self) eq "DBD::MySQL::Cluster") {
+ }
+ $self->trace(sprintf "DESTROY %s exit with handle %s %s", ref($self), $handle1, $handle2);
+ if (ref($self) eq "DBD::MySQL::Cluster") {
+ #printf "humpftata\n";
+ }
+}
+
+sub save_state {
+ my $self = shift;
+ my %params = @_;
+ my $extension = "";
+ mkdir $params{statefilesdir} unless -d $params{statefilesdir};
+ my $statefile = sprintf "%s/%s_%s",
+ $params{statefilesdir}, $params{hostname}, $params{mode};
+ $extension .= $params{differenciator} ? "_".$params{differenciator} : "";
+ $extension .= $params{socket} ? "_".$params{socket} : "";
+ $extension .= $params{port} ? "_".$params{port} : "";
+ $extension .= $params{database} ? "_".$params{database} : "";
+ $extension .= $params{tablespace} ? "_".$params{tablespace} : "";
+ $extension .= $params{datafile} ? "_".$params{datafile} : "";
+ $extension .= $params{name} ? "_".$params{name} : "";
+ $extension =~ s/\//_/g;
+ $extension =~ s/\(/_/g;
+ $extension =~ s/\)/_/g;
+ $extension =~ s/\*/_/g;
+ $extension =~ s/\s/_/g;
+ $statefile .= $extension;
+ $statefile = lc $statefile;
+ open(STATE, ">$statefile");
+ if ((ref($params{save}) eq "HASH") && exists $params{save}->{timestamp}) {
+ $params{save}->{localtime} = scalar localtime $params{save}->{timestamp};
+ }
+ printf STATE Data::Dumper::Dumper($params{save});
+ close STATE;
+ $self->debug(sprintf "saved %s to %s",
+ Data::Dumper::Dumper($params{save}), $statefile);
+}
+
+sub load_state {
+ my $self = shift;
+ my %params = @_;
+ my $extension = "";
+ my $statefile = sprintf "%s/%s_%s",
+ $params{statefilesdir}, $params{hostname}, $params{mode};
+ $extension .= $params{differenciator} ? "_".$params{differenciator} : "";
+ $extension .= $params{socket} ? "_".$params{socket} : "";
+ $extension .= $params{port} ? "_".$params{port} : "";
+ $extension .= $params{database} ? "_".$params{database} : "";
+ $extension .= $params{tablespace} ? "_".$params{tablespace} : "";
+ $extension .= $params{datafile} ? "_".$params{datafile} : "";
+ $extension .= $params{name} ? "_".$params{name} : "";
+ $extension =~ s/\//_/g;
+ $extension =~ s/\(/_/g;
+ $extension =~ s/\)/_/g;
+ $extension =~ s/\*/_/g;
+ $extension =~ s/\s/_/g;
+ $statefile .= $extension;
+ $statefile = lc $statefile;
+ if ( -f $statefile) {
+ our $VAR1;
+ eval {
+ require $statefile;
+ };
+ if($@) {
+printf "rumms\n";
+ }
+ $self->debug(sprintf "load %s", Data::Dumper::Dumper($VAR1));
+ return $VAR1;
+ } else {
+ return undef;
+ }
+}
+
+sub valdiff {
+ my $self = shift;
+ my $pparams = shift;
+ my %params = %{$pparams};
+ my @keys = @_;
+ my $last_values = $self->load_state(%params) || eval {
+ my $empty_events = {};
+ foreach (@keys) {
+ $empty_events->{$_} = 0;
+ }
+ $empty_events->{timestamp} = 0;
+ $empty_events;
+ };
+ foreach (@keys) {
+ $self->{'delta_'.$_} = $self->{$_} - $last_values->{$_};
+ $self->debug(sprintf "delta_%s %f", $_, $self->{'delta_'.$_});
+ }
+ $self->{'delta_timestamp'} = time - $last_values->{timestamp};
+ $params{save} = eval {
+ my $empty_events = {};
+ foreach (@keys) {
+ $empty_events->{$_} = $self->{$_};
+ }
+ $empty_events->{timestamp} = time;
+ $empty_events;
+ };
+ $self->save_state(%params);
+}
+
+sub requires_version {
+ my $self = shift;
+ my $version = shift;
+ my @instances = DBD::MySQL::Cluster::return_clusters();
+ my $instversion = $instances[0]->{version};
+ if (! $self->version_is_minimum($version)) {
+ $self->add_nagios($ERRORS{UNKNOWN},
+ sprintf "not implemented/possible for MySQL release %s", $instversion);
+ }
+}
+
+sub version_is_minimum {
+ # the current version is newer or equal
+ my $self = shift;
+ my $version = shift;
+ my $newer = 1;
+ my @instances = DBD::MySQL::Cluster::return_clusters();
+ my @v1 = map { $_ eq "x" ? 0 : $_ } split(/\./, $version);
+ my @v2 = split(/\./, $instances[0]->{version});
+ if (scalar(@v1) > scalar(@v2)) {
+ push(@v2, (0) x (scalar(@v1) - scalar(@v2)));
+ } elsif (scalar(@v2) > scalar(@v1)) {
+ push(@v1, (0) x (scalar(@v2) - scalar(@v1)));
+ }
+ foreach my $pos (0..$#v1) {
+ if ($v2[$pos] > $v1[$pos]) {
+ $newer = 1;
+ last;
+ } elsif ($v2[$pos] < $v1[$pos]) {
+ $newer = 0;
+ last;
+ }
+ }
+ #printf STDERR "check if %s os minimum %s\n", join(".", @v2), join(".", @v1);
+ return $newer;
+}
+
+sub instance_rac {
+ my $self = shift;
+ my @instances = DBD::MySQL::Cluster::return_clusters();
+ return (lc $instances[0]->{parallel} eq "yes") ? 1 : 0;
+}
+
+sub instance_thread {
+ my $self = shift;
+ my @instances = DBD::MySQL::Cluster::return_clusters();
+ return $instances[0]->{thread};
+}
+
+sub windows_cluster {
+ my $self = shift;
+ my @instances = DBD::MySQL::Cluster::return_clusters();
+ if ($instances[0]->{os} =~ /Win/i) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+sub system_vartmpdir {
+ my $self = shift;
+ if ($^O =~ /MSWin/) {
+ return $self->system_tmpdir();
+ } else {
+ return "/var/tmp/check_mysql_health";
+ }
+}
+
+sub system_oldvartmpdir {
+ my $self = shift;
+ return "/tmp";
+}
+
+sub system_tmpdir {
+ my $self = shift;
+ if ($^O =~ /MSWin/) {
+ return $ENV{TEMP} if defined $ENV{TEMP};
+ return $ENV{TMP} if defined $ENV{TMP};
+ return File::Spec->catfile($ENV{windir}, 'Temp')
+ if defined $ENV{windir};
+ return 'C:\Temp';
+ } else {
+ return "/tmp";
+ }
+}
+
+
+package DBD::MySQL::Cluster::Node;
+
+use strict;
+
+our @ISA = qw(DBD::MySQL::Cluster);
+
+
+sub new {
+ my $class = shift;
+ my %params = @_;
+ my $self = {
+ mode => $params{mode},
+ timeout => $params{timeout},
+ type => $params{type},
+ id => $params{id},
+ status => $params{status},
+ };
+ bless $self, $class;
+ $self->init(%params);
+ if ($params{type} eq "NDB") {
+ bless $self, "DBD::MySQL::Cluster::Node::NDB";
+ $self->init(%params);
+ }
+ return $self;
+}
+
+sub init {
+ my $self = shift;
+ my %params = @_;
+ if ($self->{status} =~ /@(\d+\.\d+\.\d+\.\d+)\s/) {
+ $self->{addr} = $1;
+ $self->{connected} = 1;
+ } elsif ($self->{status} =~ /accepting connect from (\d+\.\d+\.\d+\.\d+)/) {
+ $self->{addr} = $1;
+ $self->{connected} = 0;
+ }
+ if ($self->{status} =~ /starting,/) {
+ $self->{status} = "starting";
+ } elsif ($self->{status} =~ /shutting,/) {
+ $self->{status} = "shutting";
+ } else {
+ $self->{status} = $self->{connected} ? "running" : "dead";
+ }
+}
+
+
+package DBD::MySQL::Cluster::Node::NDB;
+
+use strict;
+
+our @ISA = qw(DBD::MySQL::Cluster::Node);
+
+
+sub init {
+ my $self = shift;
+ my %params = @_;
+ if ($self->{status} =~ /Nodegroup:\s*(\d+)/) {
+ $self->{nodegroup} = $1;
+ }
+ $self->{master} = ($self->{status} =~ /Master\)/) ? 1 : 0;
+}
+
+
+package Extraopts;
+
+use strict;
+use File::Basename;
+use Data::Dumper;
+
+sub new {
+ my $class = shift;
+ my %params = @_;
+ my $self = {
+ file => $params{file},
+ commandline => $params{commandline},
+ config => {},
+ section => 'default_no_section',
+ };
+ bless $self, $class;
+ $self->prepare_file_and_section();
+ $self->init();
+ return $self;
+}
+
+sub prepare_file_and_section {
+ my $self = shift;
+ if (! defined $self->{file}) {
+ # ./check_stuff --extra-opts
+ $self->{section} = basename($0);
+ $self->{file} = $self->get_default_file();
+ } elsif ($self->{file} =~ /^[^@]+$/) {
+ # ./check_stuff --extra-opts=special_opts
+ $self->{section} = $self->{file};
+ $self->{file} = $self->get_default_file();
+ } elsif ($self->{file} =~ /^@(.*)/) {
+ # ./check_stuff --extra-opts=@/etc/myconfig.ini
+ $self->{section} = basename($0);
+ $self->{file} = $1;
+ } elsif ($self->{file} =~ /^(.*?)@(.*)/) {
+ # ./check_stuff --extra-opts=special_opts@/etc/myconfig.ini
+ $self->{section} = $1;
+ $self->{file} = $2;
+ }
+}
+
+sub get_default_file {
+ my $self = shift;
+ foreach my $default (qw(/etc/nagios/plugins.ini
+ /usr/local/nagios/etc/plugins.ini
+ /usr/local/etc/nagios/plugins.ini
+ /etc/opt/nagios/plugins.ini
+ /etc/nagios-plugins.ini
+ /usr/local/etc/nagios-plugins.ini
+ /etc/opt/nagios-plugins.ini)) {
+ if (-f $default) {
+ return $default;
+ }
+ }
+ return undef;
+}
+
+sub init {
+ my $self = shift;
+ if (! defined $self->{file}) {
+ $self->{errors} = sprintf 'no extra-opts file specified and no default file found';
+ } elsif (! -f $self->{file}) {
+ $self->{errors} = sprintf 'could not open %s', $self->{file};
+ } else {
+ my $data = do { local (@ARGV, $/) = $self->{file}; <> };
+ my $in_section = 'default_no_section';
+ foreach my $line (split(/\n/, $data)) {
+ if ($line =~ /\[(.*)\]/) {
+ $in_section = $1;
+ } elsif ($line =~ /(.*?)\s*=\s*(.*)/) {
+ $self->{config}->{$in_section}->{$1} = $2;
+ }
+ }
+ }
+}
+
+sub is_valid {
+ my $self = shift;
+ return ! exists $self->{errors};
+}
+
+sub overwrite {
+ my $self = shift;
+ my %commandline = ();
+ if (scalar(keys %{$self->{config}->{default_no_section}}) > 0) {
+ foreach (keys %{$self->{config}->{default_no_section}}) {
+ $commandline{$_} = $self->{config}->{default_no_section}->{$_};
+ }
+ }
+ if (exists $self->{config}->{$self->{section}}) {
+ foreach (keys %{$self->{config}->{$self->{section}}}) {
+ $commandline{$_} = $self->{config}->{$self->{section}}->{$_};
+ }
+ }
+ foreach (keys %commandline) {
+ if (! exists $self->{commandline}->{$_}) {
+ $self->{commandline}->{$_} = $commandline{$_};
+ }
+ }
+}
+
+
+
+package main;
+
+use strict;
+use Getopt::Long qw(:config no_ignore_case);
+use File::Basename;
+use lib dirname($0);
+
+
+
+use vars qw ($PROGNAME $REVISION $CONTACT $TIMEOUT $STATEFILESDIR $needs_restart %commandline);
+
+$PROGNAME = "check_mysql_health";
+$REVISION = '$Revision: 2.1.7 $';
+$CONTACT = 'gerhard.lausser@consol.de';
+$TIMEOUT = 60;
+$STATEFILESDIR = '/var/tmp/check_mysql_health';
+$needs_restart = 0;
+
+my @modes = (
+ ['server::connectiontime',
+ 'connection-time', undef,
+ 'Time to connect to the server' ],
+ ['server::uptime',
+ 'uptime', undef,
+ 'Time the server is running' ],
+ ['server::instance::connectedthreads',
+ 'threads-connected', undef,
+ 'Number of currently open connections' ],
+ ['server::instance::threadcachehitrate',
+ 'threadcache-hitrate', undef,
+ 'Hit rate of the thread-cache' ],
+ ['server::instance::createdthreads',
+ 'threads-created', undef,
+ 'Number of threads created per sec' ],
+ ['server::instance::runningthreads',
+ 'threads-running', undef,
+ 'Number of currently running threads' ],
+ ['server::instance::cachedthreads',
+ 'threads-cached', undef,
+ 'Number of currently cached threads' ],
+ ['server::instance::abortedconnects',
+ 'connects-aborted', undef,
+ 'Number of aborted connections per sec' ],
+ ['server::instance::abortedclients',
+ 'clients-aborted', undef,
+ 'Number of aborted connections (because the client died) per sec' ],
+ ['server::instance::replication::slavelag',
+ 'slave-lag', ['replication-slave-lag'],
+ 'Seconds behind master' ],
+ ['server::instance::replication::slaveiorunning',
+ 'slave-io-running', ['replication-slave-io-running'],
+ 'Slave io running: Yes' ],
+ ['server::instance::replication::slavesqlrunning',
+ 'slave-sql-running', ['replication-slave-sql-running'],
+ 'Slave sql running: Yes' ],
+ ['server::instance::querycachehitrate',
+ 'qcache-hitrate', ['querycache-hitrate'],
+ 'Query cache hitrate' ],
+ ['server::instance::querycachelowmemprunes',
+ 'qcache-lowmem-prunes', ['querycache-lowmem-prunes'],
+ 'Query cache entries pruned because of low memory' ],
+ ['server::instance::myisam::keycache::hitrate',
+ 'keycache-hitrate', ['myisam-keycache-hitrate'],
+ 'MyISAM key cache hitrate' ],
+ ['server::instance::innodb::bufferpool::hitrate',
+ 'bufferpool-hitrate', ['innodb-bufferpool-hitrate'],
+ 'InnoDB buffer pool hitrate' ],
+ ['server::instance::innodb::bufferpool::waitfree',
+ 'bufferpool-wait-free', ['innodb-bufferpool-wait-free'],
+ 'InnoDB buffer pool waits for clean page available' ],
+ ['server::instance::innodb::logwaits',
+ 'log-waits', ['innodb-log-waits'],
+ 'InnoDB log waits because of a too small log buffer' ],
+ ['server::instance::tablecachehitrate',
+ 'tablecache-hitrate', undef,
+ 'Table cache hitrate' ],
+ ['server::instance::tablelockcontention',
+ 'table-lock-contention', undef,
+ 'Table lock contention' ],
+ ['server::instance::tableindexusage',
+ 'index-usage', undef,
+ 'Usage of indices' ],
+ ['server::instance::tabletmpondisk',
+ 'tmp-disk-tables', undef,
+ 'Percent of temp tables created on disk' ],
+ ['server::instance::needoptimize',
+ 'table-fragmentation', undef,
+ 'Show tables which should be optimized' ],
+ ['server::instance::openfiles',
+ 'open-files', undef,
+ 'Percent of opened files' ],
+ ['server::instance::slowqueries',
+ 'slow-queries', undef,
+ 'Slow queries' ],
+ ['server::instance::longprocs',
+ 'long-running-procs', undef,
+ 'long running processes' ],
+ ['cluster::ndbdrunning',
+ 'cluster-ndbd-running', undef,
+ 'ndnd nodes are up and running' ],
+ ['server::sql',
+ 'sql', undef,
+ 'any sql command returning a single number' ],
+);
+
+# rrd data store names are limited to 19 characters
+my %labels = (
+ bufferpool_hitrate => {
+ groundwork => 'bp_hitrate',
+ },
+ bufferpool_hitrate_now => {
+ groundwork => 'bp_hitrate_now',
+ },
+ bufferpool_free_waits_rate => {
+ groundwork => 'bp_freewaits',
+ },
+ innodb_log_waits_rate => {
+ groundwork => 'inno_log_waits',
+ },
+ keycache_hitrate => {
+ groundwork => 'kc_hitrate',
+ },
+ keycache_hitrate_now => {
+ groundwork => 'kc_hitrate_now',
+ },
+ threads_created_per_sec => {
+ groundwork => 'thrds_creat_per_s',
+ },
+ connects_aborted_per_sec => {
+ groundwork => 'conn_abrt_per_s',
+ },
+ clients_aborted_per_sec => {
+ groundwork => 'clnt_abrt_per_s',
+ },
+ thread_cache_hitrate => {
+ groundwork => 'tc_hitrate',
+ },
+ thread_cache_hitrate_now => {
+ groundwork => 'tc_hitrate_now',
+ },
+ qcache_lowmem_prunes_rate => {
+ groundwork => 'qc_lowm_prnsrate',
+ },
+ slow_queries_rate => {
+ groundwork => 'slow_q_rate',
+ },
+ tablecache_hitrate => {
+ groundwork => 'tac_hitrate',
+ },
+ tablecache_fillrate => {
+ groundwork => 'tac_fillrate',
+ },
+ tablelock_contention => {
+ groundwork => 'tl_contention',
+ },
+ tablelock_contention_now => {
+ groundwork => 'tl_contention_now',
+ },
+ pct_tmp_table_on_disk => {
+ groundwork => 'tmptab_on_disk',
+ },
+ pct_tmp_table_on_disk_now => {
+ groundwork => 'tmptab_on_disk_now',
+ },
+);
+
+sub print_usage () {
+ print <<EOUS;
+ Usage:
+ $PROGNAME [-v] [-t <timeout>] [[--hostname <hostname>]
+ [--port <port> | --socket <socket>]
+ --username <username> --password <password>] --mode <mode>
+ [--method mysql]
+ $PROGNAME [-h | --help]
+ $PROGNAME [-V | --version]
+
+ Options:
+ --hostname
+ the database server's hostname
+ --port
+ the database's port. (default: 3306)
+ --socket
+ the database's unix socket.
+ --username
+ the mysql db user
+ --password
+ the mysql db user's password
+ --database
+ the database's name. (default: information_schema)
+ --warning
+ the warning range
+ --critical
+ the critical range
+ --mode
+ the mode of the plugin. select one of the following keywords:
+EOUS
+ my $longest = length ((reverse sort {length $a <=> length $b} map { $_->[1] } @modes)[0]);
+ my $format = " %-".
+ (length ((reverse sort {length $a <=> length $b} map { $_->[1] } @modes)[0])).
+ "s\t(%s)\n";
+ foreach (@modes) {
+ printf $format, $_->[1], $_->[3];
+ }
+ printf "\n";
+ print <<EOUS;
+ --name
+ the name of something that needs to be further specified,
+ currently only used for sql statements
+ --name2
+ if name is a sql statement, this statement would appear in
+ the output and the performance data. This can be ugly, so
+ name2 can be used to appear instead.
+ --regexp
+ if this parameter is used, name will be interpreted as a
+ regular expression.
+ --units
+ one of %, KB, MB, GB. This is used for a better output of mode=sql
+ and for specifying thresholds for mode=tablespace-free
+ --labelformat
+ one of pnp4nagios (which is the default) or groundwork.
+ It is used to shorten performance data labels to 19 characters.
+
+ In mode sql you can url-encode the statement so you will not have to mess
+ around with special characters in your Nagios service definitions.
+ Instead of
+ --name="select count(*) from v\$session where status = 'ACTIVE'"
+ you can say
+ --name=select%20count%28%2A%29%20from%20v%24session%20where%20status%20%3D%20%27ACTIVE%27
+ For your convenience you can call check_mysql_health with the --mode encode
+ option and it will encode the standard input.
+
+ You can find the full documentation at
+ http://www.consol.de/opensource/nagios/check-mysql-health
+
+EOUS
+
+}
+
+sub print_help () {
+ print "Copyright (c) 2009 Gerhard Lausser\n\n";
+ print "\n";
+ print " Check various parameters of MySQL databases \n";
+ print "\n";
+ print_usage();
+ support();
+}
+
+
+sub print_revision ($$) {
+ my $commandName = shift;
+ my $pluginRevision = shift;
+ $pluginRevision =~ s/^\$Revision: //;
+ $pluginRevision =~ s/ \$\s*$//;
+ print "$commandName ($pluginRevision)\n";
+ print "This nagios plugin comes with ABSOLUTELY NO WARRANTY. You may redistribute\ncopies of this plugin under the terms of the GNU General Public License.\n";
+}
+
+sub support () {
+ my $support='Send email to gerhard.lausser@consol.de if you have questions\nregarding use of this software. \nPlease include version information with all correspondence (when possible,\nuse output from the --version option of the plugin itself).\n';
+ $support =~ s/@/\@/g;
+ $support =~ s/\\n/\n/g;
+ print $support;
+}
+
+sub contact_author ($$) {
+ my $item = shift;
+ my $strangepattern = shift;
+ if ($commandline{verbose}) {
+ printf STDERR
+ "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n".
+ "You found a line which is not recognized by %s\n".
+ "This means, certain components of your system cannot be checked.\n".
+ "Please contact the author %s and\nsend him the following output:\n\n".
+ "%s /%s/\n\nThank you!\n".
+ "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n",
+ $PROGNAME, $CONTACT, $item, $strangepattern;
+ }
+}
+
+%commandline = ();
+my @params = (
+ "timeout|t=i",
+ "version|V",
+ "help|h",
+ "verbose|v",
+ "debug|d",
+ "hostname|H=s",
+ "database=s",
+ "port|P=s",
+ "socket|S=s",
+ "username|u=s",
+ "password|p=s",
+ "mode|m=s",
+ "name=s",
+ "name2=s",
+ "regexp",
+ "perfdata",
+ "warning=s",
+ "critical=s",
+ "dbthresholds:s",
+ "absolute|a",
+ "environment|e=s%",
+ "method=s",
+ "runas|r=s",
+ "scream",
+ "shell",
+ "eyecandy",
+ "encode",
+ "units=s",
+ "lookback=i",
+ "3",
+ "statefilesdir=s",
+ "with-mymodules-dyn-dir=s",
+ "report=s",
+ "labelformat=s",
+ "extra-opts:s");
+
+if (! GetOptions(\%commandline, @params)) {
+ print_help();
+ exit $ERRORS{UNKNOWN};
+}
+
+if (exists $commandline{'extra-opts'}) {
+ # read the extra file and overwrite other parameters
+ my $extras = Extraopts->new(file => $commandline{'extra-opts'}, commandline =>
+ \%commandline);
+ if (! $extras->is_valid()) {
+ printf "extra-opts are not valid: %s\n", $extras->{errors};
+ exit $ERRORS{UNKNOWN};
+ } else {
+ $extras->overwrite();
+ }
+}
+
+if (exists $commandline{version}) {
+ print_revision($PROGNAME, $REVISION);
+ exit $ERRORS{OK};
+}
+
+if (exists $commandline{help}) {
+ print_help();
+ exit $ERRORS{OK};
+} elsif (! exists $commandline{mode}) {
+ printf "Please select a mode\n";
+ print_help();
+ exit $ERRORS{OK};
+}
+
+if ($commandline{mode} eq "encode") {
+ my $input = <>;
+ chomp $input;
+ $input =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg;
+ printf "%s\n", $input;
+ exit $ERRORS{OK};
+}
+
+if (exists $commandline{3}) {
+ $ENV{NRPE_MULTILINESUPPORT} = 1;
+}
+
+if (exists $commandline{timeout}) {
+ $TIMEOUT = $commandline{timeout};
+}
+
+if (exists $commandline{verbose}) {
+ $DBD::MySQL::Server::verbose = exists $commandline{verbose};
+}
+
+if (exists $commandline{scream}) {
+# $DBD::MySQL::Server::hysterical = exists $commandline{scream};
+}
+
+if (exists $commandline{method}) {
+ # snmp or mysql cmdline
+} else {
+ $commandline{method} = "dbi";
+}
+
+if (exists $commandline{report}) {
+ # short, long, html
+} else {
+ $commandline{report} = "long";
+}
+
+if (exists $commandline{labelformat}) {
+ # groundwork
+} else {
+ $commandline{labelformat} = "pnp4nagios";
+}
+
+if (exists $commandline{'with-mymodules-dyn-dir'}) {
+ $DBD::MySQL::Server::my_modules_dyn_dir = $commandline{'with-mymodules-dyn-dir'};
+} else {
+ $DBD::MySQL::Server::my_modules_dyn_dir = '/usr/local/nagios/libexec';
+}
+
+if (exists $commandline{environment}) {
+ # if the desired environment variable values are different from
+ # the environment of this running script, then a restart is necessary.
+ # because setting $ENV does _not_ change the environment of the running script.
+ foreach (keys %{$commandline{environment}}) {
+ if ((! $ENV{$_}) || ($ENV{$_} ne $commandline{environment}->{$_})) {
+ $needs_restart = 1;
+ $ENV{$_} = $commandline{environment}->{$_};
+ printf STDERR "new %s=%s forces restart\n", $_, $ENV{$_}
+ if $DBD::MySQL::Server::verbose;
+ }
+ }
+ # e.g. called with --runas dbnagio. shlib_path environment variable is stripped
+ # during the sudo.
+ # so the perl interpreter starts without a shlib_path. but --runas cares for
+ # a --environment shlib_path=...
+ # so setting the environment variable in the code above and restarting the
+ # perl interpreter will help it find shared libs
+}
+
+if (exists $commandline{runas}) {
+ # remove the runas parameter
+ # exec sudo $0 ... the remaining parameters
+ $needs_restart = 1;
+ # if the calling script has a path for shared libs and there is no --environment
+ # parameter then the called script surely needs the variable too.
+ foreach my $important_env qw(LD_LIBRARY_PATH SHLIB_PATH
+ ORACLE_HOME TNS_ADMIN ORA_NLS ORA_NLS33 ORA_NLS10) {
+ if ($ENV{$important_env} && ! scalar(grep { /^$important_env=/ }
+ keys %{$commandline{environment}})) {
+ $commandline{environment}->{$important_env} = $ENV{$important_env};
+ printf STDERR "add important --environment %s=%s\n",
+ $important_env, $ENV{$important_env} if $DBD::MySQL::Server::verbose;
+ }
+ }
+}
+
+if ($needs_restart) {
+ my @newargv = ();
+ my $runas = undef;
+ if (exists $commandline{runas}) {
+ $runas = $commandline{runas};
+ delete $commandline{runas};
+ }
+ foreach my $option (keys %commandline) {
+ if (grep { /^$option/ && /=/ } @params) {
+ if (ref ($commandline{$option}) eq "HASH") {
+ foreach (keys %{$commandline{$option}}) {
+ push(@newargv, sprintf "--%s", $option);
+ push(@newargv, sprintf "%s=%s", $_, $commandline{$option}->{$_});
+ }
+ } else {
+ push(@newargv, sprintf "--%s", $option);
+ push(@newargv, sprintf "%s", $commandline{$option});
+ }
+ } else {
+ push(@newargv, sprintf "--%s", $option);
+ }
+ }
+ if ($runas) {
+ exec "sudo", "-S", "-u", $runas, $0, @newargv;
+ } else {
+ exec $0, @newargv;
+ # this makes sure that even a SHLIB or LD_LIBRARY_PATH are set correctly
+ # when the perl interpreter starts. Setting them during runtime does not
+ # help loading e.g. libclntsh.so
+ }
+ exit;
+}
+
+if (exists $commandline{shell}) {
+ # forget what you see here.
+ system("/bin/sh");
+}
+
+if (! exists $commandline{statefilesdir}) {
+ if (exists $ENV{OMD_ROOT}) {
+ $commandline{statefilesdir} = $ENV{OMD_ROOT}."/var/tmp/check_mysql_health";
+ } else {
+ $commandline{statefilesdir} = $STATEFILESDIR;
+ }
+}
+
+if (exists $commandline{name}) {
+ # objects can be encoded like an url
+ # with s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg;
+ if (($commandline{mode} ne "sql") ||
+ (($commandline{mode} eq "sql") &&
+ ($commandline{name} =~ /select%20/i))) { # protect ... like '%cac%' ... from decoding
+ $commandline{name} =~ s/\%([A-Fa-f0-9]{2})/pack('C', hex($1))/seg;
+ }
+ if ($commandline{name} =~ /^0$/) {
+ # without this, $params{selectname} would be treated like undef
+ $commandline{name} = "00";
+ }
+}
+
+$SIG{'ALRM'} = sub {
+ printf "UNKNOWN - %s timed out after %d seconds\n", $PROGNAME, $TIMEOUT;
+ exit $ERRORS{UNKNOWN};
+};
+alarm($TIMEOUT);
+
+my $nagios_level = $ERRORS{UNKNOWN};
+my $nagios_message = "";
+my $perfdata = "";
+if ($commandline{mode} =~ /^my-([^\-.]+)/) {
+ my $param = $commandline{mode};
+ $param =~ s/\-/::/g;
+ push(@modes, [$param, $commandline{mode}, undef, 'my extension']);
+} elsif ((! grep { $commandline{mode} eq $_ } map { $_->[1] } @modes) &&
+ (! grep { $commandline{mode} eq $_ } map { defined $_->[2] ? @{$_->[2]} : () } @modes)) {
+ printf "UNKNOWN - mode %s\n", $commandline{mode};
+ print_usage();
+ exit 3;
+}
+
+my %params = (
+ timeout => $TIMEOUT,
+ mode => (
+ map { $_->[0] }
+ grep {
+ ($commandline{mode} eq $_->[1]) ||
+ ( defined $_->[2] && grep { $commandline{mode} eq $_ } @{$_->[2]})
+ } @modes
+ )[0],
+ cmdlinemode => $commandline{mode},
+ method => $commandline{method} ||
+ $ENV{NAGIOS__SERVICEMYSQL_METH} ||
+ $ENV{NAGIOS__HOSTMYSQL_METH} || 'dbi',
+ hostname => $commandline{hostname} ||
+ $ENV{NAGIOS__SERVICEMYSQL_HOST} ||
+ $ENV{NAGIOS__HOSTMYSQL_HOST} || 'localhost',
+ database => $commandline{database} ||
+ $ENV{NAGIOS__SERVICEMYSQL_DATABASE} ||
+ $ENV{NAGIOS__HOSTMYSQL_DATABASE} || 'information_schema',
+ port => $commandline{port} || (($commandline{mode} =~ /^cluster/) ?
+ ($ENV{NAGIOS__SERVICENDBMGM_PORT} || $ENV{NAGIOS__HOSTNDBMGM_PORT} || 1186) :
+ ($ENV{NAGIOS__SERVICEMYSQL_PORT} || $ENV{NAGIOS__HOSTMYSQL_PORT} || 3306)),
+ socket => $commandline{socket} ||
+ $ENV{NAGIOS__SERVICEMYSQL_SOCKET} ||
+ $ENV{NAGIOS__HOSTMYSQL_SOCKET},
+ username => $commandline{username} ||
+ $ENV{NAGIOS__SERVICEMYSQL_USER} ||
+ $ENV{NAGIOS__HOSTMYSQL_USER},
+ password => $commandline{password} ||
+ $ENV{NAGIOS__SERVICEMYSQL_PASS} ||
+ $ENV{NAGIOS__HOSTMYSQL_PASS},
+ warningrange => $commandline{warning},
+ criticalrange => $commandline{critical},
+ dbthresholds => $commandline{dbthresholds},
+ absolute => $commandline{absolute},
+ lookback => $commandline{lookback},
+ selectname => $commandline{name} || $commandline{tablespace} || $commandline{datafile},
+ regexp => $commandline{regexp},
+ name => $commandline{name},
+ name2 => $commandline{name2} || $commandline{name},
+ units => $commandline{units},
+ lookback => $commandline{lookback} || 0,
+ eyecandy => $commandline{eyecandy},
+ statefilesdir => $commandline{statefilesdir},
+ verbose => $commandline{verbose},
+ report => $commandline{report},
+ labelformat => $commandline{labelformat},
+);
+
+my $server = undef;
+my $cluster = undef;
+
+if ($params{mode} =~ /^(server|my)/) {
+ $server = DBD::MySQL::Server->new(%params);
+ $server->nagios(%params);
+ $server->calculate_result(\%labels);
+ $nagios_message = $server->{nagios_message};
+ $nagios_level = $server->{nagios_level};
+ $perfdata = $server->{perfdata};
+} elsif ($params{mode} =~ /^cluster/) {
+ $cluster = DBD::MySQL::Cluster->new(%params);
+ $cluster->nagios(%params);
+ $cluster->calculate_result(\%labels);
+ $nagios_message = $cluster->{nagios_message};
+ $nagios_level = $cluster->{nagios_level};
+ $perfdata = $cluster->{perfdata};
+}
+
+printf "%s - %s", $ERRORCODES{$nagios_level}, $nagios_message;
+printf " | %s", $perfdata if $perfdata;
+printf "\n";
+exit $nagios_level;
+
+
+__END__
+
+
diff --git a/puppet/modules/nagios/files/plugins/check_openvpn_server.pl b/puppet/modules/nagios/files/plugins/check_openvpn_server.pl
new file mode 100755
index 00000000..b74ace89
--- /dev/null
+++ b/puppet/modules/nagios/files/plugins/check_openvpn_server.pl
@@ -0,0 +1,109 @@
+#!/usr/bin/perl
+#
+# Filaname: check_openvpn
+# Created: 2012-06-15
+# Website: http://blog.kernelpicnic.net
+#
+# Description:
+# This script is for verifying the status of an OpenVPN daemon. It has been
+# written to integrate directly with Nagios / Opsview.
+#
+# Usage:
+# check_openvpn [OPTIONS]...
+#
+# -H, --hostname Host to check
+# -p, --port Port number to check
+# -h, --help Display help.
+#
+#############################################################################
+
+# Custom library path for Nagis modules.
+use lib qw ( /usr/local/nagios/perl/lib );
+
+# Enforce sanity.
+use strict;
+use warnings;
+
+# Required modules.
+use Getopt::Long qw(:config no_ignore_case);
+use Nagios::Plugin;
+use IO::Socket;
+
+# Define defaults.
+my $help = 0;
+my $timeout = 5;
+
+# Ensure required variables are set.
+my($hostname, $port);
+
+my $options = GetOptions(
+ "hostname|H=s" => \$hostname,
+ "timeout|t=s" => \$timeout,
+ "port|p=s" => \$port,
+ "help|h" => \$help,
+);
+
+# Check if help has been requested.
+if($help || !$hostname || !$port) {
+
+ printf("\n");
+ printf("Usage: check_openvpn [OPTIONS]...\n\n");
+ printf(" -H, --hostname Host to check\n");
+ printf(" -p, --port Port number to check\n");
+ printf(" -h, --help This help page\n");
+ printf(" -t, --timeout Socket timeout\n");
+ printf("\n");
+
+ exit(-1);
+
+}
+
+# Setup a new Nagios::Plugin object.
+my $nagios = Nagios::Plugin->new();
+
+# Define the check string to send to the OpenVPN server - as binary due
+# to non-printable characters.
+my $check_string = "001110000011001010010010011011101000000100010001110"
+ ."100110110101010110011000000000000000000000000000000"
+ ."0000000000";
+
+# Attempt to setup a socket to the specified host.
+my $host_sock = IO::Socket::INET->new(
+ Proto => 'udp',
+ PeerAddr => $hostname,
+ PeerPort => $port,
+);
+
+# Ensure we have a socket.
+if(!$host_sock) {
+ $nagios->nagios_exit(UNKNOWN, "Unable to bind socket");
+}
+
+# Fire off the check request.
+$host_sock->send(pack("B*", $check_string));
+
+# Wait for $timeout for response for a response, otherwise, fail.
+my $response;
+
+eval {
+
+ # Define how to handle ALARM.
+ local $SIG{ALRM} = sub {
+ $nagios->nagios_exit(CRITICAL, "No response received");
+ };
+
+ # Set the alarm for the given timeout value.
+ alarm($timeout);
+
+ # Check for response.
+ $host_sock->recv($response, 1)
+ or $nagios->nagios_exit(CRITICAL, "No response received");
+
+ # Alright, response received, cancel alarm.
+ alarm(0);
+ 1;
+
+};
+
+# Reply received, return okay.
+$nagios->nagios_exit(OK, "Response received from host");
diff --git a/puppet/modules/nagios/files/plugins/check_pop3_login b/puppet/modules/nagios/files/plugins/check_pop3_login
new file mode 100644
index 00000000..4eb29b88
--- /dev/null
+++ b/puppet/modules/nagios/files/plugins/check_pop3_login
@@ -0,0 +1,83 @@
+#!/usr/bin/python
+# -*- coding: UTF-8 -*-
+# -*- Mode: Python -*-
+#
+# Copyright (C) 2006 Bertera Pietro <pietro@bertera.it>
+# Copyright (C) 2015 mh <mh@immerda.ch>
+# Response time monitoring with perfdata modification by Ivan Savcic <isavcic@gmail.com> and Milos Buncic, 2012.
+# Derived from: https://github.com/isavcic/check_imap_login
+
+# This file may be distributed and/or modified under the terms of
+# the GNU General Public License version 2 as published by
+# the Free Software Foundation.
+# This file is distributed without any warranty; without even the implied
+# warranty of merchantability or fitness for a particular purpose.
+
+import sys, os, poplib, getopt
+from time import time
+
+def usage():
+ print sys.argv[0] + " -u <user> -p <password> -H <host> [-s] -w <warning threshold (sec)> -c <critical threshold (sec)>\n -s is for using POP3s"
+
+def main():
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], "u:p:sH:w:c:")
+ except getopt.GetoptError:
+ usage()
+ return 3
+
+ user = host = password = use_ssl = warning = critical = None
+
+ for o, a in opts:
+ if o == "-u":
+ user = a
+ elif o == "-p":
+ password = a
+ elif o == "-s":
+ use_ssl = True
+ elif o == "-H":
+ host = a
+ elif o == "-w":
+ warning = float(a)
+ elif o == "-c":
+ critical = float(a)
+
+ if user == None or password == None or host == None or warning == None or critical == None:
+ usage()
+ return 1
+
+ if use_ssl:
+ M = poplib.POP3_SSL(host=host)
+ else:
+ M = poplib.POP3(host)
+
+ timestamp = time()
+
+ try:
+ M.getwelcome()
+ M.user(user)
+ M.pass_(password)
+ except Exception, e:
+ print "CRITICAL POP3 Login Failed: %s" % e
+ return 2
+
+ M.quit()
+
+ timestamp = time() - timestamp
+
+ if timestamp < warning:
+ status = "OK"
+ exitcode = 0
+ if timestamp >= warning:
+ status = "WARNING"
+ exitcode = 1
+ if timestamp >= critical:
+ status = "CRITICAL"
+ exitcode = 2
+
+ print '%s POP3 Login | response_time=%.3fs;%.3f;%.3f' % (status, timestamp, warning, critical)
+
+ return exitcode
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/puppet/modules/nagios/files/pnp4nagios/action.gif b/puppet/modules/nagios/files/pnp4nagios/action.gif
new file mode 100644
index 00000000..96571a40
--- /dev/null
+++ b/puppet/modules/nagios/files/pnp4nagios/action.gif
Binary files differ
diff --git a/puppet/modules/nagios/files/pnp4nagios/apache.conf b/puppet/modules/nagios/files/pnp4nagios/apache.conf
new file mode 100644
index 00000000..816bf05c
--- /dev/null
+++ b/puppet/modules/nagios/files/pnp4nagios/apache.conf
@@ -0,0 +1,31 @@
+# SAMPLE CONFIG SNIPPETS FOR APACHE WEB SERVER
+
+Alias /pnp4nagios "/usr/share/pnp4nagios/html"
+
+<Directory "/usr/share/pnp4nagios/html">
+ AllowOverride None
+ Order allow,deny
+ Allow from all
+ #
+ # Use the same value as defined in nagios.conf
+ #
+ AuthName "Nagios Access"
+ AuthType Basic
+ AuthUserFile /etc/nagios3/htpasswd.users
+ Require valid-user
+ <IfModule mod_rewrite.c>
+ # Turn on URL rewriting
+ RewriteEngine On
+ Options FollowSymLinks
+ # Installation directory
+ RewriteBase /pnp4nagios/
+ # Protect application and system files from being viewed
+ RewriteRule ^(application|modules|system) - [F,L]
+ # Allow any files or directories that exist to be displayed directly
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteCond %{REQUEST_FILENAME} !-d
+ # Rewrite all other URLs to index.php/URL
+ RewriteRule .* index.php/$0 [PT,L]
+ </IfModule>
+ DirectoryIndex index.php
+</Directory>
diff --git a/puppet/modules/nagios/files/pnp4nagios/npcd b/puppet/modules/nagios/files/pnp4nagios/npcd
new file mode 100644
index 00000000..64b3d4d9
--- /dev/null
+++ b/puppet/modules/nagios/files/pnp4nagios/npcd
@@ -0,0 +1,8 @@
+# Default settings for the NPCD init script.
+
+# Should NPCD be started? ("yes" to enable)
+RUN="yes"
+
+# Additional options that are passed to the daemon.
+DAEMON_OPTS="-d -f /etc/pnp4nagios/npcd.cfg"
+
diff --git a/puppet/modules/nagios/files/pnp4nagios/pnp4nagios-popup-templates.cfg b/puppet/modules/nagios/files/pnp4nagios/pnp4nagios-popup-templates.cfg
new file mode 100644
index 00000000..de17d841
--- /dev/null
+++ b/puppet/modules/nagios/files/pnp4nagios/pnp4nagios-popup-templates.cfg
@@ -0,0 +1,31 @@
+# http://docs.pnp4nagios.org/de/pnp-0.6/webfe
+
+define host {
+ name host-pnp
+ action_url /pnp4nagios/index.php/graph?host=$HOSTNAME$&srv=_HOST_' class='tips' rel='/pnp4nagios/index.php/popup?host=$HOSTNAME$&srv=_HOST_
+ register 0
+}
+
+define service {
+ name srv-pnp
+ action_url /pnp4nagios/index.php/graph?host=$HOSTNAME$&srv=$SERVICEDESC$' class='tips' rel='/pnp4nagios/index.php/popup?host=$HOSTNAME$&srv=$SERVICEDESC$
+ register 0
+}
+
+# templates for explicit use, i.e.
+# use => 'generic-host-pnp'
+
+define host {
+ name generic-host-pnp
+ use generic-host,host-pnp
+# action_url /pnp4nagios/index.php/graph?host=$HOSTNAME$&srv=_HOST_
+ register 0
+}
+
+define service {
+ name generic-service-pnp
+ use generic-service,srv-pnp
+# action_url /pnp4nagios/index.php/graph?host=$HOSTNAME$&srv=_HOST_
+ register 0
+}
+
diff --git a/puppet/modules/nagios/files/pnp4nagios/pnp4nagios-templates.cfg b/puppet/modules/nagios/files/pnp4nagios/pnp4nagios-templates.cfg
new file mode 100644
index 00000000..64c51865
--- /dev/null
+++ b/puppet/modules/nagios/files/pnp4nagios/pnp4nagios-templates.cfg
@@ -0,0 +1,33 @@
+# http://docs.pnp4nagios.org/de/pnp-0.6/webfe
+
+# templates for additional use, i.e.
+# use => 'generic-host,host-pnp'
+define host {
+ name host-pnp
+ action_url /pnp4nagios/index.php/graph?host=$HOSTNAME$&srv=_HOST_
+ register 0
+}
+
+define service {
+ name srv-pnp
+ action_url /pnp4nagios/index.php/graph?host=$HOSTNAME$&srv=$SERVICEDESC$
+ register 0
+}
+
+# templates for explicit use, i.e.
+# use => 'generic-host-pnp'
+
+define host {
+ name generic-host-pnp
+ use generic-host,host-pnp
+# action_url /pnp4nagios/index.php/graph?host=$HOSTNAME$&srv=_HOST_
+ register 0
+}
+
+define service {
+ name generic-service-pnp
+ use generic-service,srv-pnp
+# action_url /pnp4nagios/index.php/graph?host=$HOSTNAME$&srv=_HOST_
+ register 0
+}
+
diff --git a/puppet/modules/nagios/files/pnp4nagios/status-header.ssi b/puppet/modules/nagios/files/pnp4nagios/status-header.ssi
new file mode 100644
index 00000000..472be3a2
--- /dev/null
+++ b/puppet/modules/nagios/files/pnp4nagios/status-header.ssi
@@ -0,0 +1,8 @@
+<script src="/pnp4nagios/media/js/jquery-min.js" type="text/javascript"></script>
+<script src="/pnp4nagios/media/js/jquery.cluetip.js" type="text/javascript"></script>
+<script type="text/javascript">
+$(document).ready(function() {
+ $('a.tips').cluetip({ajaxCache: false, dropShadow: false,showTitle: false });
+});
+</script>
+
diff --git a/puppet/modules/nagios/images/nagiosgraph.gif b/puppet/modules/nagios/images/nagiosgraph.gif
new file mode 100644
index 00000000..068082af
--- /dev/null
+++ b/puppet/modules/nagios/images/nagiosgraph.gif
Binary files differ
diff --git a/puppet/modules/nagios/manifests/apache.pp b/puppet/modules/nagios/manifests/apache.pp
new file mode 100644
index 00000000..87fe3d2f
--- /dev/null
+++ b/puppet/modules/nagios/manifests/apache.pp
@@ -0,0 +1,15 @@
+# setup naguis together with apache
+class nagios::apache(
+ $allow_external_cmd = false,
+ $manage_shorewall = false,
+ $manage_munin = false,
+ $storeconfigs = true
+) {
+ class{'::nagios':
+ httpd => 'apache',
+ allow_external_cmd => $allow_external_cmd,
+ manage_munin => $manage_munin,
+ manage_shorewall => $manage_shorewall,
+ storeconfigs => $storeconfigs
+ }
+}
diff --git a/puppet/modules/nagios/manifests/base.pp b/puppet/modules/nagios/manifests/base.pp
new file mode 100644
index 00000000..18d5c12f
--- /dev/null
+++ b/puppet/modules/nagios/manifests/base.pp
@@ -0,0 +1,144 @@
+# basic stuff for nagios
+class nagios::base {
+ # include the variables
+ include ::nagios::defaults::vars
+
+ package { 'nagios':
+ ensure => present,
+ }
+
+ service { 'nagios':
+ ensure => running,
+ enable => $nagios::service_at_boot,
+ require => Package['nagios'],
+ }
+
+ $cfg_dir = $nagios::defaults::vars::int_cfgdir
+ # this file should contain all the nagios_puppet-paths:
+ file{
+ 'nagios_cfgdir':
+ ensure => directory,
+ path => $cfg_dir,
+ alias => nagios_confd,
+ recurse => true,
+ purge => true,
+ force => true,
+ require => Package['nagios'],
+ notify => Service['nagios'],
+ owner => root,
+ group => root,
+ mode => '0755';
+ 'nagios_main_cfg':
+ path => "${cfg_dir}/nagios.cfg",
+ source => [ "puppet:///modules/site_nagios/configs/${::fqdn}/nagios.cfg",
+ "puppet:///modules/site_nagios/configs/${::operatingsystem}/nagios.cfg",
+ 'puppet:///modules/site_nagios/configs/nagios.cfg',
+ "puppet:///modules/nagios/configs/${::operatingsystem}/nagios.cfg",
+ 'puppet:///modules/nagios/configs/nagios.cfg' ],
+ notify => Service['nagios'],
+ owner => root,
+ group => root,
+ mode => '0644';
+ 'nagios_cgi_cfg':
+ path => "${cfg_dir}/cgi.cfg",
+ source => [ "puppet:///modules/site_nagios/configs/${::fqdn}/cgi.cfg",
+ "puppet:///modules/site_nagios/configs/${::operatingsystem}/cgi.cfg",
+ 'puppet:///modules/site_nagios/configs/cgi.cfg',
+ "puppet:///modules/nagios/configs/${::operatingsystem}/cgi.cfg",
+ 'puppet:///modules/nagios/configs/cgi.cfg' ],
+ notify => Service['apache'],
+ owner => 'root',
+ group => 0,
+ mode => '0644';
+ 'nagios_htpasswd':
+ path => "${cfg_dir}/htpasswd.users",
+ source => [ 'puppet:///modules/site_nagios/htpasswd.users',
+ 'puppet:///modules/nagios/htpasswd.users' ],
+ owner => root,
+ group => apache,
+ mode => '0640';
+ 'nagios_resource_cfg':
+ path => "${cfg_dir}/resource.cfg",
+ source => [ "puppet:///modules/site_nagios/configs/${::operatingsystem}/private/resource.cfg.${::architecture}",
+ "puppet:///modules/nagios/configs/${::operatingsystem}/private/resource.cfg.${::architecture}" ],
+ notify => Service['nagios'],
+ owner => root,
+ group => nagios,
+ mode => '0640';
+ }
+
+ if $cfg_dir == '/etc/nagios3' {
+ file{'/etc/nagios':
+ ensure => link,
+ target => $cfg_dir,
+ before => File['nagios_cfgdir'],
+ }
+ }
+
+ file{
+ [ "${cfg_dir}/nagios_command.cfg",
+ "${cfg_dir}/nagios_contact.cfg",
+ "${cfg_dir}/nagios_contactgroup.cfg",
+ "${cfg_dir}/nagios_host.cfg",
+ "${cfg_dir}/nagios_hostdependency.cfg",
+ "${cfg_dir}/nagios_hostescalation.cfg",
+ "${cfg_dir}/nagios_hostextinfo.cfg",
+ "${cfg_dir}/nagios_hostgroup.cfg",
+ "${cfg_dir}/nagios_hostgroupescalation.cfg",
+ "${cfg_dir}/nagios_service.cfg",
+ "${cfg_dir}/nagios_servicedependency.cfg",
+ "${cfg_dir}/nagios_serviceescalation.cfg",
+ "${cfg_dir}/nagios_serviceextinfo.cfg",
+ "${cfg_dir}/nagios_servicegroup.cfg",
+ "${cfg_dir}/nagios_timeperiod.cfg" ]:
+ ensure => file,
+ replace => false,
+ notify => Service['nagios'],
+ require => File['nagios_cfgdir'],
+ owner => root,
+ group => 0,
+ mode => '0644';
+ }
+
+ resources {
+ [
+ 'nagios_command',
+ 'nagios_contactgroup',
+ 'nagios_contact',
+ 'nagios_hostdependency',
+ 'nagios_hostescalation',
+ 'nagios_hostextinfo',
+ 'nagios_hostgroup',
+ 'nagios_host',
+ 'nagios_servicedependency',
+ 'nagios_serviceescalation',
+ 'nagios_servicegroup',
+ 'nagios_serviceextinfo',
+ 'nagios_service',
+ 'nagios_timeperiod',
+ ]:
+ notify => Service['nagios'],
+ purge => $::nagios::purge_resources
+ }
+
+ # make sure nagios resources are defined after nagios is
+ # installed and the nagios_cfgdir resource is present
+ File['nagios_cfgdir'] -> Nagios_command <||>
+ File['nagios_cfgdir'] -> Nagios_contactgroup <||>
+ File['nagios_cfgdir'] -> Nagios_contact <||>
+ File['nagios_cfgdir'] -> Nagios_hostdependency <||>
+ File['nagios_cfgdir'] -> Nagios_hostescalation <||>
+ File['nagios_cfgdir'] -> Nagios_hostextinfo <||>
+ File['nagios_cfgdir'] -> Nagios_hostgroup <||>
+ File['nagios_cfgdir'] -> Nagios_host <||>
+ File['nagios_cfgdir'] -> Nagios_servicedependency <||>
+ File['nagios_cfgdir'] -> Nagios_serviceescalation <||>
+ File['nagios_cfgdir'] -> Nagios_servicegroup <||>
+ File['nagios_cfgdir'] -> Nagios_serviceextinfo <||>
+ File['nagios_cfgdir'] -> Nagios_service <||>
+ File['nagios_cfgdir'] -> Nagios_timeperiod <||>
+
+ if ( $nagios::storeconfigs == true ) {
+ include ::nagios::storeconfigs
+ }
+}
diff --git a/puppet/modules/nagios/manifests/centos.pp b/puppet/modules/nagios/manifests/centos.pp
new file mode 100644
index 00000000..f41d46dc
--- /dev/null
+++ b/puppet/modules/nagios/manifests/centos.pp
@@ -0,0 +1,42 @@
+# centos specific changes
+class nagios::centos inherits nagios::base {
+
+ package { [ 'nagios-plugins', 'nagios-plugins-smtp','nagios-plugins-http',
+ 'nagios-plugins-ssh', 'nagios-plugins-tcp', 'nagios-plugins-dig',
+ 'nagios-plugins-nrpe', 'nagios-plugins-load', 'nagios-plugins-dns',
+ 'nagios-plugins-ping', 'nagios-plugins-procs', 'nagios-plugins-users',
+ 'nagios-plugins-ldap', 'nagios-plugins-disk', 'nagios-plugins-swap',
+ 'nagios-plugins-nagios', 'nagios-plugins-perl', 'nagios-plugins-ntp',
+ 'nagios-plugins-snmp' ]:
+ ensure => 'present',
+ notify => Service['nagios'],
+ }
+
+ Service['nagios']{
+ hasstatus => true,
+ }
+
+ file{
+ 'nagios_private':
+ ensure => directory,
+ path => "${nagios::base::cfg_dir}/private",
+ purge => true,
+ recurse => true,
+ notify => Service['nagios'],
+ owner => root,
+ group => nagios,
+ mode => '0750';
+ }
+ File['nagios_resource_cfg']{
+ path => "${nagios::base::cfg_dir}/private/resource.cfg",
+ }
+ if $nagios::allow_external_cmd {
+ file{'/var/spool/nagios/cmd':
+ ensure => 'directory',
+ require => Package['nagios'],
+ owner => apache,
+ group => nagios,
+ mode => '2660',
+ }
+ }
+}
diff --git a/puppet/modules/nagios/manifests/command/imap_pop3.pp b/puppet/modules/nagios/manifests/command/imap_pop3.pp
new file mode 100644
index 00000000..42e4092b
--- /dev/null
+++ b/puppet/modules/nagios/manifests/command/imap_pop3.pp
@@ -0,0 +1,30 @@
+# manage mail checks
+class nagios::command::imap_pop3 {
+ require ::nagios::plugins::mail_login
+ case $::operatingsystem {
+ 'Debian','Ubuntu': { } # Debian/Ubuntu already define those checks
+ default: {
+ nagios_command {
+ 'check_imap':
+ command_line => '$USER1$/check_imap -H $ARG1$ -p $ARG2$';
+ }
+ }
+ }
+
+ nagios_command {
+ 'check_imap_ssl':
+ command_line => '$USER1$/check_imap -H $ARG1$ -p $ARG2$ -S';
+ 'check_pop3':
+ command_line => '$USER1$/check_pop -H $ARG1$ -p $ARG2$';
+ 'check_pop3_ssl':
+ command_line => '$USER1$/check_pop -H $ARG1$ -p $ARG2$ -S';
+ 'check_managesieve':
+ command_line => '$USER1$/check_tcp -H $ARG1$ -p 4190';
+ 'check_managesieve_legacy':
+ command_line => '$USER1$/check_tcp -H $ARG1$ -p 2000';
+ 'check_imap_login':
+ command_line => '$USER1$/check_imap_login -s -H $ARG1$ -u $ARG2$ -p $ARG3$ -w $ARG4$ -c $ARG5$';
+ 'check_pop3_login':
+ command_line => '$USER1$/check_pop3_login -s -H $ARG1$ -u $ARG2$ -p $ARG3$ -w $ARG4$ -c $ARG5$';
+ }
+}
diff --git a/puppet/modules/nagios/manifests/command/nrpe.pp b/puppet/modules/nagios/manifests/command/nrpe.pp
new file mode 100644
index 00000000..7539a266
--- /dev/null
+++ b/puppet/modules/nagios/manifests/command/nrpe.pp
@@ -0,0 +1,14 @@
+class nagios::command::nrpe {
+
+ # this command runs a program $ARG1$ with arguments $ARG2$
+ nagios_command {
+ 'check_nrpe':
+ command_line => '/usr/lib/nagios/plugins/check_nrpe -H $HOSTADDRESS$ -c $ARG1$ -a $ARG2$'
+ }
+
+ # this command runs a program $ARG1$ with no arguments
+ nagios_command {
+ 'check_nrpe_1arg':
+ command_line => '/usr/lib/nagios/plugins/check_nrpe -H $HOSTADDRESS$ -c $ARG1$'
+ }
+}
diff --git a/puppet/modules/nagios/manifests/command/nrpe_timeout.pp b/puppet/modules/nagios/manifests/command/nrpe_timeout.pp
new file mode 100644
index 00000000..799f2fc3
--- /dev/null
+++ b/puppet/modules/nagios/manifests/command/nrpe_timeout.pp
@@ -0,0 +1,11 @@
+class nagios::command::nrpe_timeout {
+ nagios_command {
+ 'check_nrpe_timeout':
+ command_line => '/usr/lib/nagios/plugins/check_nrpe -t $ARG1$ -H $HOSTADDRESS$ -c $ARG2$ -a $ARG3$',
+ require => Package['nagios-nrpe-server'];
+
+ 'check_nrpe_1arg_timeout':
+ command_line => '/usr/lib/nagios/plugins/check_nrpe -t $ARG1$ -H $HOSTADDRESS$ -c $ARG2$',
+ require => Package['nagios-nrpe-server']
+ }
+}
diff --git a/puppet/modules/nagios/manifests/command/smtp.pp b/puppet/modules/nagios/manifests/command/smtp.pp
new file mode 100644
index 00000000..29d97f8b
--- /dev/null
+++ b/puppet/modules/nagios/manifests/command/smtp.pp
@@ -0,0 +1,22 @@
+class nagios::command::smtp {
+ case $operatingsystem {
+ debian,ubuntu: { } # Debian/Ubuntu already define those checks
+ default: {
+ nagios_command {
+ 'check_smtp':
+ command_line => '$USER1$/check_smtp -H $ARG1$ -p $ARG2$';
+ 'check_ssmtp':
+ command_line => '$USER1$/check_ssmtp -H $ARG1$ -p $ARG2$ -S';
+ }
+ }
+ }
+
+ nagios_command {
+ 'check_smtp_tls':
+ command_line => '$USER1$/check_smtp -H $ARG1$ -p $ARG2$ -S';
+ 'check_smtp_cert':
+ command_line => '$USER1$/check_smtp -H $ARG1$ -p $ARG2$ -S -D $ARG3$';
+ 'check_ssmtp_cert':
+ command_line => '$USER1$/check_ssmtp -H $ARG1$ -p $ARG2$ -S -D $ARG3$';
+ }
+}
diff --git a/puppet/modules/nagios/manifests/debian.pp b/puppet/modules/nagios/manifests/debian.pp
new file mode 100644
index 00000000..39af973e
--- /dev/null
+++ b/puppet/modules/nagios/manifests/debian.pp
@@ -0,0 +1,54 @@
+# debian specific things
+class nagios::debian inherits nagios::base {
+
+ Package['nagios'] { name => 'nagios3' }
+
+ package { [ 'nagios-plugins', 'nagios-snmp-plugins','nagios-nrpe-plugin' ]:
+ ensure => 'present',
+ notify => Service['nagios'],
+ }
+
+ Service['nagios'] {
+ name => 'nagios3',
+ hasstatus => true,
+ }
+
+ File['nagios_htpasswd', 'nagios_cgi_cfg'] { group => 'www-data' }
+
+ file{
+ 'nagios_commands_cfg':
+ path => "${nagios::defaults::vars::int_cfgdir}/commands.cfg",
+ notify => Service['nagios'],
+ owner => root,
+ group => root,
+ mode => '0644',
+ require => Package['nagios'];
+ "${nagios::defaults::vars::int_cfgdir}/stylesheets":
+ ensure => directory,
+ purge => false,
+ recurse => true,
+ require => Package['nagios'];
+ }
+
+ if $nagios::allow_external_cmd {
+ exec { 'nagios_external_cmd_perms_overrides':
+ command => 'dpkg-statoverride --update --add nagios www-data 2710 /var/lib/nagios3/rw && dpkg-statoverride --update --add nagios nagios 751 /var/lib/nagios3',
+ unless => 'dpkg-statoverride --list nagios www-data 2710 /var/lib/nagios3/rw && dpkg-statoverride --list nagios nagios 751 /var/lib/nagios3',
+ logoutput => false,
+ notify => Service['nagios'],
+ require => Package['nagios'],
+ }
+ exec { 'nagios_external_cmd_perms_1':
+ command => 'chmod 0751 /var/lib/nagios3 && chown nagios:nagios /var/lib/nagios3',
+ unless => 'test "`stat -c "%a %U %G" /var/lib/nagios3`" = "751 nagios nagios"',
+ notify => Service['nagios'],
+ require => Package['nagios'],
+ }
+ exec { 'nagios_external_cmd_perms_2':
+ command => 'chmod 2751 /var/lib/nagios3/rw && chown nagios:www-data /var/lib/nagios3/rw',
+ unless => 'test "`stat -c "%a %U %G" /var/lib/nagios3/rw`" = "2751 nagios www-data"',
+ notify => Service['nagios'],
+ require => Package['nagios'],
+ }
+ }
+}
diff --git a/puppet/modules/nagios/manifests/debian/apache.pp b/puppet/modules/nagios/manifests/debian/apache.pp
new file mode 100644
index 00000000..17b60c60
--- /dev/null
+++ b/puppet/modules/nagios/manifests/debian/apache.pp
@@ -0,0 +1,22 @@
+# Handle files that are specifically needed for nagios with apache on debian
+#
+# Do not include this class directly. It is included by the nagios class and
+# needs variables from it.
+#
+class nagios::debian::apache {
+
+ include ::nagios::defaults::vars
+
+ file { "${nagios::defaults::vars::int_cfgdir}/apache2.conf":
+ source => [ "puppet:///modules/site_nagios/configs/${::fqdn}/apache2.conf",
+ 'puppet:///modules/site_nagios/configs/apache2.conf',
+ 'puppet:///modules/nagios/configs/apache2.conf'],
+ }
+
+ apache::config::global { 'nagios3.conf':
+ ensure => link,
+ target => "${nagios::defaults::vars::int_cfgdir}/apache2.conf",
+ require => File["${nagios::defaults::vars::int_cfgdir}/apache2.conf"],
+ }
+
+}
diff --git a/puppet/modules/nagios/manifests/defaults.pp b/puppet/modules/nagios/manifests/defaults.pp
new file mode 100644
index 00000000..7c25ac94
--- /dev/null
+++ b/puppet/modules/nagios/manifests/defaults.pp
@@ -0,0 +1,12 @@
+class nagios::defaults {
+
+ # include some default nagios objects
+
+ include nagios::defaults::commands
+ include nagios::defaults::contactgroups
+ include nagios::defaults::contacts
+ include nagios::defaults::hostgroups
+ include nagios::defaults::templates
+ include nagios::defaults::timeperiods
+ include nagios::defaults::plugins
+}
diff --git a/puppet/modules/nagios/manifests/defaults/commands.pp b/puppet/modules/nagios/manifests/defaults/commands.pp
new file mode 100644
index 00000000..0f24411f
--- /dev/null
+++ b/puppet/modules/nagios/manifests/defaults/commands.pp
@@ -0,0 +1,145 @@
+# defaults commands we wanna have available
+class nagios::defaults::commands {
+
+ include ::nagios::command::smtp
+ include ::nagios::command::imap_pop3
+ include ::nagios::plugins::horde_login
+
+ # common service commands
+ case $::operatingsystem {
+ 'Debian','Ubuntu': {
+ nagios_command {
+ 'check_dummy':
+ command_line => '$USER1$/check_dummy $ARG1$';
+ 'check_https_cert':
+ command_line => '$USER1$/check_http --ssl -C 20 -H $HOSTADDRESS$ -I $HOSTADDRESS$';
+ 'check_http_url':
+ command_line => '$USER1$/check_http -H $ARG1$ -u $ARG2$';
+ 'check_http_url_regex':
+ command_line => '$USER1$/check_http -H $ARG1$ -p $ARG2$ -u $ARG3$ -e $ARG4$';
+ 'check_https_url':
+ command_line => '$USER1$/check_http --ssl -H $ARG1$ -u $ARG2$';
+ 'check_https_url_regex':
+ command_line => '$USER1$/check_http --ssl -H $ARG1$ -u $ARG2$ -e $ARG3$';
+ 'check_mysql_db':
+ command_line => '$USER1$/check_mysql -H $ARG1$ -P $ARG2$ -u $ARG3$ -p $ARG4$ -d $ARG5$';
+ 'check_ntp_time':
+ command_line => '$USER1$/check_ntp_time -H $HOSTADDRESS$ -w 0.5 -c 1';
+ 'check_silc':
+ command_line => '$USER1$/check_tcp -p 706 -H $ARG1$';
+ 'check_sobby':
+ command_line => '$USER1$/check_tcp -H $ARG1$ -p $ARG2$';
+ 'check_jabber':
+ command_line => '$USER1$/check_jabber -H $ARG1$';
+ 'check_git':
+ command_line => '$USER1$/check_tcp -H $ARG1$ -p 9418';
+ }
+ }
+ default: {
+ nagios_command {
+ 'check_dummy':
+ command_line => '$USER1$/check_dummy $ARG1$';
+ 'check_ping':
+ command_line => '$USER1$/check_ping -H $HOSTADDRESS$ -w $ARG1$ -c $ARG2$';
+ 'check-host-alive':
+ command_line => '$USER1$/check_ping -H $HOSTADDRESS$ -w 5000,100% -c 5000,100% -p 1';
+ 'check_tcp':
+ command_line => '$USER1$/check_tcp -H $HOSTADDRESS$ -p $ARG1$';
+ 'check_udp':
+ command_line => '$USER1$/check_udp -H $HOSTADDRESS$ -p $ARG1$';
+ 'check_load':
+ command_line => '$USER1$/check_load --warning=$ARG1$,$ARG2$,$ARG3$ --critical=$ARG4$,$ARG5$,$ARG6$';
+ 'check_disk':
+ command_line => '$USER1$/check_disk -w $ARG1$ -c $ARG2$ -e -p $ARG3$';
+ 'check_all_disks':
+ command_line => '$USER1$/check_disk -w $ARG1$ -c $ARG2$ -e';
+ 'check_ssh':
+ command_line => '$USER1$/check_ssh $HOSTADDRESS$';
+ 'check_ssh_port':
+ command_line => '$USER1$/check_ssh -p $ARG1$ $HOSTADDRESS$';
+ 'check_ssh_port_host':
+ command_line => '$USER1$/check_ssh -p $ARG1$ $ARG2$';
+ 'check_http':
+ command_line => '$USER1$/check_http -H $HOSTADDRESS$ -I $HOSTADDRESS$';
+ 'check_https':
+ command_line => '$USER1$/check_http --ssl -H $HOSTADDRESS$ -I $HOSTADDRESS$';
+ 'check_https_cert':
+ command_line => '$USER1$/check_http --ssl -C 20 -H $HOSTADDRESS$ -I $HOSTADDRESS$';
+ 'check_http_url':
+ command_line => '$USER1$/check_http -H $ARG1$ -u $ARG2$';
+ 'check_http_url_regex':
+ command_line => '$USER1$/check_http -H $ARG1$ -p $ARG2$ -u $ARG3$ -e $ARG4$';
+ 'check_https_url':
+ command_line => '$USER1$/check_http --ssl -H $ARG1$ -u $ARG2$';
+ 'check_https_url_regex':
+ command_line => '$USER1$/check_http --ssl -H $ARG1$ -u $ARG2$ -e $ARG3$';
+ 'check_mysql':
+ command_line => '$USER1$/check_mysql -H $ARG1$ -P $ARG2$ -u $ARG3$ -p $ARG4$';
+ 'check_mysql_db':
+ command_line => '$USER1$/check_mysql -H $ARG1$ -P $ARG2$ -u $ARG3$ -p $ARG4$ -d $ARG5$';
+ 'check_ntp_time':
+ command_line => '$USER1$/check_ntp_time -H $HOSTADDRESS$ -w 0.5 -c 1';
+ 'check_silc':
+ command_line => '$USER1$/check_tcp -p 706 -H $ARG1$';
+ 'check_sobby':
+ command_line => '$USER1$/check_tcp -H $ARG1$ -p $ARG2$';
+ 'check_jabber':
+ command_line => '$USER1$/check_jabber -H $ARG1$';
+ 'check_git':
+ command_line => '$USER1$/check_tcp -H $ARG1$ -p 9418';
+ }
+ }
+ }
+
+ # commands for services defined by other modules
+
+ nagios_command {
+ # from apache module
+ 'http_port':
+ command_line => '$USER1$/check_http -p $ARG1$ -H $HOSTADDRESS$ -I $HOSTADDRESS$';
+
+ 'check_http_port_url_content':
+ command_line => '$USER1$/check_http -H $ARG1$ -p $ARG2$ -u $ARG3$ -s $ARG4$';
+ 'check_https_port_url_content':
+ command_line => '$USER1$/check_http --ssl -H $ARG1$ -p $ARG2$ -u $ARG3$ -s $ARG4$';
+ 'check_http_url_content':
+ command_line => '$USER1$/check_http -H $ARG1$ -u $ARG2$ -s $ARG3$';
+ 'check_https_url_content':
+ command_line => '$USER1$/check_http --ssl -H $ARG1$ -u $ARG2$ -s $ARG3$';
+
+ # from bind module
+ 'check_dig2':
+ command_line => '$USER1$/check_dig -H $HOSTADDRESS$ -l $ARG1$ --record_type=$ARG2$';
+
+ # from mysql module
+ 'check_mysql_health':
+ command_line => '$USER1$/check_mysql_health --hostname $ARG1$ --port $ARG2$ --username $ARG3$ --password $ARG4$ --mode $ARG5$ --database $ARG6$ $ARG7$ $ARG8$';
+
+ # better check_dns
+ 'check_dns2':
+ command_line => '$USER1$/check_dns2 -c $ARG1$ A $ARG2$';
+
+ # dnsbl checking
+ 'check_dnsbl':
+ command_line => '$USER1$/check_dnsbl -H $ARG1$';
+ }
+
+ # notification commands
+
+ $mail_cmd_location = $::operatingsystem ? {
+ 'CentOS' => '/bin/mail',
+ default => '/usr/bin/mail'
+ }
+
+ case $::lsbdistcodename {
+ 'wheezy': { }
+ default: {
+ nagios_command {
+ 'notify-host-by-email':
+ command_line => "/usr/bin/printf \"%b\" \"***** Nagios *****\\n\\nNotification Type: \$NOTIFICATIONTYPE\$\\n\\nHost: \$HOSTNAME\$ (\$HOSTALIAS\$)\\nAddress: \$HOSTADDRESS\$\\nState: \$HOSTSTATE\$\\nDuration: \$HOSTDURATION\$\\n\\nDate/Time: \$LONGDATETIME\$\\n\\nOutput: \$HOSTOUTPUT\$\" | ${mail_cmd_location} -s \"\$NOTIFICATIONTYPE\$: \$HOSTSTATE\$ - \$HOSTNAME\$\" \$CONTACTEMAIL\$";
+ 'notify-service-by-email':
+ command_line => "/usr/bin/printf \"%b\" \"***** Nagios *****\\n\\nNotification Type: \$NOTIFICATIONTYPE\$\\n\\nHost: \$HOSTNAME\$ (\$HOSTALIAS\$)\\nAddress: \$HOSTADDRESS\$\\n\\nService: \$SERVICEDESC\$\\nState: \$SERVICESTATE\$\\nDuration: \$SERVICEDURATION\$\\n\\nDate/Time: \$LONGDATETIME\$\\n\\nOutput: \$SERVICEOUTPUT\$\" | ${mail_cmd_location} -s \"\$NOTIFICATIONTYPE\$: \$SERVICESTATE\$ - \$HOSTALIAS\$/\$SERVICEDESC\$\" \$CONTACTEMAIL\$";
+ }
+ }
+ }
+}
diff --git a/puppet/modules/nagios/manifests/defaults/contactgroups.pp b/puppet/modules/nagios/manifests/defaults/contactgroups.pp
new file mode 100644
index 00000000..f5affc60
--- /dev/null
+++ b/puppet/modules/nagios/manifests/defaults/contactgroups.pp
@@ -0,0 +1,9 @@
+class nagios::defaults::contactgroups {
+
+ nagios_contactgroup {
+ 'admins':
+ alias => 'Nagios Administrators',
+ members => 'root',
+ }
+
+}
diff --git a/puppet/modules/nagios/manifests/defaults/contacts.pp b/puppet/modules/nagios/manifests/defaults/contacts.pp
new file mode 100644
index 00000000..0252b5a8
--- /dev/null
+++ b/puppet/modules/nagios/manifests/defaults/contacts.pp
@@ -0,0 +1,15 @@
+class nagios::defaults::contacts {
+
+ nagios_contact {
+ 'root':
+ alias => 'Root',
+ service_notification_period => '24x7',
+ host_notification_period => '24x7',
+ service_notification_options => 'w,u,c,r',
+ host_notification_options => 'd,r',
+ service_notification_commands => 'notify-service-by-email',
+ host_notification_commands => 'notify-host-by-email',
+ email => 'root@localhost',
+ }
+
+}
diff --git a/puppet/modules/nagios/manifests/defaults/host_templates.pp b/puppet/modules/nagios/manifests/defaults/host_templates.pp
new file mode 100644
index 00000000..0f47324a
--- /dev/null
+++ b/puppet/modules/nagios/manifests/defaults/host_templates.pp
@@ -0,0 +1,24 @@
+class nagios::defaults::host_templates {
+
+ # this inoperative for the moment, see :
+ # http://projects.reductivelabs.com/issues/1180
+
+ nagios_host {
+ 'generic-host':
+ notifications_enabled => '1',
+ event_handler_enabled => '1',
+ flap_detection_enabled => '1',
+ failure_prediction_enabled => '1',
+ process_perf_data => '1',
+ retain_status_information => '1',
+ retain_nonstatus_information => '1',
+ check_command => 'check-host-alive',
+ max_check_attempts => '10',
+ notification_interval => '0',
+ notification_period => '24x7',
+ notification_options => 'd,u,r',
+ contact_groups => 'admins',
+ register => '0',
+ }
+
+}
diff --git a/puppet/modules/nagios/manifests/defaults/hostgroups.pp b/puppet/modules/nagios/manifests/defaults/hostgroups.pp
new file mode 100644
index 00000000..8715adee
--- /dev/null
+++ b/puppet/modules/nagios/manifests/defaults/hostgroups.pp
@@ -0,0 +1,11 @@
+class nagios::defaults::hostgroups {
+ nagios_hostgroup {
+ 'all':
+ alias => 'All Servers',
+ members => '*';
+ 'debian-servers':
+ alias => 'Debian GNU/Linux Servers';
+ 'centos-servers':
+ alias => 'CentOS GNU/Linux Servers';
+ }
+}
diff --git a/puppet/modules/nagios/manifests/defaults/plugins.pp b/puppet/modules/nagios/manifests/defaults/plugins.pp
new file mode 100644
index 00000000..abd8b528
--- /dev/null
+++ b/puppet/modules/nagios/manifests/defaults/plugins.pp
@@ -0,0 +1,10 @@
+class nagios::defaults::plugins {
+ nagios::plugin {
+ 'check_mysql_health':
+ source => 'nagios/plugins/check_mysql_health';
+ 'check_dns2':
+ source => 'nagios/plugins/check_dns2';
+ 'check_dnsbl':
+ source => 'nagios/plugins/check_dnsbl';
+ }
+}
diff --git a/puppet/modules/nagios/manifests/defaults/pnp4nagios.pp b/puppet/modules/nagios/manifests/defaults/pnp4nagios.pp
new file mode 100644
index 00000000..58676c5a
--- /dev/null
+++ b/puppet/modules/nagios/manifests/defaults/pnp4nagios.pp
@@ -0,0 +1,14 @@
+# configure default cmds for pnp4nagios
+class nagios::defaults::pnp4nagios {
+
+ # performance data cmds
+ # http://docs.pnp4nagios.org/de/pnp-0.6/config#bulk_mode_mit_npcd
+ nagios_command {
+ 'process-service-perfdata-file-pnp4nagios-bulk-npcd':
+ command_line => '/bin/mv /var/lib/nagios3/service-perfdata /var/spool/pnp4nagios/npcd/service-perfdata.$TIMET$',
+ require => Package['nagios'];
+ 'process-host-perfdata-file-pnp4nagios-bulk-npcd':
+ command_line => '/bin/mv /var/lib/nagios3/host-perfdata /var/spool/pnp4nagios/npcd/host-perfdata.$TIMET$',
+ require => Package['nagios'];
+ }
+}
diff --git a/puppet/modules/nagios/manifests/defaults/service_templates.pp b/puppet/modules/nagios/manifests/defaults/service_templates.pp
new file mode 100644
index 00000000..e39441a1
--- /dev/null
+++ b/puppet/modules/nagios/manifests/defaults/service_templates.pp
@@ -0,0 +1,32 @@
+# define the generic service template
+class nagios::defaults::service_templates {
+
+ # this inoperative for the moment, see :
+ # http://projects.reductivelabs.com/issues/1180
+
+ nagios_service {
+ 'generic-service':
+ active_checks_enabled => '1',
+ passive_checks_enabled => '1',
+ parallelize_check => '1',
+ obsess_over_service => '1',
+ check_freshness => '0',
+ notifications_enabled => '1',
+ event_handler_enabled => '1',
+ flap_detection_enabled => '1',
+ failure_prediction_enabled => '1',
+ process_perf_data => '1',
+ retain_status_information => '1',
+ retain_nonstatus_information => '1',
+ notification_interval => '0',
+ is_volatile => '0',
+ check_period => '24x7',
+ check_interval => '5',
+ retry_check_interval => '1',
+ max_check_attempts => '4',
+ notification_period => '24x7',
+ notification_options => 'w,u,c,r',
+ contact_groups => 'admins',
+ register => '0',
+ }
+}
diff --git a/puppet/modules/nagios/manifests/defaults/templates.pp b/puppet/modules/nagios/manifests/defaults/templates.pp
new file mode 100644
index 00000000..5158189c
--- /dev/null
+++ b/puppet/modules/nagios/manifests/defaults/templates.pp
@@ -0,0 +1,17 @@
+# manage nagios_templates
+class nagios::defaults::templates {
+ include nagios::defaults::vars
+
+ file { 'nagios_templates':
+ path => "${nagios::defaults::vars::int_cfgdir}/nagios_templates.cfg",
+ source => [ "puppet:///modules/site_nagios/configs/${::fqdn}/nagios_templates.cfg",
+ "puppet:///modules/site_nagios/configs/${::operatingsystem}/nagios_templates.cfg",
+ 'puppet:///modules/site_nagios/configs/nagios_templates.cfg',
+ "puppet:///modules/nagios/configs/${::operatingsystem}/nagios_templates.cfg",
+ 'puppet:///modules/nagios/configs/nagios_templates.cfg' ],
+ notify => Service['nagios'],
+ owner => root,
+ group => root,
+ mode => '0644';
+ }
+}
diff --git a/puppet/modules/nagios/manifests/defaults/timeperiods.pp b/puppet/modules/nagios/manifests/defaults/timeperiods.pp
new file mode 100644
index 00000000..0d05118a
--- /dev/null
+++ b/puppet/modules/nagios/manifests/defaults/timeperiods.pp
@@ -0,0 +1,33 @@
+class nagios::defaults::timeperiods {
+
+ nagios_timeperiod {
+ '24x7':
+ alias => '24 Hours A Day, 7 Days A Week',
+ sunday => '00:00-24:00',
+ monday => '00:00-24:00',
+ tuesday => '00:00-24:00',
+ wednesday => '00:00-24:00',
+ thursday => '00:00-24:00',
+ friday => '00:00-24:00',
+ saturday => '00:00-24:00';
+ 'workhours':
+ alias => 'Standard Work Hours',
+ monday => '09:00-17:00',
+ tuesday => '09:00-17:00',
+ wednesday => '09:00-17:00',
+ thursday => '09:00-17:00',
+ friday => '09:00-17:00';
+ 'nonworkhours':
+ alias => 'Non-Work Hours',
+ sunday => '00:00-24:00',
+ monday => '00:00-09:00,17:00-24:00',
+ tuesday => '00:00-09:00,17:00-24:00',
+ wednesday => '00:00-09:00,17:00-24:00',
+ thursday => '00:00-09:00,17:00-24:00',
+ friday => '00:00-09:00,17:00-24:00',
+ saturday => '00:00-24:00';
+ 'never':
+ alias => 'Never';
+ }
+
+}
diff --git a/puppet/modules/nagios/manifests/defaults/vars.pp b/puppet/modules/nagios/manifests/defaults/vars.pp
new file mode 100644
index 00000000..e1a62245
--- /dev/null
+++ b/puppet/modules/nagios/manifests/defaults/vars.pp
@@ -0,0 +1,11 @@
+# some default vars
+class nagios::defaults::vars {
+ case $nagios::cfgdir {
+ '': { $int_cfgdir = $::operatingsystem ? {
+ centos => '/etc/nagios',
+ default => '/etc/nagios3'
+ }
+ }
+ default: { $int_cfgdir = $nagios::cfgdir }
+ }
+}
diff --git a/puppet/modules/nagios/manifests/headless.pp b/puppet/modules/nagios/manifests/headless.pp
new file mode 100644
index 00000000..ba8af8f4
--- /dev/null
+++ b/puppet/modules/nagios/manifests/headless.pp
@@ -0,0 +1,5 @@
+class nagios::headless {
+ class { 'nagios':
+ httpd => 'absent',
+ }
+}
diff --git a/puppet/modules/nagios/manifests/init.pp b/puppet/modules/nagios/manifests/init.pp
new file mode 100644
index 00000000..e3421a0a
--- /dev/null
+++ b/puppet/modules/nagios/manifests/init.pp
@@ -0,0 +1,56 @@
+#
+# nagios module
+# nagios.pp - everything nagios related
+#
+# Copyright (C) 2007 David Schmitt <david@schmitt.edv-bus.at>
+# Copyright 2008, admin(at)immerda.ch
+# Copyright 2008, Puzzle ITC GmbH
+# Marcel Haerry haerry+puppet(at)puzzle.ch
+# Simon Josi josi+puppet(at)puzzle.ch
+#
+# This program is free software; you can redistribute
+# it and/or modify it under the terms of the GNU
+# General Public License version 3 as published by
+# the Free Software Foundation.
+#
+
+# manage nagios
+class nagios(
+ $httpd = 'apache',
+ $allow_external_cmd = false,
+ $manage_shorewall = false,
+ $manage_munin = false,
+ $service_atboot = true,
+ $purge_resources = true,
+ $gpgkey_checks = {},
+ $storeconfigs = true
+) {
+ case $nagios::httpd {
+ 'absent': { }
+ 'lighttpd': { include ::lighttpd }
+ 'apache': {
+ include ::apache
+ if $::operatingsystem == 'Debian' {
+ include ::nagios::debian::apache
+ }
+ }
+ default: { include ::apache }
+ }
+ case $::operatingsystem {
+ 'centos': {
+ $cfgdir = '/etc/nagios'
+ include ::nagios::centos
+ }
+ 'debian': {
+ $cfgdir = '/etc/nagios3'
+ include ::nagios::debian
+ }
+ default: {
+ fail("No such operatingsystem: ${::operatingsystem} yet defined")
+ }
+ }
+ if $manage_munin {
+ include ::nagios::munin
+ }
+ create_resources('nagios::service::gpgkey',$gpgkey_checks)
+}
diff --git a/puppet/modules/nagios/manifests/irc_bot.pp b/puppet/modules/nagios/manifests/irc_bot.pp
new file mode 100644
index 00000000..7e934ef1
--- /dev/null
+++ b/puppet/modules/nagios/manifests/irc_bot.pp
@@ -0,0 +1,50 @@
+class nagios::irc_bot(
+ $nsa_socket = 'absent',
+ $nsa_server,
+ $nsa_port = 6667,
+ $nsa_nickname,
+ $nsa_password = '',
+ $nsa_channel,
+ $nsa_pidfile = 'absent',
+ $nsa_realname = 'Nagios',
+ $nsa_usenotices = false,
+ $nsa_commandfile = 'absent'
+) {
+ $real_nsa_socket = $nsa_socket ? {
+ 'absent' => $::operatingsystem ? {
+ centos => '/var/run/nagios-nsa/nsa.socket',
+ default => '/var/run/nagios3/nsa.socket'
+ },
+ default => $nsa_socket,
+ }
+ $real_nsa_pidfile = $nsa_pidfile ? {
+ 'absent' => $::operatingsystem ? {
+ centos => '/var/run/nagios-nsa/nsa.pid',
+ default => '/var/run/nagios3/nsa.pid'
+ },
+ default => $nsa_pidfile,
+ }
+ $real_nsa_commandfile = $nsa_commandfile ? {
+ 'absent' => $::operatingsystem ? {
+ centos => '/var/spool/nagios/cmd/nagios.cmd',
+ default => '/var/lib/nagios3/rw/nagios.cmd'
+ },
+ default => $nsa_commandfile,
+ }
+
+ case $::operatingsystem {
+ centos: {
+ include nagios::irc_bot::centos
+ }
+ debian,ubuntu: {
+ include nagios::irc_bot::debian
+ }
+ default: {
+ include nagios::irc_bot::base
+ }
+ }
+
+ if $nagios::manage_shorewall {
+ include shorewall::rules::out::irc
+ }
+}
diff --git a/puppet/modules/nagios/manifests/irc_bot/base.pp b/puppet/modules/nagios/manifests/irc_bot/base.pp
new file mode 100644
index 00000000..fff9da4f
--- /dev/null
+++ b/puppet/modules/nagios/manifests/irc_bot/base.pp
@@ -0,0 +1,41 @@
+class nagios::irc_bot::base {
+ file {
+ '/usr/local/bin/riseup-nagios-client.pl':
+ source => 'puppet:///modules/nagios/irc_bot/riseup-nagios-client.pl',
+ owner => root, group => 0, mode => '0755';
+
+ '/usr/local/bin/riseup-nagios-server.pl':
+ source => 'puppet:///modules/nagios/irc_bot/riseup-nagios-server.pl',
+ owner => root, group => 0, mode => '0755';
+
+ '/etc/init.d/nagios-nsa':
+ content => template("nagios/irc_bot/${::operatingsystem}/nagios-nsa.sh.erb"),
+ require => File['/usr/local/bin/riseup-nagios-server.pl'],
+ owner => root, group => 0, mode => '0755';
+
+ '/etc/nagios_nsa.cfg':
+ ensure => present,
+ content => template('nagios/irc_bot/nsa.cfg.erb'),
+ owner => nagios, group => 0, mode => '0400',
+ notify => Service['nagios-nsa'];
+ }
+
+ package { 'libnet-irc-perl':
+ ensure => present,
+ }
+
+ service { 'nagios-nsa':
+ ensure => 'running',
+ hasstatus => true,
+ require => [ File['/etc/nagios_nsa.cfg'],
+ Package['libnet-irc-perl'],
+ Service['nagios'] ],
+ }
+
+ nagios_command {
+ 'notify-by-irc':
+ command_line => '/usr/local/bin/riseup-nagios-client.pl "$HOSTNAME$ ($SERVICEDESC$) $NOTIFICATIONTYPE$ n.$SERVICEATTEMPT$ $SERVICESTATETYPE$ $SERVICEEXECUTIONTIME$s $SERVICELATENCY$s $SERVICEOUTPUT$ $SERVICEPERFDATA$"';
+ 'host-notify-by-irc':
+ command_line => '/usr/local/bin/riseup-nagios-client.pl "$HOSTNAME$ ($HOSTALIAS$) $NOTIFICATIONTYPE$ n.$HOSTATTEMPT$ $HOSTSTATETYPE$ took $HOSTEXECUTIONTIME$s $HOSTOUTPUT$ $HOSTPERFDATA$ $HOSTLATENCY$s"';
+ }
+}
diff --git a/puppet/modules/nagios/manifests/irc_bot/centos.pp b/puppet/modules/nagios/manifests/irc_bot/centos.pp
new file mode 100644
index 00000000..d7b19063
--- /dev/null
+++ b/puppet/modules/nagios/manifests/irc_bot/centos.pp
@@ -0,0 +1,9 @@
+class nagios::irc_bot::centos inherits nagios::irc_bot::base {
+ Package['libnet-irc-perl']{
+ name => 'perl-Net-IRC',
+ }
+
+ Service['nagios-nsa']{
+ enable => true,
+ }
+}
diff --git a/puppet/modules/nagios/manifests/irc_bot/debian.pp b/puppet/modules/nagios/manifests/irc_bot/debian.pp
new file mode 100644
index 00000000..93ea64b8
--- /dev/null
+++ b/puppet/modules/nagios/manifests/irc_bot/debian.pp
@@ -0,0 +1,8 @@
+class nagios::irc_bot::debian inherits nagios::irc_bot::base {
+ exec { "nagios_nsa_init_script":
+ command => "/usr/sbin/update-rc.d nagios-nsa defaults",
+ unless => "/bin/ls /etc/rc3.d/ | /bin/grep nagios-nsa",
+ require => File["/etc/init.d/nagios-nsa"],
+ before => Service['nagios-nsa'],
+ }
+}
diff --git a/puppet/modules/nagios/manifests/irc_bot/disable.pp b/puppet/modules/nagios/manifests/irc_bot/disable.pp
new file mode 100644
index 00000000..d6b7c551
--- /dev/null
+++ b/puppet/modules/nagios/manifests/irc_bot/disable.pp
@@ -0,0 +1,8 @@
+class nagios::irc_bot::disable inherits nagios::irc_bot::base {
+
+ Service['nagios-nsa'] {
+ ensure => stopped,
+ enable => false,
+ }
+
+}
diff --git a/puppet/modules/nagios/manifests/lighttpd.pp b/puppet/modules/nagios/manifests/lighttpd.pp
new file mode 100644
index 00000000..0f298964
--- /dev/null
+++ b/puppet/modules/nagios/manifests/lighttpd.pp
@@ -0,0 +1,12 @@
+class nagios::lighttpd(
+ $allow_external_cmd = false,
+ $manage_shorewall = false,
+ $manage_munin = false
+) {
+ class{'nagios':
+ httpd => 'lighttpd',
+ allow_external_cmd => $allow_external_cmd,
+ manage_munin => $manage_munin,
+ manage_shorewall => $manage_shorewall,
+ }
+}
diff --git a/puppet/modules/nagios/manifests/munin.pp b/puppet/modules/nagios/manifests/munin.pp
new file mode 100644
index 00000000..dc5cc4c3
--- /dev/null
+++ b/puppet/modules/nagios/manifests/munin.pp
@@ -0,0 +1,19 @@
+class nagios::munin {
+ include munin::plugins::base
+
+ munin::plugin::deploy {
+ 'nagios_hosts':
+ source => 'nagios/munin/nagios_hosts',
+ config => 'user nagios';
+ 'nagios_svc':
+ source => 'nagios/munin/nagios_svc',
+ config => 'user nagios';
+ 'nagios_perf_hosts':
+ source => 'nagios/munin/nagios_perf',
+ config => 'user nagios';
+ 'nagios_perf_svc':
+ source => 'nagios/munin/nagios_perf',
+ config => 'user nagios';
+ }
+
+}
diff --git a/puppet/modules/nagios/manifests/nrpe.pp b/puppet/modules/nagios/manifests/nrpe.pp
new file mode 100644
index 00000000..b7984b6e
--- /dev/null
+++ b/puppet/modules/nagios/manifests/nrpe.pp
@@ -0,0 +1,41 @@
+# setup nrpe stuff
+class nagios::nrpe (
+ $cfg_dir = '',
+ $pid_file = '',
+ $plugin_dir = '',
+ $server_address = '',
+ $allowed_hosts = '',
+ $dont_blame = '0',
+) {
+
+ if !($dont_blame in ['0', '1']) {
+ fail('Unrecognized value for $dont_blame, must be one of "0", or "1".')
+ }
+
+ case $::operatingsystem {
+ 'FreeBSD': {
+ if $cfg_dir == '' { $real_cfg_dir = '/usr/local/etc' }
+ if $pid_file == '' { $real_pid_file = '/var/spool/nagios/nrpe2.pid' }
+ if $plugin_dir == '' { $real_plugin_dir = '/usr/local/libexec/nagios' }
+
+ include ::nagios::nrpe::freebsd
+ }
+ 'Debian': {
+ if $cfg_dir == '' { $real_cfg_dir = '/etc/nagios' }
+ if $pid_file == '' { $real_pid_file = '/var/run/nagios/nrpe.pid' }
+ if $plugin_dir == '' { $real_plugin_dir = '/usr/lib/nagios/plugins' }
+ include ::nagios::nrpe::linux
+ }
+ default: {
+ if $cfg_dir == '' { $real_cfg_dir = '/etc/nagios' }
+ if $pid_file == '' { $real_pid_file = '/var/run/nrpe.pid' }
+ if $plugin_dir == '' { $real_plugin_dir = '/usr/lib/nagios/plugins' }
+
+ case $::kernel {
+ 'Linux': { include ::nagios::nrpe::linux }
+ default: { include ::nagios::nrpe::base }
+ }
+ }
+ }
+
+}
diff --git a/puppet/modules/nagios/manifests/nrpe/base.pp b/puppet/modules/nagios/manifests/nrpe/base.pp
new file mode 100644
index 00000000..e48e87b4
--- /dev/null
+++ b/puppet/modules/nagios/manifests/nrpe/base.pp
@@ -0,0 +1,58 @@
+# basic nrpe stuff
+class nagios::nrpe::base {
+
+ # Import all variables from entry point
+ $cfg_dir = $::nagios::nrpe::real_cfg_dir
+ $pid_file = $::nagios::nrpe::real_pid_file
+ $plugin_dir = $::nagios::nrpe::real_plugin_dir
+ $server_address = $::nagios::nrpe::server_address
+ $allowed_hosts = $::nagios::nrpe::allowed_hosts
+ $dont_blame = $::nagios::nrpe::dont_blame
+
+ package{['nagios-nrpe-server', 'nagios-plugins-basic', 'libwww-perl']:
+ ensure => installed;
+ }
+
+ # Special-case lenny. the package doesn't exist
+ if $::lsbdistcodename != 'lenny' {
+ package{'libnagios-plugin-perl': ensure => installed; }
+ }
+
+ file{
+ [ $cfg_dir, "${cfg_dir}/nrpe.d" ]:
+ ensure => directory;
+ }
+
+ file { "${cfg_dir}/nrpe.cfg":
+ content => template('nagios/nrpe/nrpe.cfg'),
+ owner => root,
+ group => 0,
+ mode => '0644';
+ }
+
+ # default commands
+ nagios::nrpe::command{'basic_nrpe':
+ source => [ "puppet:///modules/site_nagios/configs/nrpe/nrpe_commands.${::fqdn}.cfg",
+ 'puppet:///modules/site_nagios/configs/nrpe/nrpe_commands.cfg',
+ 'puppet:///modules/nagios/nrpe/nrpe_commands.cfg' ],
+ }
+ # the check for load should be customized for each server based on number
+ # of CPUs and the type of activity.
+ $warning_1_threshold = 7 * $::processorcount
+ $warning_5_threshold = 6 * $::processorcount
+ $warning_15_threshold = 5 * $::processorcount
+ $critical_1_threshold = 10 * $::processorcount
+ $critical_5_threshold = 9 * $::processorcount
+ $critical_15_threshold = 8 * $::processorcount
+ nagios::nrpe::command {'check_load':
+ command_line => "${plugin_dir}/check_load -w ${warning_1_threshold},${warning_5_threshold},${warning_15_threshold} -c ${critical_1_threshold},${critical_5_threshold},${critical_15_threshold}",
+ }
+
+ service{'nagios-nrpe-server':
+ ensure => running,
+ enable => true,
+ pattern => 'nrpe',
+ subscribe => File["${cfg_dir}/nrpe.cfg"],
+ require => Package['nagios-nrpe-server'],
+ }
+}
diff --git a/puppet/modules/nagios/manifests/nrpe/command.pp b/puppet/modules/nagios/manifests/nrpe/command.pp
new file mode 100644
index 00000000..c66ab986
--- /dev/null
+++ b/puppet/modules/nagios/manifests/nrpe/command.pp
@@ -0,0 +1,34 @@
+# manage an nrpe command
+define nagios::nrpe::command (
+ $ensure = present,
+ $command_line = '',
+ $source = '',
+){
+ if ($command_line == '' and $source == '') {
+ fail('Either one of $command_line or $source must be given to nagios::nrpe::command.' )
+ }
+
+ $cfg_dir = $nagios::nrpe::real_cfg_dir
+
+ file{"${cfg_dir}/nrpe.d/${name}_command.cfg":
+ ensure => $ensure,
+ notify => Service['nagios-nrpe-server'],
+ require => File["${cfg_dir}/nrpe.d" ],
+ owner => 'root',
+ group => 0,
+ mode => '0644';
+ }
+
+ case $source {
+ '': {
+ File["${cfg_dir}/nrpe.d/${name}_command.cfg"] {
+ content => template('nagios/nrpe/nrpe_command.erb'),
+ }
+ }
+ default: {
+ File["${cfg_dir}/nrpe.d/${name}_command.cfg"] {
+ source => $source,
+ }
+ }
+ }
+}
diff --git a/puppet/modules/nagios/manifests/nrpe/debian.pp b/puppet/modules/nagios/manifests/nrpe/debian.pp
new file mode 100644
index 00000000..fcaf8514
--- /dev/null
+++ b/puppet/modules/nagios/manifests/nrpe/debian.pp
@@ -0,0 +1,6 @@
+class nagios::nrpe::debian inherits nagios::nrpe::base {
+ include nagios::nrpe::linux
+ Service['nagios-nrpe-server'] {
+ hasstatus => false,
+ }
+}
diff --git a/puppet/modules/nagios/manifests/nrpe/freebsd.pp b/puppet/modules/nagios/manifests/nrpe/freebsd.pp
new file mode 100644
index 00000000..063b79bc
--- /dev/null
+++ b/puppet/modules/nagios/manifests/nrpe/freebsd.pp
@@ -0,0 +1,16 @@
+class nagios::nrpe::freebsd inherits nagios::nrpe::base {
+
+ Package["nagios-nrpe-server"] { name => "nrpe" }
+ Package["nagios-plugins-basic"] { name => "nagios-plugins" }
+ Package["libnagios-plugin-perl"] { name => "p5-Nagios-Plugin" }
+ Package["libwww-perl"] { name => "p5-libwww" }
+
+ # TODO check_cpustats.sh is probably not working as of now. the package 'sysstat' is not available under FreeBSD
+
+ Service["nagios-nrpe-server"] {
+ pattern => "^/usr/local/sbin/nrpe2",
+ path => "/usr/local/etc/rc.d",
+ name => "nrpe2",
+ hasstatus => "false",
+ }
+}
diff --git a/puppet/modules/nagios/manifests/nrpe/linux.pp b/puppet/modules/nagios/manifests/nrpe/linux.pp
new file mode 100644
index 00000000..14e007f3
--- /dev/null
+++ b/puppet/modules/nagios/manifests/nrpe/linux.pp
@@ -0,0 +1,9 @@
+class nagios::nrpe::linux inherits nagios::nrpe::base {
+
+ package {
+ "nagios-plugins-standard": ensure => present;
+ "ksh": ensure => present; # for check_cpustats.sh
+ "sysstat": ensure => present; # for check_cpustats.sh
+ }
+
+}
diff --git a/puppet/modules/nagios/manifests/nrpe/xinetd.pp b/puppet/modules/nagios/manifests/nrpe/xinetd.pp
new file mode 100644
index 00000000..4de0bac6
--- /dev/null
+++ b/puppet/modules/nagios/manifests/nrpe/xinetd.pp
@@ -0,0 +1,11 @@
+# This is created only to cope with cases where we're not the only ones
+# administering a machine and NRPE is running in xinetd.
+class nagios::nrpe::xinetd inherits base {
+
+ Service["nagios-nrpe-server"] {
+ ensure => stopped,
+ }
+
+ # TODO manage the xinetd config file that glues with NRPE
+
+}
diff --git a/puppet/modules/nagios/manifests/nsca.pp b/puppet/modules/nagios/manifests/nsca.pp
new file mode 100644
index 00000000..d5be298c
--- /dev/null
+++ b/puppet/modules/nagios/manifests/nsca.pp
@@ -0,0 +1,3 @@
+class nagios::nsca {
+ include nagios::nsca::server
+}
diff --git a/puppet/modules/nagios/manifests/nsca/client.pp b/puppet/modules/nagios/manifests/nsca/client.pp
new file mode 100644
index 00000000..6aa8c0b1
--- /dev/null
+++ b/puppet/modules/nagios/manifests/nsca/client.pp
@@ -0,0 +1,18 @@
+# manage nsca client
+class nagios::nsca::client {
+
+ package{'nsca':
+ ensure => installed
+ }
+
+ file{'/etc/send_nsca.cfg':
+ source => [ "puppet:///modules/site_nagios/nsca/${::fqdn}/send_nsca.cfg",
+ 'puppet:///modules/site_nagios/nsca/send_nsca.cfg',
+ 'puppet:///modules/nagios/nsca/send_nsca.cfg' ],
+ owner => 'nagios',
+ group => 'nogroup',
+ mode => '0400',
+ require => Package['nsca'];
+ }
+
+}
diff --git a/puppet/modules/nagios/manifests/nsca/server.pp b/puppet/modules/nagios/manifests/nsca/server.pp
new file mode 100644
index 00000000..8163eec1
--- /dev/null
+++ b/puppet/modules/nagios/manifests/nsca/server.pp
@@ -0,0 +1,24 @@
+# an nsca server
+class nagios::nsca::server {
+ package{'nsca':
+ ensure => installed
+ }
+
+ service { 'nsca':
+ ensure => running,
+ hasstatus => false,
+ hasrestart => true,
+ require => Package['nsca'],
+ }
+
+ file { '/etc/nsca.cfg':
+ source => [ "puppet:///modules/site_nagios/nsca/${::fqdn}/nsca.cfg",
+ 'puppet:///modules/site_nagios/nsca/nsca.cfg',
+ 'puppet:///modules/nagios/nsca/nsca.cfg' ],
+ owner => 'nagios',
+ group => 'nogroup',
+ mode => '0400',
+ notify => Service['nsca'],
+ }
+
+}
diff --git a/puppet/modules/nagios/manifests/plugin.pp b/puppet/modules/nagios/manifests/plugin.pp
new file mode 100644
index 00000000..07938cd2
--- /dev/null
+++ b/puppet/modules/nagios/manifests/plugin.pp
@@ -0,0 +1,28 @@
+# a wrapper for syncing a plugin
+define nagios::plugin(
+ $source = 'absent',
+ $ensure = present,
+){
+ if $::hardwaremodel == 'x86_64' and $::operatingsystem != 'Debian' {
+ $real_path = "/usr/lib64/nagios/plugins/${name}"
+ }
+ else {
+ $real_path = "/usr/lib/nagios/plugins/${name}"
+ }
+
+ $real_source = $source ? {
+ 'absent' => "puppet:///modules/nagios/plugins/${name}",
+ default => "puppet:///modules/${source}"
+ }
+
+ file{$name:
+ ensure => $ensure,
+ path => $real_path,
+ source => $real_source,
+ tag => 'nagios_plugin',
+ require => Package['nagios-plugins'],
+ owner => 'root',
+ group => 0,
+ mode => '0755';
+ }
+}
diff --git a/puppet/modules/nagios/manifests/plugin/deploy.pp b/puppet/modules/nagios/manifests/plugin/deploy.pp
new file mode 100644
index 00000000..76815909
--- /dev/null
+++ b/puppet/modules/nagios/manifests/plugin/deploy.pp
@@ -0,0 +1,41 @@
+# deploy a specific plugin
+define nagios::plugin::deploy(
+ $source = '',
+ $ensure = 'present',
+ $config = '',
+ $require_package = 'nagios-plugins'
+) {
+ $plugin_src = $ensure ? {
+ 'present' => $name,
+ 'absent' => $name,
+ default => $ensure
+ }
+ $real_source = $source ? {
+ '' => "nagios/plugins/${plugin_src}",
+ default => $source
+ }
+
+ if !defined(Package[$require_package]) {
+ package { $require_package:
+ ensure => installed,
+ tag => 'nagios::plugin::deploy::package';
+ }
+ }
+
+ include ::nagios::plugin::scriptpaths
+ file{"nagios_plugin_${name}":
+ path => "${nagios::plugin::scriptpaths::script_path}/${name}",
+ source => "puppet:///modules/${real_source}",
+ require => Package[$require_package],
+ tag => 'nagios::plugin::deploy::file',
+ owner => root,
+ group => 0,
+ mode => '0755';
+ }
+
+ # register the plugin
+ nagios::plugin{$name:
+ ensure => $ensure,
+ require => Package['nagios-plugins']
+ }
+}
diff --git a/puppet/modules/nagios/manifests/plugin/scriptpaths.pp b/puppet/modules/nagios/manifests/plugin/scriptpaths.pp
new file mode 100644
index 00000000..9cd4b5d6
--- /dev/null
+++ b/puppet/modules/nagios/manifests/plugin/scriptpaths.pp
@@ -0,0 +1,6 @@
+class nagios::plugin::scriptpaths {
+ case $::hardwaremodel {
+ x86_64: { $script_path = "/usr/lib64/nagios/plugins/" }
+ default: { $script_path = "/usr/lib/nagios/plugins" }
+ }
+}
diff --git a/puppet/modules/nagios/manifests/plugins/gpg.pp b/puppet/modules/nagios/manifests/plugins/gpg.pp
new file mode 100644
index 00000000..a09736a8
--- /dev/null
+++ b/puppet/modules/nagios/manifests/plugins/gpg.pp
@@ -0,0 +1,30 @@
+# check_gpg from
+# https://github.com/lelutin/nagios-plugins/blob/master/check_gpg
+class nagios::plugins::gpg {
+ require ::gpg
+ nagios::plugin{'check_gpg':
+ source => 'nagios/plugins/check_gpg',
+ }
+
+ $gpg_home = '/var/local/nagios_gpg_homedir'
+ file{
+ $gpg_home:
+ ensure => 'directory',
+ owner => nagios,
+ group => nagios,
+ mode => '0600',
+ require => Nagios::Plugin['check_gpg'];
+ "${gpg_home}/sks-keyservers.netCA.pem":
+ source => 'puppet:///modules/nagios/plugin_data/sks-keyservers.netCA.pem',
+ owner => nagios,
+ group => 0,
+ mode => '0400',
+ before => Nagios_command['check_gpg'];
+ }
+ nagios_command {
+ 'check_gpg':
+ command_line => "\$USER1\$/check_gpg --gnupg-homedir ${gpg_home} -w \$ARG1\$ \$ARG2\$",
+ require => Nagios::Plugin['check_gpg'],
+ }
+}
+
diff --git a/puppet/modules/nagios/manifests/plugins/horde_login.pp b/puppet/modules/nagios/manifests/plugins/horde_login.pp
new file mode 100644
index 00000000..4274b4cf
--- /dev/null
+++ b/puppet/modules/nagios/manifests/plugins/horde_login.pp
@@ -0,0 +1,11 @@
+# check_horde_login
+class nagios::plugins::horde_login {
+ ensure_packages(['python-requests'])
+ nagios::plugin { 'check_horde_login':
+ source => 'nagios/plugins/check_horde_login',
+ require => Package['python-requests'],
+ } -> nagios_command {
+ 'check_horde_login':
+ command_line => "\$USER1\$/check_horde_login -s \$ARG1\$ -u \$ARG2\$ -p \$ARG3\$",
+ }
+}
diff --git a/puppet/modules/nagios/manifests/plugins/jabber.pp b/puppet/modules/nagios/manifests/plugins/jabber.pp
new file mode 100644
index 00000000..380a5c0a
--- /dev/null
+++ b/puppet/modules/nagios/manifests/plugins/jabber.pp
@@ -0,0 +1,10 @@
+class nagios::plugins::jabber {
+
+ # for check_jabber_login
+ require rubygems::xmpp4r
+
+ nagios::plugin { 'check_jabber_login':
+ source => 'nagios/plugins/check_jabber_login'
+ }
+}
+
diff --git a/puppet/modules/nagios/manifests/plugins/mail_login.pp b/puppet/modules/nagios/manifests/plugins/mail_login.pp
new file mode 100644
index 00000000..a86cdc24
--- /dev/null
+++ b/puppet/modules/nagios/manifests/plugins/mail_login.pp
@@ -0,0 +1,10 @@
+# simple mail login check
+class nagios::plugins::mail_login {
+ nagios::plugin {
+ 'check_imap_login':
+ source => 'nagios/plugins/check_imap_login';
+ 'check_pop3_login':
+ source => 'nagios/plugins/check_pop3_login';
+ }
+}
+
diff --git a/puppet/modules/nagios/manifests/pnp4nagios.pp b/puppet/modules/nagios/manifests/pnp4nagios.pp
new file mode 100644
index 00000000..bd7ab0ca
--- /dev/null
+++ b/puppet/modules/nagios/manifests/pnp4nagios.pp
@@ -0,0 +1,68 @@
+# manage pnp4nagios
+class nagios::pnp4nagios {
+ include nagios::defaults::pnp4nagios
+
+ package { [ 'pnp4nagios', 'pnp4nagios-web-config-nagios3']:
+ ensure => installed,
+ require => Package['nagios']
+ }
+
+ # unfortunatly we can't use the nagios_host and nagios_service
+ # definition to define templates, so we need to copy a file here.
+ # see http://projects.reductivelabs.com/issues/1180 for this limitation
+
+ file { 'pnp4nagios-templates.cfg':
+ path => "${nagios::defaults::vars::int_cfgdir}/pnp4nagios-templates.cfg",
+ source => [ 'puppet:///modules/site_nagios/pnp4nagios/pnp4nagios-templates.cfg',
+ 'puppet:///modules/nagios/pnp4nagios/pnp4nagios-templates.cfg' ],
+ mode => '0644',
+ owner => root,
+ group => root,
+ notify => Service['nagios'],
+ require => Package['nagios'];
+ }
+
+ file { 'apache.conf':
+ path => '/etc/pnp4nagios/apache.conf',
+ source => ['puppet:///modules/site_nagios/pnp4nagios/apache.conf',
+ 'puppet:///modules/nagios/pnp4nagios/apache.conf' ],
+ mode => '0644',
+ owner => root,
+ group => root,
+ notify => Service['apache'],
+ require => [ Package['apache2'], Package['pnp4nagios'] ],
+ }
+
+ # run npcd as daemon
+
+ file { '/etc/default/npcd':
+ path => '/etc/default/npcd',
+ source => [ 'puppet:///modules/site_nagios/pnp4nagios/npcd',
+ 'puppet:///modules/nagios/pnp4nagios/npcd' ],
+ mode => '0644',
+ owner => root,
+ group => root,
+ notify => Service['npcd'],
+ require => [ Package['nagios'], Package['pnp4nagios'] ];
+ }
+
+ service { 'npcd':
+ ensure => running,
+ enable => true,
+ hasstatus => true,
+ require => Package['pnp4nagios'],
+ }
+
+ # modify action.gif
+
+ file { '/usr/share/nagios3/htdocs/images/action.gif':
+ path => '/usr/share/nagios3/htdocs/images/action.gif',
+ source => [ 'puppet:///modules/site_nagios/pnp4nagios/action.gif',
+ 'puppet:///modules/nagios/pnp4nagios/action.gif' ],
+ mode => '0644',
+ owner => root,
+ group => root,
+ notify => Service['nagios'],
+ require => Package['nagios'];
+ }
+}
diff --git a/puppet/modules/nagios/manifests/pnp4nagios/popup.pp b/puppet/modules/nagios/manifests/pnp4nagios/popup.pp
new file mode 100644
index 00000000..91136ccb
--- /dev/null
+++ b/puppet/modules/nagios/manifests/pnp4nagios/popup.pp
@@ -0,0 +1,24 @@
+class nagios::pnp4nagios::popup inherits nagios::pnp4nagios {
+ File['pnp4nagios-templates.cfg']{
+ source => [
+ 'puppet:///modules/site-nagios/pnp4nagios/pnp4nagios-popup-templates.cfg',
+ 'puppet:///modules/nagios/pnp4nagios/pnp4nagios-popup-templates.cfg' ],
+ }
+
+ file { '/usr/share/nagios3/htdocs/ssi':
+ ensure => directory,
+ require => Package['nagios'],
+ }
+
+ file { 'status-header.ssi':
+ path => '/usr/share/nagios3/htdocs/ssi/status-header.ssi',
+ source => [
+ 'puppet:///modules/site-nagios/pnp4nagios/status-header.ssi',
+ 'puppet:///modules/nagios/pnp4nagios/status-header.ssi'],
+ mode => '0644',
+ owner => root,
+ group => root,
+ notify => Service['nagios'],
+ require => Package['nagios'],
+ }
+}
diff --git a/puppet/modules/nagios/manifests/service.pp b/puppet/modules/nagios/manifests/service.pp
new file mode 100644
index 00000000..e2c08e99
--- /dev/null
+++ b/puppet/modules/nagios/manifests/service.pp
@@ -0,0 +1,91 @@
+# a wrapper around nagios_service to make it more convenient and
+# also automatically an exported resource.
+define nagios::service (
+ $ensure = present,
+ $host_name = $::fqdn,
+ $check_command = 'absent',
+ $check_period = undef,
+ $check_interval = undef,
+ $retry_check_interval = undef,
+ $max_check_attempts = undef,
+ $notification_interval = undef,
+ $notification_period = undef,
+ $notification_options = undef,
+ $contact_groups = undef,
+ $use = 'generic-service',
+ $service_description = 'absent',
+ $use_nrpe = undef,
+ $nrpe_args = undef,
+ $nrpe_timeout = 10,
+) {
+
+ # TODO: this resource should normally accept all nagios_host parameters
+
+ $real_name = "${::hostname}_${name}"
+
+ @@nagios_service {$real_name:
+ ensure => $ensure,
+ notify => Service['nagios'];
+ }
+
+ if $ensure != 'absent' {
+ if $check_command == 'absent' {
+ fail("Must pass a check_command to ${name} if it should be present")
+ }
+ if str2bool($use_nrpe) {
+ include ::nagios::command::nrpe_timeout
+
+ if $nrpe_args {
+ $real_check_command = "check_nrpe_timeout!${nrpe_timeout}!${check_command}!\"${nrpe_args}\""
+ } else {
+ $real_check_command = "check_nrpe_1arg_timeout!${nrpe_timeout}!${check_command}"
+ }
+ } else {
+ $real_check_command = $check_command
+ }
+
+ $real_service_description = $service_description ? {
+ 'absent' => $name,
+ default => $service_description
+ }
+ Nagios_service[$real_name] {
+ check_command => $check_command,
+ host_name => $host_name,
+ use => $use,
+ service_description => $real_service_description,
+ }
+
+ if $check_period {
+ Nagios_service[$real_name] { check_period => $check_period }
+ }
+
+ if $check_interval {
+ Nagios_service[$real_name] { check_interval => $check_interval }
+ }
+
+ if $retry_check_interval {
+ Nagios_service[$real_name] { retry_check_interval => $retry_check_interval }
+ }
+
+ if $max_check_attempts {
+ Nagios_service[$real_name] { max_check_attempts => $max_check_attempts }
+ }
+
+ if $notification_interval {
+ Nagios_service[$real_name] { notification_interval => $notification_interval }
+ }
+
+ if $notification_period {
+ Nagios_service[$real_name] { notification_period => $notification_period }
+ }
+
+ if $notification_options {
+ Nagios_service[$real_name] { notification_options => $notification_options }
+ }
+
+ if $contact_groups {
+ Nagios_service[$real_name] { contact_groups => $contact_groups }
+ }
+ }
+}
+
diff --git a/puppet/modules/nagios/manifests/service/dns.pp b/puppet/modules/nagios/manifests/service/dns.pp
new file mode 100644
index 00000000..5ef6e3e8
--- /dev/null
+++ b/puppet/modules/nagios/manifests/service/dns.pp
@@ -0,0 +1,19 @@
+define nagios::service::dns(
+ $host_name = $::fqdn,
+ $comment = $name,
+ $check_domain = $name,
+ $ip
+){
+ if $name != $comment {
+ $check_name = "${comment}_${name}_${::hostname}"
+ } else {
+ $check_name = "${name}_${::hostname}"
+ }
+
+ nagios::service{
+ $check_name:
+ check_command => "check_dns2!${check_domain}!${ip}",
+ host_name => $host_name,
+ service_description => "check if ${::host_name} is resolving ${check_domain}";
+ }
+}
diff --git a/puppet/modules/nagios/manifests/service/dns_host.pp b/puppet/modules/nagios/manifests/service/dns_host.pp
new file mode 100644
index 00000000..d88f3735
--- /dev/null
+++ b/puppet/modules/nagios/manifests/service/dns_host.pp
@@ -0,0 +1,22 @@
+# add a special host and monitor
+# it's dns service
+define nagios::service::dns_host(
+ $check_domain,
+ $host_alias,
+ $parent,
+ $ip
+){
+ @@nagios_host{$name:
+ address => $ip,
+ alias => $host_alias,
+ use => 'generic-host',
+ parents => $parent,
+ }
+
+ nagios::service::dns{$name:
+ host_name => $name,
+ comment => 'public_ns',
+ check_domain => $check_domain,
+ ip => $ip,
+ }
+}
diff --git a/puppet/modules/nagios/manifests/service/gpgkey.pp b/puppet/modules/nagios/manifests/service/gpgkey.pp
new file mode 100644
index 00000000..df13ca88
--- /dev/null
+++ b/puppet/modules/nagios/manifests/service/gpgkey.pp
@@ -0,0 +1,49 @@
+# define a gpgkey to be watched
+define nagios::service::gpgkey(
+ $ensure = 'present',
+ $warning = '14',
+ $key_info = undef,
+ $check_interval = 60,
+){
+ validate_slength($name,40,40)
+ require ::nagios::plugins::gpg
+ $gpg_home = $nagios::plugins::gpg::gpg_home
+ $gpg_cmd = "gpg --homedir ${gpg_home}"
+
+ exec{"manage_key_${name}":
+ user => nagios,
+ group => nagios,
+ }
+ nagios::service{
+ "check_gpg_${name}":
+ ensure => $ensure;
+ }
+
+ if $ensure == 'present' {
+ Exec["manage_key_${name}"]{
+ command => "${gpg_cmd} --keyserver hkps://hkps.pool.sks-keyservers.net --keyserver-options ca-cert-file=${gpg_home}/sks-keyservers.netCA.pem --recv-keys ${name}",
+ unless => "${gpg_cmd} --list-keys ${name}",
+ before => Nagios::Service["check_gpg_${name}"],
+ }
+
+ Nagios::Service["check_gpg_${name}"]{
+ check_command => "check_gpg!${warning}!${name}",
+ check_interval => $check_interval,
+ }
+ if $key_info {
+ Nagios::Service["check_gpg_${name}"]{
+ service_description => "Keyfingerprint: ${name} - Info: ${key_info}",
+ }
+ } else {
+ Nagios::Service["check_gpg_${name}"]{
+ service_description => "Keyfingerprint: ${name}",
+ }
+ }
+ } else {
+ Exec["manage_key_${name}"]{
+ command => "${gpg_cmd} --batch --delete-key ${name}",
+ onlyif => "${gpg_cmd} --list-keys ${name}",
+ require => Nagios::Service["check_gpg_${name}"],
+ }
+ }
+}
diff --git a/puppet/modules/nagios/manifests/service/horde_login.pp b/puppet/modules/nagios/manifests/service/horde_login.pp
new file mode 100644
index 00000000..6cab59e9
--- /dev/null
+++ b/puppet/modules/nagios/manifests/service/horde_login.pp
@@ -0,0 +1,18 @@
+# a horde login check
+define nagios::service::horde_login(
+ $password,
+ $url,
+ $username = $name,
+ $ensure = 'present',
+){
+ nagios::service{
+ "horde_${name}":
+ ensure => $ensure;
+ }
+
+ if $ensure != 'absent' {
+ Nagios::Service["horde_${name}"]{
+ check_command => "check_horde_login!${url}!${username}!${password}",
+ }
+ }
+}
diff --git a/puppet/modules/nagios/manifests/service/http.pp b/puppet/modules/nagios/manifests/service/http.pp
new file mode 100644
index 00000000..b80c140e
--- /dev/null
+++ b/puppet/modules/nagios/manifests/service/http.pp
@@ -0,0 +1,54 @@
+# ssl_mode:
+# - false: only check http
+# - true: check http and https
+# - force: http is permanent redirect to https
+# - only: check only https
+define nagios::service::http(
+ $ensure = present,
+ $check_domain = 'absent',
+ $port = '80',
+ $check_url = '/',
+ $check_code = '200,301,302',
+ $use = 'generic-service',
+ $ssl_mode = false
+){
+ $real_check_domain = $check_domain ? {
+ 'absent' => $name,
+ default => $check_domain
+ }
+ if is_hash($check_code) {
+ $check_code_hash = $check_code
+ } else {
+ $check_code_hash = {
+ http => $check_code,
+ https => $check_code,
+ }
+ }
+ case $ssl_mode {
+ 'force',true,'only': {
+ nagios::service{"https_${name}":
+ ensure => $ensure,
+ use => $use,
+ check_command => "check_https_url_regex!${real_check_domain}!${check_url}!'${check_code_hash[https]}'",
+ }
+ case $ssl_mode {
+ 'force': {
+ nagios::service{"http_${name}":
+ ensure => $ensure,
+ use => $use,
+ check_command => "check_http_url_regex!${real_check_domain}!${port}!${check_url}!'301'",
+ }
+ }
+ }
+ }
+ }
+ case $ssl_mode {
+ false,true: {
+ nagios::service{"http_${name}":
+ ensure => $ensure,
+ use => $use,
+ check_command => "check_http_url_regex!${real_check_domain}!${port}!${check_url}!'${check_code_hash[http]}'",
+ }
+ }
+ }
+}
diff --git a/puppet/modules/nagios/manifests/service/imap.pp b/puppet/modules/nagios/manifests/service/imap.pp
new file mode 100644
index 00000000..45b667ab
--- /dev/null
+++ b/puppet/modules/nagios/manifests/service/imap.pp
@@ -0,0 +1,34 @@
+# check an imap service
+define nagios::service::imap(
+ $ensure = 'present',
+ $host = 'absent',
+ $port = '143',
+ $tls = true,
+ $tls_port = '993'
+){
+
+ $real_host = $host ? {
+ 'absent' => $name,
+ default => $host
+ }
+
+ $tls_ensure = $tls ? {
+ true => $ensure,
+ default => 'absent'
+ }
+ nagios::service{
+ "imap_${name}_${port}":
+ ensure => $ensure;
+ "imaps_${name}_${tls_port}":
+ ensure => $tls_ensure;
+ }
+
+ if $ensure != 'absent' {
+ Nagios::Service["imap_${name}_${port}"]{
+ check_command => "check_imap!${real_host}!${port}",
+ }
+ Nagios::Service["imaps_${name}_${tls_port}"]{
+ check_command => "check_imap_ssl!${real_host}!${tls_port}",
+ }
+ }
+}
diff --git a/puppet/modules/nagios/manifests/service/imap_login.pp b/puppet/modules/nagios/manifests/service/imap_login.pp
new file mode 100644
index 00000000..25303a3f
--- /dev/null
+++ b/puppet/modules/nagios/manifests/service/imap_login.pp
@@ -0,0 +1,22 @@
+# a imap login check
+define nagios::service::imap_login(
+ $username,
+ $password,
+ $warning = 5,
+ $critical = 10,
+ $host = $::fqdn,
+ $host_name = $::fqdn,
+ $ensure = 'present',
+){
+ nagios::service{
+ "imap_login_${name}":
+ ensure => $ensure;
+ }
+
+ if $ensure != 'absent' {
+ Nagios::Service["imap_login_${name}"]{
+ check_command => "check_imap_login!${host}!${username}!${password}!${warning}!${critical}",
+ host_name => $host_name,
+ }
+ }
+}
diff --git a/puppet/modules/nagios/manifests/service/mysql.pp b/puppet/modules/nagios/manifests/service/mysql.pp
new file mode 100644
index 00000000..9559b17c
--- /dev/null
+++ b/puppet/modules/nagios/manifests/service/mysql.pp
@@ -0,0 +1,58 @@
+# Checks a mysql instance via tcp or socket
+define nagios::service::mysql(
+ $ensure = present,
+ $check_host = 'absent',
+ $check_port = '3306',
+ $check_username = 'nagios',
+ $check_password,
+ $check_database = 'information_schema',
+ $check_warning = undef,
+ $check_critical = undef,
+ $check_health_mode = $name,
+ $check_name = undef,
+ $check_name2 = undef,
+ $check_regexp = undef,
+ $check_units = undef,
+ $check_mode = 'tcp' )
+{
+
+ if ($check_host == 'absent') {
+ fail("Please specify a hostname, ip address or socket to check a mysql instance.")
+ }
+
+ if $check_name != undef {
+ $real_check_name = "!--name $check_name"
+ }
+
+ if $check_warning != undef {
+ $real_check_warning = "!--warning $check_warning"
+ }
+
+ if $check_critical != undef {
+ $real_check_critical = "!--critical $check_critical"
+ }
+
+ case $check_mode {
+ 'tcp': {
+ if ($check_host == 'localhost') {
+ $real_check_host = '127.0.0.1'
+ }
+ else {
+ $real_check_host = $check_host
+ }
+ }
+ default: {
+ if ($check_host == '127.0.0.1') {
+ $real_check_host = 'localhost'
+ }
+ else {
+ $real_check_host = $check_host
+ }
+ }
+ }
+
+ nagios::service { "mysql_health_${name}":
+ ensure => $ensure,
+ check_command => "check_mysql_health!${real_check_host}!${check_port}!${check_username}!'${check_password}'!${check_health_mode}!${check_database}${real_check_name}${real_check_warning}${real_check_critical}",
+ }
+}
diff --git a/puppet/modules/nagios/manifests/service/ntp.pp b/puppet/modules/nagios/manifests/service/ntp.pp
new file mode 100644
index 00000000..b3cde2ab
--- /dev/null
+++ b/puppet/modules/nagios/manifests/service/ntp.pp
@@ -0,0 +1,9 @@
+# manifests/service/ntp.pp
+
+class nagios::service::ntp {
+ nagios::service{ "check_ntp":
+ check_command => "check_ntp_time",
+ host_name => $::fqdn,
+ }
+}
+
diff --git a/puppet/modules/nagios/manifests/service/passive.pp b/puppet/modules/nagios/manifests/service/passive.pp
new file mode 100644
index 00000000..f3df1e8b
--- /dev/null
+++ b/puppet/modules/nagios/manifests/service/passive.pp
@@ -0,0 +1,18 @@
+define nagios::service::passive(
+ $ensure = present,
+ $notification_interval = '',
+ $notification_period = '',
+ $notification_options = '',
+ $contact_groups = ''
+) {
+
+ nagios::service { $name:
+ use => 'passive-service',
+ check_command => 'check_dummy!0',
+ notification_interval => $notification_interval,
+ notification_period => $notification_period,
+ notification_options => $notification_options,
+ contact_groups => $contact_groups,
+ }
+
+}
diff --git a/puppet/modules/nagios/manifests/service/ping.pp b/puppet/modules/nagios/manifests/service/ping.pp
new file mode 100644
index 00000000..f1c8d878
--- /dev/null
+++ b/puppet/modules/nagios/manifests/service/ping.pp
@@ -0,0 +1,9 @@
+define nagios::service::ping(
+ $ensure = present,
+ $ping_rate = '!100.0,20%!500.0,60%'
+){
+ nagios::service{ "check_ping":
+ ensure => $ensure,
+ check_command => "check_ping${ping_rate}",
+ }
+}
diff --git a/puppet/modules/nagios/manifests/service/pop.pp b/puppet/modules/nagios/manifests/service/pop.pp
new file mode 100644
index 00000000..9ec4aec1
--- /dev/null
+++ b/puppet/modules/nagios/manifests/service/pop.pp
@@ -0,0 +1,32 @@
+define nagios::service::pop(
+ $ensure = 'present',
+ $host = 'absent',
+ $port = '110',
+ $tls = true,
+ $tls_port = '995'
+){
+
+ $real_host = $host ? {
+ 'absent' => $name,
+ default => $host
+ }
+
+ nagios::service{
+ "pop_${name}_${port}":
+ ensure => $ensure;
+ "pops_${name}_${tls_port}":
+ ensure => $tls ? {
+ true => $ensure,
+ default => 'absent'
+ };
+ }
+
+ if $ensure != 'absent' {
+ Nagios::Service["pop_${name}_${port}"]{
+ check_command => "check_pop3!${real_host}!${port}",
+ }
+ Nagios::Service["pops_${name}_${tls_port}"]{
+ check_command => "check_pop3_ssl!${real_host}!${tls_port}",
+ }
+ }
+}
diff --git a/puppet/modules/nagios/manifests/service/pop3_login.pp b/puppet/modules/nagios/manifests/service/pop3_login.pp
new file mode 100644
index 00000000..74535289
--- /dev/null
+++ b/puppet/modules/nagios/manifests/service/pop3_login.pp
@@ -0,0 +1,22 @@
+# a pop3 login check
+define nagios::service::pop3_login(
+ $username,
+ $password,
+ $warning = 5,
+ $critical = 10,
+ $host = $::fqdn,
+ $host_name = $::fqdn,
+ $ensure = 'present',
+){
+ nagios::service{
+ "pop3_login_${name}":
+ ensure => $ensure;
+ }
+
+ if $ensure != 'absent' {
+ Nagios::Service["pop3_login_${name}"]{
+ check_command => "check_pop3_login!${host}!${username}!${password}!${warning}!${critical}",
+ host_name => $host_name,
+ }
+ }
+}
diff --git a/puppet/modules/nagios/manifests/service/smtp.pp b/puppet/modules/nagios/manifests/service/smtp.pp
new file mode 100644
index 00000000..14237a9e
--- /dev/null
+++ b/puppet/modules/nagios/manifests/service/smtp.pp
@@ -0,0 +1,50 @@
+# true:
+# - true : check tls and plain connect *defualt*
+# - false : check plain connection only
+# cert_days:
+# If tls is used add an additionl check
+# to check for validity for cert.
+# - 'absent' : do not execute that check
+# - INTEGER : Minimum number of days a certificate
+# has to be valid. Default: 10
+define nagios::service::smtp(
+ $ensure = 'present',
+ $host = 'absent',
+ $port = '25',
+ $tls = true,
+ $cert_days = 10
+){
+ $real_host = $host ? {
+ 'absent' => $name,
+ default => $host
+ }
+
+ nagios::service{
+ "smtp_${name}_${port}":
+ ensure => $ensure;
+ "smtp_tls_${name}_${port}":
+ ensure => $tls ? {
+ true => $ensure,
+ default => 'absent'
+ };
+ "smtp_tls_cert_${name}_${port}":
+ ensure => $cert_days ? {
+ 'absent' => 'absent',
+ default => $ensure
+ };
+ }
+
+ if $ensure != 'absent' {
+ Nagios::Service["smtp_${name}_${port}"]{
+ check_command => "check_smtp!${real_host}!${port}",
+ }
+ Nagios::Service["smtp_tls_${name}_${port}"]{
+ check_command => "check_smtp_tls!${real_host}!${port}",
+ }
+ if $cert_days != 'absent' {
+ Nagios::Service["smtp_tls_cert_${name}_${port}"]{
+ check_command => "check_smtp_cert!${real_host}!${port}!${cert_days}",
+ }
+ }
+ }
+}
diff --git a/puppet/modules/nagios/manifests/service/ssmtp.pp b/puppet/modules/nagios/manifests/service/ssmtp.pp
new file mode 100644
index 00000000..b05678a6
--- /dev/null
+++ b/puppet/modules/nagios/manifests/service/ssmtp.pp
@@ -0,0 +1,32 @@
+define nagios::service::ssmtp(
+ $ensure = 'present',
+ $host = 'absent',
+ $port = '465',
+ $cert_days = 10
+){
+ $real_host = $host ? {
+ 'absent' => $name,
+ default => $host
+ }
+
+ nagios::service{
+ "ssmtp_${name}_${port}":
+ ensure => $ensure;
+ "ssmtp_cert_${name}_${port}":
+ ensure => $cert_days ? {
+ 'absent' => 'absent',
+ default => $ensure
+ };
+ }
+
+ if $ensure != 'absent' {
+ Nagios::Service["ssmtp_${name}_${port}"]{
+ check_command => "check_ssmtp!${real_host}!${port}",
+ }
+ if $cert_days != 'absent' {
+ Nagios::Service["ssmtp_cert_${name}_${port}"]{
+ check_command => "check_ssmtp_cert!${real_host}!${port}!${cert_days}",
+ }
+ }
+ }
+}
diff --git a/puppet/modules/nagios/manifests/storeconfigs.pp b/puppet/modules/nagios/manifests/storeconfigs.pp
new file mode 100644
index 00000000..96c30dd5
--- /dev/null
+++ b/puppet/modules/nagios/manifests/storeconfigs.pp
@@ -0,0 +1,61 @@
+# collect exported resources when using 'storeconfigs => true'
+class nagios::storeconfigs {
+
+ Nagios_command <<||>>
+ Nagios_contactgroup <<||>>
+ Nagios_contact <<||>>
+ Nagios_hostdependency <<||>>
+ Nagios_hostescalation <<||>>
+ Nagios_hostextinfo <<||>>
+ Nagios_hostgroup <<||>>
+ Nagios_host <<||>>
+ Nagios_servicedependency <<||>>
+ Nagios_serviceescalation <<||>>
+ Nagios_servicegroup <<||>>
+ Nagios_serviceextinfo <<||>>
+ Nagios_service <<||>>
+ Nagios_timeperiod <<||>>
+
+ Nagios_command <||> {
+ notify => Service['nagios'],
+ }
+ Nagios_contact <||> {
+ notify => Service['nagios'],
+ }
+ Nagios_contactgroup <||> {
+ notify => Service['nagios'],
+ }
+ Nagios_host <||> {
+ notify => Service['nagios'],
+ }
+ Nagios_hostdependency <||> {
+ notify => Service['nagios'],
+ }
+ Nagios_hostescalation <||> {
+ notify => Service['nagios'],
+ }
+ Nagios_hostextinfo <||> {
+ notify => Service['nagios'],
+ }
+ Nagios_hostgroup <||> {
+ notify => Service['nagios'],
+ }
+ Nagios_service <||> {
+ notify => Service['nagios'],
+ }
+ Nagios_servicegroup <||> {
+ notify => Service['nagios'],
+ }
+ Nagios_servicedependency <||> {
+ notify => Service['nagios'],
+ }
+ Nagios_serviceescalation <||> {
+ notify => Service['nagios'],
+ }
+ Nagios_serviceextinfo <||> {
+ notify => Service['nagios'],
+ }
+ Nagios_timeperiod <||> {
+ notify => Service['nagios'],
+ }
+}
diff --git a/puppet/modules/nagios/manifests/stored_config.pp b/puppet/modules/nagios/manifests/stored_config.pp
new file mode 100644
index 00000000..5afda04f
--- /dev/null
+++ b/puppet/modules/nagios/manifests/stored_config.pp
@@ -0,0 +1,19 @@
+class nagios::stored_config {
+ # collect exported resources
+
+ Nagios_command <<||>>
+ Nagios_contactgroup <<||>>
+ Nagios_contact <<||>>
+ Nagios_hostdependency <<||>>
+ Nagios_hostescalation <<||>>
+ Nagios_hostextinfo <<||>>
+ Nagios_hostgroup <<||>>
+ Nagios_host <<||>>
+ Nagios_servicedependency <<||>>
+ Nagios_serviceescalation <<||>>
+ Nagios_servicegroup <<||>>
+ Nagios_serviceextinfo <<||>>
+ Nagios_service <<||>>
+ Nagios_timeperiod <<||>>
+
+}
diff --git a/puppet/modules/nagios/manifests/target.pp b/puppet/modules/nagios/manifests/target.pp
new file mode 100644
index 00000000..760d7d47
--- /dev/null
+++ b/puppet/modules/nagios/manifests/target.pp
@@ -0,0 +1,32 @@
+# a simple nagios target to monitor
+class nagios::target(
+ $parents = 'absent',
+ $address = $::ipaddress,
+ $nagios_alias = false,
+ $hostgroups = 'absent',
+ $use = 'generic-host',
+){
+ @@nagios_host { $::fqdn:
+ address => $address,
+ use => $use,
+ }
+ # Watch out with using aliases: they need to be unique throughout *all*
+ # resources in a given host's catalogue.
+ if $nagios_alias {
+ Nagios_host[$::fqdn]{
+ alias => $nagios_alias
+ }
+ }
+
+ if ($parents != 'absent') {
+ Nagios_host[$::fqdn]{
+ parents => $parents
+ }
+ }
+
+ if ($hostgroups != 'absent') {
+ Nagios_host[$::fqdn]{
+ hostgroups => $hostgroups
+ }
+ }
+}
diff --git a/puppet/modules/nagios/manifests/target/fqdn.pp b/puppet/modules/nagios/manifests/target/fqdn.pp
new file mode 100644
index 00000000..31fc4b71
--- /dev/null
+++ b/puppet/modules/nagios/manifests/target/fqdn.pp
@@ -0,0 +1,12 @@
+# monitor a host by fqdn
+class nagios::target::fqdn(
+ $address = $::fqdn,
+ $hostgroups = 'absent',
+ $parents = 'absent'
+) {
+ class{'nagios::target':
+ address => $address,
+ hostgroups => $hostgroups,
+ parents => $parents
+ }
+}
diff --git a/puppet/modules/nagios/templates/irc_bot/CentOS/nagios-nsa.sh.erb b/puppet/modules/nagios/templates/irc_bot/CentOS/nagios-nsa.sh.erb
new file mode 100644
index 00000000..0f9f87b4
--- /dev/null
+++ b/puppet/modules/nagios/templates/irc_bot/CentOS/nagios-nsa.sh.erb
@@ -0,0 +1,104 @@
+#!/bin/sh
+#
+# nagios-nsa - manage nagios irc bot
+#
+# chkconfig: - 99 01
+# description: Nagios Simple IRC Agent
+
+### BEGIN INIT INFO
+# Provides: nagios-nsa
+# Required-Start: $nagios
+# Required-Stop: $nagios
+# Default-Start: 2 3 4 5
+# Default-Stop: 1 6 0
+# Short-Description: Nagios Simple IRC Agent
+### END INIT INFO
+
+# Source function library.
+. /etc/rc.d/init.d/functions
+
+exec="/usr/local/bin/riseup-nagios-server.pl"
+prog="nsa"
+PIDFILE=<%= scope.lookupvar('nagios::irc_bot::real_nsa_pidfile') %>
+SOCKFILE=<%= scope.lookupvar('nagios::irc_bot::real_nsa_socket') %>
+
+[ -e /etc/sysconfig/$prog ] && . /etc/sysconfig/$prog
+
+lockfile=/var/lock/subsys/$prog
+mkdir -p /var/run/nagios-nsa 2>/dev/null
+chown nagios /var/run/nagios-nsa
+
+start() {
+ [ -x $exec ] || exit 5
+ [ -f $config ] || exit 6
+ echo -n $"Starting $prog: "
+ daemon --pidfile $PIDFILE --user nagios /usr/local/bin/riseup-nagios-server.pl
+ retval=$?
+ echo
+ [ $retval -eq 0 ] && touch $lockfile
+ return $retval
+}
+
+stop() {
+ echo -n $"Stopping $prog: "
+ killproc -p $PIDFILE $prog
+ retval=$?
+ echo
+ [ $retval -eq 0 ] && rm -f $lockfile
+ return $retval
+}
+
+restart() {
+ stop
+ start
+}
+
+reload() {
+ restart
+}
+
+force_reload() {
+ restart
+}
+
+rh_status() {
+ # run checks to determine if the service is running or use generic status
+ status -p $PIDFILE $prog
+}
+
+rh_status_q() {
+ rh_status >/dev/null 2>&1
+}
+
+
+case "$1" in
+ start)
+ rh_status_q && exit 0
+ $1
+ ;;
+ stop)
+ rh_status_q || exit 0
+ $1
+ ;;
+ restart)
+ $1
+ ;;
+ reload)
+ rh_status_q || exit 7
+ $1
+ ;;
+ force-reload)
+ force_reload
+ ;;
+ status)
+ rh_status
+ ;;
+ condrestart|try-restart)
+ rh_status_q || exit 0
+ restart
+ ;;
+ *)
+ echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}"
+ exit 2
+esac
+exit $?
diff --git a/puppet/modules/nagios/templates/irc_bot/Debian/nagios-nsa.sh.erb b/puppet/modules/nagios/templates/irc_bot/Debian/nagios-nsa.sh.erb
new file mode 100644
index 00000000..43c0e794
--- /dev/null
+++ b/puppet/modules/nagios/templates/irc_bot/Debian/nagios-nsa.sh.erb
@@ -0,0 +1,72 @@
+#! /bin/sh
+
+### BEGIN INIT INFO
+# Provides: nagios-nsa
+# Required-Start: $remote_fs $syslog nagios3
+# Required-Stop: $remote_fs $syslog nagios3
+# Default-Start: 2 3 4 5
+# Default-Stop: 1 6 0
+# Short-Description: Nagios Simple IRC Agent
+### END INIT INFO
+
+PIDFILE=<%= scope.lookupvar('nagios::irc_bot::real_nsa_pidfile') %>
+SOCKFILE=<%= scope.lookupvar('nagios::irc_bot::real_nsa_socket') %>
+
+. /lib/lsb/init-functions
+
+start() {
+ log_daemon_msg "Starting nagios IRC bot" "nagios-nsa"
+ if start-stop-daemon --start --quiet --oknodo --pidfile $PIDFILE --user nagios --chuid nagios --exec /usr/local/bin/riseup-nagios-server.pl; then
+ log_end_msg 0
+ else
+ log_end_msg 1
+ fi
+}
+
+stop () {
+ log_daemon_msg "Stopping nagios IRC bot" "nagios-nsa"
+ if start-stop-daemon --stop --quiet --pidfile $PIDFILE; then
+ log_end_msg 0
+ else
+ log_end_msg 1
+ fi
+}
+
+remove_socket() {
+ [ -e $SOCKFILE ] && rm $SOCKFILE
+}
+
+cleanup() {
+ if [ -r $PIDFILE ]; then
+ ps -p `cat $PIDFILE` | grep -v 'PID' || {
+ echo "not running"
+ remove_socket
+ }
+ else
+ echo "no pid file"
+ remove_socket
+ fi
+}
+
+case $1
+in
+ start)
+ cleanup
+ start
+ ;;
+ stop)
+ stop
+ ;;
+ restart)
+ stop
+ cleanup
+ start
+ ;;
+ status)
+ status_of_proc -p $PIDFILE /usr/local/bin/riseup-nagios-server.pl && exit 0 || exit $?
+ ;;
+ *)
+ log_action_msg "Usage: /etc/init.d/nagios-nsa {start|stop|restart|status}"
+ exit 1
+esac
+
diff --git a/puppet/modules/nagios/templates/irc_bot/nsa.cfg.erb b/puppet/modules/nagios/templates/irc_bot/nsa.cfg.erb
new file mode 100644
index 00000000..c4091e8a
--- /dev/null
+++ b/puppet/modules/nagios/templates/irc_bot/nsa.cfg.erb
@@ -0,0 +1,15 @@
+%Nsa = (
+ 'socket' => '<%= scope.lookupvar('nagios::irc_bot::real_nsa_socket') %>',
+ 'server' => '<%= scope.lookupvar('nagios::irc_bot::nsa_server') %>',
+ 'port' => '<%= scope.lookupvar('nagios::irc_bot::nsa_port') %>',
+ 'nickname' => '<%= scope.lookupvar('nagios::irc_bot::nsa_nickname') %>',
+ 'password' => '<%= scope.lookupvar('nagios::irc_bot::nsa_password') %>',
+ # this needs libio-socket-ssl-perl
+ # doesn't actually works because Net::IRC is braindead and tries to use IO::Socket::SSL->read/write instead of the builtin print, see http://search.cpan.org/dist/IO-Socket-SSL/SSL.pm
+ #'SSL' => 0,
+ 'channel' => '<%= scope.lookupvar('nagios::irc_bot::nsa_channel') %>',
+ 'pidfile' => '<%= scope.lookupvar('nagios::irc_bot::real_nsa_pidfile') %>', # set to undef to disable
+ 'realname' => '<%= scope.lookupvar('nagios::irc_bot::nsa_realname') %>',
+ 'usenotices' => '<%= scope.lookupvar('nagios::irc_bot::nsa_usenotices') %>',
+ 'commandfile' => '<%= scope.lookupvar('nagios::irc_bot::real_nsa_commandfile') %>',
+);
diff --git a/puppet/modules/nagios/templates/nrpe/nrpe.cfg b/puppet/modules/nagios/templates/nrpe/nrpe.cfg
new file mode 100644
index 00000000..d4ad9a4d
--- /dev/null
+++ b/puppet/modules/nagios/templates/nrpe/nrpe.cfg
@@ -0,0 +1,203 @@
+#############################################################################
+# Sample NRPE Config File
+# Written by: Ethan Galstad (nagios@nagios.org)
+#
+# Last Modified: 02-23-2006
+#
+# NOTES:
+# This is a sample configuration file for the NRPE daemon. It needs to be
+# located on the remote host that is running the NRPE daemon, not the host
+# from which the check_nrpe client is being executed.
+#############################################################################
+
+
+# PID FILE
+# The name of the file in which the NRPE daemon should write it's process ID
+# number. The file is only written if the NRPE daemon is started by the root
+# user and is running in standalone mode.
+
+pid_file=<%= @pid_file %>
+
+
+
+# PORT NUMBER
+# Port number we should wait for connections on.
+# NOTE: This must be a non-priviledged port (i.e. > 1024).
+# NOTE: This option is ignored if NRPE is running under either inetd or xinetd
+
+server_port=5666
+
+
+
+# SERVER ADDRESS
+# Address that nrpe should bind to in case there are more than one interface
+# and you do not want nrpe to bind on all interfaces.
+# NOTE: This option is ignored if NRPE is running under either inetd or xinetd
+
+<%- if not @server_address.to_s.empty? then %>
+server_address=<%= @server_address %>
+<%- end %>
+
+
+# NRPE USER
+# This determines the effective user that the NRPE daemon should run as.
+# You can either supply a username or a UID.
+#
+# NOTE: This option is ignored if NRPE is running under either inetd or xinetd
+
+nrpe_user=nagios
+
+
+
+# NRPE GROUP
+# This determines the effective group that the NRPE daemon should run as.
+# You can either supply a group name or a GID.
+#
+# NOTE: This option is ignored if NRPE is running under either inetd or xinetd
+
+nrpe_group=nagios
+
+
+
+# ALLOWED HOST ADDRESSES
+# This is an optional comma-delimited list of IP address or hostnames
+# that are allowed to talk to the NRPE daemon.
+#
+# Note: The daemon only does rudimentary checking of the client's IP
+# address. I would highly recommend adding entries in your /etc/hosts.allow
+# file to allow only the specified host to connect to the port
+# you are running this daemon on.
+#
+# NOTE: This option is ignored if NRPE is running under either inetd or xinetd
+
+<%- if @allowed_hosts.to_s.empty? then %>
+allowed_hosts=127.0.0.1
+<%- else %>
+allowed_hosts=127.0.0.1,<%= @allowed_hosts %>
+<%- end %>
+
+# COMMAND ARGUMENT PROCESSING
+# This option determines whether or not the NRPE daemon will allow clients
+# to specify arguments to commands that are executed. This option only works
+# if the daemon was configured with the --enable-command-args configure script
+# option.
+#
+# *** ENABLING THIS OPTION IS A SECURITY RISK! ***
+# Read the SECURITY file for information on some of the security implications
+# of enabling this variable.
+#
+# Values: 0=do not allow arguments, 1=allow command arguments
+
+dont_blame_nrpe=<%= @dont_blame %>
+
+
+# COMMAND PREFIX
+# This option allows you to prefix all commands with a user-defined string.
+# A space is automatically added between the specified prefix string and the
+# command line from the command definition.
+#
+# *** THIS EXAMPLE MAY POSE A POTENTIAL SECURITY RISK, SO USE WITH CAUTION! ***
+# Usage scenario:
+# Execute restricted commmands using sudo. For this to work, you need to add
+# the nagios user to your /etc/sudoers. An example entry for alllowing
+# execution of the plugins from might be:
+#
+# nagios ALL=(ALL) NOPASSWD: /usr/lib/nagios/plugins/
+#
+# This lets the nagios user run all commands in that directory (and only them)
+# without asking for a password. If you do this, make sure you don't give
+# random users write access to that directory or its contents!
+
+# command_prefix=/usr/bin/sudo
+
+
+
+# DEBUGGING OPTION
+# This option determines whether or not debugging messages are logged to the
+# syslog facility.
+# Values: 0=debugging off, 1=debugging on
+
+debug=0
+
+
+
+# COMMAND TIMEOUT
+# This specifies the maximum number of seconds that the NRPE daemon will
+# allow plugins to finish executing before killing them off.
+
+command_timeout=60
+
+
+
+# WEEK RANDOM SEED OPTION
+# This directive allows you to use SSL even if your system does not have
+# a /dev/random or /dev/urandom (on purpose or because the necessary patches
+# were not applied). The random number generator will be seeded from a file
+# which is either a file pointed to by the environment valiable $RANDFILE
+# or $HOME/.rnd. If neither exists, the pseudo random number generator will
+# be initialized and a warning will be issued.
+# Values: 0=only seed from /dev/[u]random, 1=also seed from weak randomness
+
+#allow_weak_random_seed=1
+
+
+
+# INCLUDE CONFIG FILE
+# This directive allows you to include definitions from an external config file.
+
+#include=<somefile.cfg>
+
+
+
+# INCLUDE CONFIG DIRECTORY
+# This directive allows you to include definitions from config files (with a
+# .cfg extension) in one or more directories (with recursion).
+
+#include_dir=<somedirectory>
+#include_dir=<someotherdirectory>
+include_dir=<%= @cfg_dir %>/nrpe.d
+
+
+
+# COMMAND DEFINITIONS
+# Command definitions that this daemon will run. Definitions
+# are in the following format:
+#
+# command[<command_name>]=<command_line>
+#
+# When the daemon receives a request to return the results of <command_name>
+# it will execute the command specified by the <command_line> argument.
+#
+# Unlike Nagios, the command line cannot contain macros - it must be
+# typed exactly as it should be executed.
+#
+# Note: Any plugins that are used in the command lines must reside
+# on the machine that this daemon is running on! The examples below
+# assume that you have plugins installed in a /usr/local/nagios/libexec
+# directory. Also note that you will have to modify the definitions below
+# to match the argument format the plugins expect. Remember, these are
+# examples only!
+
+# The following examples use hardcoded command arguments...
+
+#command[check_users]=/usr/lib/nagios/plugins/check_users -w 5 -c 10
+#command[check_load]=/usr/lib/nagios/plugins/check_load -w 15,10,5 -c 30,25,20
+#command[check_disk1]=/usr/lib/nagios/plugins/check_disk -w 20 -c 10 -p /dev/hda1
+#command[check_disk2]=/usr/lib/nagios/plugins/check_disk -w 20 -c 10 -p /dev/hdb1
+#command[check_zombie_procs]=/usr/lib/nagios/plugins/check_procs -w 5 -c 10 -s Z
+#command[check_total_procs]=/usr/lib/nagios/plugins/check_procs -w 150 -c 200
+
+# The following examples allow user-supplied arguments and can
+# only be used if the NRPE daemon was compiled with support for
+# command arguments *AND* the dont_blame_nrpe directive in this
+# config file is set to '1'...
+
+#command[check_users]=/usr/lib/nagios/plugins/check_users -w $ARG1$ -c $ARG2$
+#command[check_load]=/usr/lib/nagios/plugins/check_load -w $ARG1$ -c $ARG2$
+#command[check_disk]=/usr/lib/nagios/plugins/check_disk -w $ARG1$ -c $ARG2$ -p $ARG3$
+#command[check_procs]=/usr/lib/nagios/plugins/check_procs -w $ARG1$ -c $ARG2$ -s $ARG3$
+
+#
+# local configuration:
+# if you'd prefer, you can instead place directives here
+
diff --git a/puppet/modules/nagios/templates/nrpe/nrpe_command.erb b/puppet/modules/nagios/templates/nrpe/nrpe_command.erb
new file mode 100644
index 00000000..99f4601b
--- /dev/null
+++ b/puppet/modules/nagios/templates/nrpe/nrpe_command.erb
@@ -0,0 +1,2 @@
+# generated by puppet, do not edit
+command[<%= name -%>]=<%= command_line %>
diff --git a/puppet/modules/ntp/.fixtures.yml b/puppet/modules/ntp/.fixtures.yml
new file mode 100644
index 00000000..a4b98014
--- /dev/null
+++ b/puppet/modules/ntp/.fixtures.yml
@@ -0,0 +1,5 @@
+fixtures:
+ repositories:
+ "stdlib": "git://github.com/puppetlabs/puppetlabs-stdlib.git"
+ symlinks:
+ "ntp": "#{source_dir}"
diff --git a/puppet/modules/ntp/.gitignore b/puppet/modules/ntp/.gitignore
new file mode 100644
index 00000000..49cf4650
--- /dev/null
+++ b/puppet/modules/ntp/.gitignore
@@ -0,0 +1,3 @@
+pkg/
+metadata.json
+Gemfile.lock
diff --git a/puppet/modules/ntp/.nodeset.yml b/puppet/modules/ntp/.nodeset.yml
new file mode 100644
index 00000000..cbd0d57b
--- /dev/null
+++ b/puppet/modules/ntp/.nodeset.yml
@@ -0,0 +1,35 @@
+---
+default_set: 'centos-64-x64'
+sets:
+ 'centos-59-x64':
+ nodes:
+ "main.foo.vm":
+ prefab: 'centos-59-x64'
+ 'centos-64-x64':
+ nodes:
+ "main.foo.vm":
+ prefab: 'centos-64-x64'
+ 'fedora-18-x64':
+ nodes:
+ "main.foo.vm":
+ prefab: 'fedora-18-x64'
+ 'debian-607-x64':
+ nodes:
+ "main.foo.vm":
+ prefab: 'debian-607-x64'
+ 'debian-70rc1-x64':
+ nodes:
+ "main.foo.vm":
+ prefab: 'debian-70rc1-x64'
+ 'ubuntu-server-10044-x64':
+ nodes:
+ "main.foo.vm":
+ prefab: 'ubuntu-server-10044-x64'
+ 'ubuntu-server-12042-x64':
+ nodes:
+ "main.foo.vm":
+ prefab: 'ubuntu-server-12042-x64'
+ 'sles-11sp1-x64':
+ nodes:
+ "main.foo.vm":
+ prefab: 'sles-11sp1-x64'
diff --git a/puppet/modules/ntp/.travis.yml b/puppet/modules/ntp/.travis.yml
new file mode 100644
index 00000000..e9f0e84b
--- /dev/null
+++ b/puppet/modules/ntp/.travis.yml
@@ -0,0 +1,40 @@
+---
+branches:
+ only:
+ - master
+language: ruby
+bundler_args: --without development
+script: "bundle exec rake spec SPEC_OPTS='--format documentation'"
+after_success:
+ - git clone -q git://github.com/puppetlabs/ghpublisher.git .forge-releng
+ - .forge-releng/publish
+rvm:
+- 1.8.7
+- 1.9.3
+- 2.0.0
+env:
+ matrix:
+ - PUPPET_GEM_VERSION="~> 2.7.0"
+ - PUPPET_GEM_VERSION="~> 3.0.0"
+ - PUPPET_GEM_VERSION="~> 3.1.0"
+ - PUPPET_GEM_VERSION="~> 3.2.0"
+ global:
+ - PUBLISHER_LOGIN=puppetlabs
+ - secure: |-
+ ZiIkYd9+CdPzpwSjFPnVkCx1FIlipxpbdyD33q94h2Tj5zXjNb1GXizVy0NR
+ kVxGhU5Ld8y9z8DTqKRgCI1Yymg3H//OU++PKLOQj/X5juWVR4URBNPeBOzu
+ IJBDl1MADKA4i1+jAZPpz4mTvTtKS4pWKErgCSmhSfsY1hs7n6c=
+matrix:
+ exclude:
+ - rvm: 1.9.3
+ env: PUPPET_GEM_VERSION="~> 2.7.0"
+ - rvm: 2.0.0
+ env: PUPPET_GEM_VERSION="~> 2.7.0"
+ - rvm: 2.0.0
+ env: PUPPET_GEM_VERSION="~> 3.0.0"
+ - rvm: 2.0.0
+ env: PUPPET_GEM_VERSION="~> 3.1.0"
+ - rvm: 1.8.7
+ env: PUPPET_GEM_VERSION="~> 3.2.0"
+notifications:
+ email: false
diff --git a/puppet/modules/ntp/CHANGELOG b/puppet/modules/ntp/CHANGELOG
new file mode 100644
index 00000000..8be6c4e0
--- /dev/null
+++ b/puppet/modules/ntp/CHANGELOG
@@ -0,0 +1,61 @@
+2013-07-31 - Version 2.0.0
+
+Summary:
+
+The 2.0 release focuses on merging all the distro specific
+templates into a single reusable template across all platforms.
+
+To aid in that goal we now allow you to change the driftfile,
+ntp keys, and perferred_servers.
+
+Backwards-incompatible changes:
+
+As all the distro specific templates have been removed and a
+unified one created you may be missing functionality you
+previously relied on. Please test carefully before rolling
+out globally.
+
+Configuration directives that might possibly be affected:
+- `filegen`
+- `fudge` (for virtual machines)
+- `keys`
+- `logfile`
+- `restrict`
+- `restrictkey`
+- `statistics`
+- `trustedkey`
+
+Features:
+- All templates merged into a single template.
+- NTP Keys support added.
+- Add preferred servers support.
+- Parameters in `ntp` class:
+ - `driftfile`: path for the ntp driftfile.
+ - `keys_enable`: Enable NTP keys feature.
+ - `keys_file`: Path for the NTP keys file.
+ - `keys_trusted`: Which keys to trust.
+ - `keys_controlkey`: Which key to use for the control key.
+ - `keys_requestkey`: Which key to use for the request key.
+ - `preferred_servers`: Array of servers to prefer.
+ - `restrict`: Array of restriction options to apply.
+
+2013-07-15 - Version 1.0.1
+Bugfixes:
+- Fix deprecated warning in `autoupdate` parameter.
+- Correctly quote is_virtual fact.
+
+2013-07-08 - Version 1.0.0
+Features:
+- Completely refactored to split across several classes.
+- rspec-puppet tests rewritten to cover more options.
+- rspec-system tests added.
+- ArchLinux handled via osfamily instead of special casing.
+- parameters in `ntp` class:
+ - `autoupdate`: deprecated in favor of directly setting package_ensure.
+ - `panic`: set to false if you wish to allow large clock skews.
+
+2011-11-10 Dan Bode <dan@puppetlabs.com> - 0.0.4
+Add Amazon Linux as a supported platform
+Add unit tests
+2011-06-16 Jeff McCune <jeff@puppetlabs.com> - 0.0.3
+Initial release under puppetlabs
diff --git a/puppet/modules/ntp/CONTRIBUTING.md b/puppet/modules/ntp/CONTRIBUTING.md
new file mode 100644
index 00000000..a2b1d77b
--- /dev/null
+++ b/puppet/modules/ntp/CONTRIBUTING.md
@@ -0,0 +1,9 @@
+Puppet Labs modules on the Puppet Forge are open projects, and community contributions
+are essential for keeping them great. We can’t access the huge number of platforms and
+myriad of hardware, software, and deployment configurations that Puppet is intended to serve.
+
+We want to keep it as easy as possible to contribute changes so that our modules work
+in your environment. There are a few guidelines that we need contributors to follow so
+that we can have a chance of keeping on top of things.
+
+You can read the complete module contribution guide [on the Puppet Labs wiki.](http://projects.puppetlabs.com/projects/module-site/wiki/Module_contributing)
diff --git a/puppet/modules/ntp/Gemfile b/puppet/modules/ntp/Gemfile
new file mode 100644
index 00000000..4e733308
--- /dev/null
+++ b/puppet/modules/ntp/Gemfile
@@ -0,0 +1,19 @@
+source 'https://rubygems.org'
+
+group :development, :test do
+ gem 'rake', :require => false
+ gem 'puppetlabs_spec_helper', :require => false
+ gem 'rspec-system-puppet', :require => false
+ gem 'puppet-lint', :require => false
+ gem 'serverspec', :require => false
+ gem 'rspec-system-serverspec', :require => false
+ gem 'vagrant-wrapper', :require => false
+end
+
+if puppetversion = ENV['PUPPET_GEM_VERSION']
+ gem 'puppet', puppetversion, :require => false
+else
+ gem 'puppet', :require => false
+end
+
+# vim:ft=ruby
diff --git a/puppet/modules/ntp/LICENSE b/puppet/modules/ntp/LICENSE
new file mode 100644
index 00000000..57bc88a1
--- /dev/null
+++ b/puppet/modules/ntp/LICENSE
@@ -0,0 +1,202 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
diff --git a/puppet/modules/ntp/Modulefile b/puppet/modules/ntp/Modulefile
new file mode 100644
index 00000000..9610ef67
--- /dev/null
+++ b/puppet/modules/ntp/Modulefile
@@ -0,0 +1,11 @@
+name 'puppetlabs-ntp'
+version '2.0.0-rc1'
+source 'git://github.com/puppetlabs/puppetlabs-ntp'
+author 'Puppet Labs'
+license 'Apache Version 2.0'
+summary 'NTP Module'
+description 'NTP Module for Debian, Ubuntu, CentOS, RHEL, OEL, Fedora, FreeBSD, ArchLinux and Gentoo.'
+project_page 'http://github.com/puppetlabs/puppetlabs-ntp'
+
+## Add dependencies, if any:
+dependency 'puppetlabs/stdlib', '>= 0.1.6'
diff --git a/puppet/modules/ntp/README.markdown b/puppet/modules/ntp/README.markdown
new file mode 100644
index 00000000..3aedd47a
--- /dev/null
+++ b/puppet/modules/ntp/README.markdown
@@ -0,0 +1,215 @@
+#ntp
+
+####Table of Contents
+
+1. [Overview](#overview)
+2. [Module Description - What the module does and why it is useful](#module-description)
+3. [Setup - The basics of getting started with ntp](#setup)
+ * [What ntp affects](#what-ntp-affects)
+ * [Setup requirements](#setup-requirements)
+ * [Beginning with ntp](#beginning-with-ntp)
+4. [Usage - Configuration options and additional functionality](#usage)
+5. [Reference - An under-the-hood peek at what the module is doing and how](#reference)
+5. [Limitations - OS compatibility, etc.](#limitations)
+6. [Development - Guide for contributing to the module](#development)
+
+##Overview
+
+The NTP module installs, configures, and manages the ntp service.
+
+##Module Description
+
+The NTP module handles running NTP across a range of operating systems and
+distributions. Where possible we use the upstream ntp templates so that the
+results closely match what you'd get if you modified the package default conf
+files.
+
+##Setup
+
+###What ntp affects
+
+* ntp package.
+* ntp configuration file.
+* ntp service.
+
+###Beginning with ntp
+
+include '::ntp' is enough to get you up and running. If you wish to pass in
+parameters like which servers to use then you can use:
+
+```puppet
+class { '::ntp':
+ servers => [ 'ntp1.corp.com', 'ntp2.corp.com' ],
+}
+```
+
+##Usage
+
+All interaction with the ntp module can do be done through the main ntp class.
+This means you can simply toggle the options in the ntp class to get at the
+full functionality.
+
+###I just want NTP, what's the minimum I need?
+
+```puppet
+include '::ntp'
+```
+
+###I just want to tweak the servers, nothing else.
+
+```puppet
+class { '::ntp':
+ servers => [ 'ntp1.corp.com', 'ntp2.corp.com' ],
+}
+```
+
+###I'd like to make sure I restrict who can connect as well.
+
+```puppet
+class { '::ntp':
+ servers => [ 'ntp1.corp.com', 'ntp2.corp.com' ],
+ restrict => 'restrict 127.0.0.1',
+}
+```
+
+###I'd like to opt out of having the service controlled, we use another tool for that.
+
+```puppet
+class { '::ntp':
+ servers => [ 'ntp1.corp.com', 'ntp2.corp.com' ],
+ restrict => 'restrict 127.0.0.1',
+ manage_service => false,
+}
+```
+
+###Looks great! But I'd like a different template, we need to do something unique here.
+
+```puppet
+class { '::ntp':
+ servers => [ 'ntp1.corp.com', 'ntp2.corp.com' ],
+ restrict => 'restrict 127.0.0.1',
+ manage_service => false,
+ config_template => 'different/module/custom.template.erb',
+}
+```
+
+##Reference
+
+###Classes
+
+* ntp: Main class, includes all the rest.
+* ntp::install: Handles the packages.
+* ntp::config: Handles the configuration file.
+* ntp::service: Handles the service.
+
+###Parameters
+
+The following parameters are available in the ntp module
+
+####`autoupdate`
+
+Deprecated: This parameter previously determined if the ntp module should be
+automatically updated to the latest version available. Replaced by package\_
+ensure.
+
+####`config`
+
+This sets the file to write ntp configuration into.
+
+####`config_template`
+
+This determines which template puppet should use for the ntp configuration.
+
+####`driftfile`
+
+This sets the location of the driftfile for ntp.
+
+####`keys_controlkey`
+
+Which of the keys is used as the control key.
+
+####`keys_enable`
+
+Should the ntp keys functionality be enabled.
+
+####`keys_file`
+
+Location of the keys file.
+
+####`keys_requestkey`
+
+Which of the keys is used as the request key.
+
+####`package_ensure`
+
+This can be set to 'present' or 'latest' or a specific version to choose the
+ntp package to be installed.
+
+####`package_name`
+
+This determines the name of the package to install.
+
+####`panic`
+
+This determines if ntp should 'panic' in the event of a very large clock skew.
+We set this to false if you're on a virtual machine by default as they don't
+do a great job with keeping time.
+
+####`preferred_servers`
+
+List of ntp servers to prefer. Will append prefer for any server in this list
+that also appears in the servers list.
+
+####`restrict`
+
+This sets the restrict options in the ntp configuration.
+
+####`servers`
+
+This selects the servers to use for ntp peers.
+
+####`service_enable`
+
+This determines if the service should be enabled at boot.
+
+####`service_ensure`
+
+This determines if the service should be running or not.
+
+####`service_manage`
+
+This selects if puppet should manage the service in the first place.
+
+####`service_name`
+
+This selects the name of the ntp service for puppet to manage.
+
+
+##Limitations
+
+This module has been built on and tested against Puppet 2.7 and higher.
+
+The module has been tested on:
+
+* RedHat Enterprise Linux 5/6
+* Debian 6/7
+* CentOS 5/6
+* Ubuntu 12.04
+* Gentoo
+* Arch Linux
+* FreeBSD
+
+Testing on other platforms has been light and cannot be guaranteed.
+
+##Development
+
+Puppet Labs modules on the Puppet Forge are open projects, and community
+contributions are essential for keeping them great. We can’t access the
+huge number of platforms and myriad of hardware, software, and deployment
+configurations that Puppet is intended to serve.
+
+We want to keep it as easy as possible to contribute changes so that our
+modules work in your environment. There are a few guidelines that we need
+contributors to follow so that we can have a chance of keeping on top of things.
+
+You can read the complete module contribution guide [on the Puppet Labs wiki.](http://projects.puppetlabs.com/projects/module-site/wiki/Module_contributing)
diff --git a/puppet/modules/ntp/Rakefile b/puppet/modules/ntp/Rakefile
new file mode 100644
index 00000000..bb60173e
--- /dev/null
+++ b/puppet/modules/ntp/Rakefile
@@ -0,0 +1,2 @@
+require 'puppetlabs_spec_helper/rake_tasks'
+require 'rspec-system/rake_task'
diff --git a/puppet/modules/ntp/manifests/config.pp b/puppet/modules/ntp/manifests/config.pp
new file mode 100644
index 00000000..1c8963dc
--- /dev/null
+++ b/puppet/modules/ntp/manifests/config.pp
@@ -0,0 +1,23 @@
+#
+class ntp::config inherits ntp {
+
+ if $keys_enable {
+ $directory = dirname($keys_file)
+ file { $directory:
+ ensure => directory,
+ owner => 0,
+ group => 0,
+ mode => '0755',
+ recurse => true,
+ }
+ }
+
+ file { $config:
+ ensure => file,
+ owner => 0,
+ group => 0,
+ mode => '0644',
+ content => template($config_template),
+ }
+
+}
diff --git a/puppet/modules/ntp/manifests/init.pp b/puppet/modules/ntp/manifests/init.pp
new file mode 100644
index 00000000..be951187
--- /dev/null
+++ b/puppet/modules/ntp/manifests/init.pp
@@ -0,0 +1,58 @@
+class ntp (
+ $autoupdate = $ntp::params::autoupdate,
+ $config = $ntp::params::config,
+ $config_template = $ntp::params::config_template,
+ $driftfile = $ntp::params::driftfile,
+ $keys_enable = $ntp::params::keys_enable,
+ $keys_file = $ntp::params::keys_file,
+ $keys_controlkey = $ntp::params::keys_controlkey,
+ $keys_requestkey = $ntp::params::keys_requestkey,
+ $keys_trusted = $ntp::params::keys_trusted,
+ $package_ensure = $ntp::params::package_ensure,
+ $package_name = $ntp::params::package_name,
+ $panic = $ntp::params::panic,
+ $preferred_servers = $ntp::params::preferred_servers,
+ $restrict = $ntp::params::restrict,
+ $servers = $ntp::params::servers,
+ $service_enable = $ntp::params::service_enable,
+ $service_ensure = $ntp::params::service_ensure,
+ $service_manage = $ntp::params::service_manage,
+ $service_name = $ntp::params::service_name,
+) inherits ntp::params {
+
+ validate_absolute_path($config)
+ validate_string($config_template)
+ validate_absolute_path($driftfile)
+ validate_bool($keys_enable)
+ validate_re($keys_controlkey, ['^\d+$', ''])
+ validate_re($keys_requestkey, ['^\d+$', ''])
+ validate_array($keys_trusted)
+ validate_string($package_ensure)
+ validate_array($package_name)
+ validate_bool($panic)
+ validate_array($preferred_servers)
+ validate_array($restrict)
+ validate_array($servers)
+ validate_bool($service_enable)
+ validate_string($service_ensure)
+ validate_bool($service_manage)
+ validate_string($service_name)
+
+ if $autoupdate {
+ notice('autoupdate parameter has been deprecated and replaced with package_ensure. Set this to latest for the same behavior as autoupdate => true.')
+ }
+
+ include '::ntp::install'
+ include '::ntp::config'
+ include '::ntp::service'
+
+ # Anchor this as per #8040 - this ensures that classes won't float off and
+ # mess everything up. You can read about this at:
+ # http://docs.puppetlabs.com/puppet/2.7/reference/lang_containment.html#known-issues
+ anchor { 'ntp::begin': }
+ anchor { 'ntp::end': }
+
+ Anchor['ntp::begin'] -> Class['::ntp::install'] -> Class['::ntp::config']
+ ~> Class['::ntp::service'] -> Anchor['ntp::end']
+
+}
diff --git a/puppet/modules/ntp/manifests/install.pp b/puppet/modules/ntp/manifests/install.pp
new file mode 100644
index 00000000..098949c3
--- /dev/null
+++ b/puppet/modules/ntp/manifests/install.pp
@@ -0,0 +1,9 @@
+#
+class ntp::install inherits ntp {
+
+ package { 'ntp':
+ ensure => $package_ensure,
+ name => $package_name,
+ }
+
+}
diff --git a/puppet/modules/ntp/manifests/params.pp b/puppet/modules/ntp/manifests/params.pp
new file mode 100644
index 00000000..10a4fb2b
--- /dev/null
+++ b/puppet/modules/ntp/manifests/params.pp
@@ -0,0 +1,116 @@
+class ntp::params {
+
+ $autoupdate = false
+ $config_template = 'ntp/ntp.conf.erb'
+ $keys_enable = false
+ $keys_controlkey = ''
+ $keys_requestkey = ''
+ $keys_trusted = []
+ $package_ensure = 'present'
+ $preferred_servers = []
+ $restrict = [
+ 'restrict default kod nomodify notrap nopeer noquery',
+ 'restrict -6 default kod nomodify notrap nopeer noquery',
+ 'restrict 127.0.0.1',
+ 'restrict -6 ::1',
+ ]
+ $service_enable = true
+ $service_ensure = 'running'
+ $service_manage = true
+
+ # On virtual machines allow large clock skews.
+ $panic = str2bool($::is_virtual) ? {
+ true => false,
+ default => true,
+ }
+
+ case $::osfamily {
+ 'Debian': {
+ $config = '/etc/ntp.conf'
+ $keys_file = '/etc/ntp/keys'
+ $driftfile = '/var/lib/ntp/drift'
+ $package_name = [ 'ntp' ]
+ $service_name = 'ntp'
+ $servers = [
+ '0.debian.pool.ntp.org iburst',
+ '1.debian.pool.ntp.org iburst',
+ '2.debian.pool.ntp.org iburst',
+ '3.debian.pool.ntp.org iburst',
+ ]
+ }
+ 'RedHat': {
+ $config = '/etc/ntp.conf'
+ $driftfile = '/var/lib/ntp/drift'
+ $keys_file = '/etc/ntp/keys'
+ $package_name = [ 'ntp' ]
+ $service_name = 'ntpd'
+ $servers = [
+ '0.centos.pool.ntp.org',
+ '1.centos.pool.ntp.org',
+ '2.centos.pool.ntp.org',
+ ]
+ }
+ 'SuSE': {
+ $config = '/etc/ntp.conf'
+ $driftfile = '/var/lib/ntp/drift/ntp.drift'
+ $keys_file = '/etc/ntp/keys'
+ $package_name = [ 'ntp' ]
+ $service_name = 'ntp'
+ $servers = [
+ '0.opensuse.pool.ntp.org',
+ '1.opensuse.pool.ntp.org',
+ '2.opensuse.pool.ntp.org',
+ '3.opensuse.pool.ntp.org',
+ ]
+ }
+ 'FreeBSD': {
+ $config = '/etc/ntp.conf'
+ $driftfile = '/var/db/ntpd.drift'
+ $keys_file = '/etc/ntp/keys'
+ $package_name = ['net/ntp']
+ $service_name = 'ntpd'
+ $servers = [
+ '0.freebsd.pool.ntp.org iburst maxpoll 9',
+ '1.freebsd.pool.ntp.org iburst maxpoll 9',
+ '2.freebsd.pool.ntp.org iburst maxpoll 9',
+ '3.freebsd.pool.ntp.org iburst maxpoll 9',
+ ]
+ }
+ 'Archlinux': {
+ $config = '/etc/ntp.conf'
+ $driftfile = '/var/lib/ntp/drift'
+ $keys_file = '/etc/ntp/keys'
+ $package_name = [ 'ntp' ]
+ $service_name = 'ntpd'
+ $servers = [
+ '0.pool.ntp.org',
+ '1.pool.ntp.org',
+ '2.pool.ntp.org',
+ ]
+ }
+ 'Linux': {
+ # Account for distributions that don't have $::osfamily specific settings.
+ case $::operatingsystem {
+ 'Gentoo': {
+ $config = '/etc/ntp.conf'
+ $driftfile = '/var/lib/ntp/drift'
+ $keys_file = '/etc/ntp/keys'
+ $package_name = ['net-misc/ntp']
+ $service_name = 'ntpd'
+ $servers = [
+ '0.gentoo.pool.ntp.org',
+ '1.gentoo.pool.ntp.org',
+ '2.gentoo.pool.ntp.org',
+ '3.gentoo.pool.ntp.org',
+ ]
+ }
+ default: {
+ fail("The ${module_name} module is not supported on an ${::operatingsystem} distribution.")
+ }
+ }
+ }
+ default: {
+ fail("The ${module_name} module is not supported on an ${::osfamily} based system.")
+ }
+ }
+}
diff --git a/puppet/modules/ntp/manifests/service.pp b/puppet/modules/ntp/manifests/service.pp
new file mode 100644
index 00000000..3f1ada0b
--- /dev/null
+++ b/puppet/modules/ntp/manifests/service.pp
@@ -0,0 +1,18 @@
+#
+class ntp::service inherits ntp {
+
+ if ! ($service_ensure in [ 'running', 'stopped' ]) {
+ fail('service_ensure parameter must be running or stopped')
+ }
+
+ if $service_manage == true {
+ service { 'ntp':
+ ensure => $service_ensure,
+ enable => $service_enable,
+ name => $service_name,
+ hasstatus => true,
+ hasrestart => true,
+ }
+ }
+
+}
diff --git a/puppet/modules/ntp/spec/classes/ntp_spec.rb b/puppet/modules/ntp/spec/classes/ntp_spec.rb
new file mode 100644
index 00000000..6c636f40
--- /dev/null
+++ b/puppet/modules/ntp/spec/classes/ntp_spec.rb
@@ -0,0 +1,261 @@
+require 'spec_helper'
+
+describe 'ntp' do
+
+ ['Debian', 'RedHat','SuSE', 'FreeBSD', 'Archlinux', 'Gentoo'].each do |system|
+ if system == 'Gentoo'
+ let(:facts) {{ :osfamily => 'Linux', :operatingsystem => system }}
+ else
+ let(:facts) {{ :osfamily => system }}
+ end
+
+ it { should include_class('ntp::install') }
+ it { should include_class('ntp::config') }
+ it { should include_class('ntp::service') }
+
+ describe 'ntp::config on #{system}' do
+ it { should contain_file('/etc/ntp.conf').with_owner('0') }
+ it { should contain_file('/etc/ntp.conf').with_group('0') }
+ it { should contain_file('/etc/ntp.conf').with_mode('0644') }
+
+ describe 'allows template to be overridden' do
+ let(:params) {{ :config_template => 'my_ntp/ntp.conf.erb' }}
+ it { should contain_file('/etc/ntp.conf').with({
+ 'content' => /server foobar/})
+ }
+ end
+
+ describe "keys for osfamily #{system}" do
+ context "when enabled" do
+ let(:params) {{
+ :keys_enable => true,
+ :keys_file => '/etc/ntp/ntp.keys',
+ :keys_trusted => ['1', '2', '3'],
+ :keys_controlkey => '2',
+ :keys_requestkey => '3',
+ }}
+
+ it { should contain_file('/etc/ntp').with({
+ 'ensure' => 'directory'})
+ }
+ it { should contain_file('/etc/ntp.conf').with({
+ 'content' => /trustedkey 1 2 3/})
+ }
+ it { should contain_file('/etc/ntp.conf').with({
+ 'content' => /controlkey 2/})
+ }
+ it { should contain_file('/etc/ntp.conf').with({
+ 'content' => /requestkey 3/})
+ }
+ end
+ end
+
+ context "when disabled" do
+ let(:params) {{
+ :keys_enable => false,
+ :keys_file => '/etc/ntp/ntp.keys',
+ :keys_trusted => ['1', '2', '3'],
+ :keys_controlkey => '2',
+ :keys_requestkey => '3',
+ }}
+
+ it { should_not contain_file('/etc/ntp').with({
+ 'ensure' => 'directory'})
+ }
+ it { should_not contain_file('/etc/ntp.conf').with({
+ 'content' => /trustedkey 1 2 3/})
+ }
+ it { should_not contain_file('/etc/ntp.conf').with({
+ 'content' => /controlkey 2/})
+ }
+ it { should_not contain_file('/etc/ntp.conf').with({
+ 'content' => /requestkey 3/})
+ }
+ end
+
+ describe 'preferred servers' do
+ context "when set" do
+ let(:params) {{
+ :servers => ['a', 'b', 'c', 'd'],
+ :preferred_servers => ['a', 'b']
+ }}
+
+ it { should contain_file('/etc/ntp.conf').with({
+ 'content' => /server a prefer\nserver b prefer\nserver c\nserver d/})
+ }
+ end
+ context "when not set" do
+ let(:params) {{
+ :servers => ['a', 'b', 'c', 'd'],
+ :preferred_servers => []
+ }}
+
+ it { should_not contain_file('/etc/ntp.conf').with({
+ 'content' => /server a prefer/})
+ }
+ end
+ end
+
+ describe 'ntp::install on #{system}' do
+ let(:params) {{ :package_ensure => 'present', :package_name => ['ntp'], }}
+
+ it { should contain_package('ntp').with(
+ :ensure => 'present',
+ :name => 'ntp'
+ )}
+
+ describe 'should allow package ensure to be overridden' do
+ let(:params) {{ :package_ensure => 'latest', :package_name => ['ntp'] }}
+ it { should contain_package('ntp').with_ensure('latest') }
+ end
+
+ describe 'should allow the package name to be overridden' do
+ let(:params) {{ :package_ensure => 'present', :package_name => ['hambaby'] }}
+ it { should contain_package('ntp').with_name('hambaby') }
+ end
+ end
+
+ describe 'ntp::service' do
+ let(:params) {{
+ :service_manage => true,
+ :service_enable => true,
+ :service_ensure => 'running',
+ :service_name => 'ntp'
+ }}
+
+ describe 'with defaults' do
+ it { should contain_service('ntp').with(
+ :enable => true,
+ :ensure => 'running',
+ :name => 'ntp'
+ )}
+ end
+
+ describe 'service_ensure' do
+ describe 'when overridden' do
+ let(:params) {{ :service_name => 'ntp', :service_ensure => 'stopped' }}
+ it { should contain_service('ntp').with_ensure('stopped') }
+ end
+ end
+
+ describe 'service_manage' do
+ let(:params) {{
+ :service_manage => false,
+ :service_enable => true,
+ :service_ensure => 'running',
+ :service_name => 'ntpd',
+ }}
+
+ it 'when set to false' do
+ should_not contain_service('ntp').with({
+ 'enable' => true,
+ 'ensure' => 'running',
+ 'name' => 'ntpd'
+ })
+ end
+ end
+ end
+ end
+
+ context 'ntp::config' do
+ describe "for operating system Gentoo" do
+ let(:facts) {{ :operatingsystem => 'Gentoo',
+ :osfamily => 'Linux' }}
+
+ it 'uses the NTP pool servers by default' do
+ should contain_file('/etc/ntp.conf').with({
+ 'content' => /server \d.gentoo.pool.ntp.org/,
+ })
+ end
+ end
+ describe "on osfamily Debian" do
+ let(:facts) {{ :osfamily => 'debian' }}
+
+ it 'uses the debian ntp servers by default' do
+ should contain_file('/etc/ntp.conf').with({
+ 'content' => /server \d.debian.pool.ntp.org iburst/,
+ })
+ end
+ end
+
+ describe "on osfamily RedHat" do
+ let(:facts) {{ :osfamily => 'RedHat' }}
+
+ it 'uses the redhat ntp servers by default' do
+ should contain_file('/etc/ntp.conf').with({
+ 'content' => /server \d.centos.pool.ntp.org/,
+ })
+ end
+ end
+
+ describe "on osfamily SuSE" do
+ let(:facts) {{ :osfamily => 'SuSE' }}
+
+ it 'uses the opensuse ntp servers by default' do
+ should contain_file('/etc/ntp.conf').with({
+ 'content' => /server \d.opensuse.pool.ntp.org/,
+ })
+ end
+ end
+
+ describe "on osfamily FreeBSD" do
+ let(:facts) {{ :osfamily => 'FreeBSD' }}
+
+ it 'uses the freebsd ntp servers by default' do
+ should contain_file('/etc/ntp.conf').with({
+ 'content' => /server \d.freebsd.pool.ntp.org iburst maxpoll 9/,
+ })
+ end
+ end
+
+ describe "on osfamily ArchLinux" do
+ let(:facts) {{ :osfamily => 'ArchLinux' }}
+
+ it 'uses the NTP pool servers by default' do
+ should contain_file('/etc/ntp.conf').with({
+ 'content' => /server \d.pool.ntp.org/,
+ })
+ end
+ end
+
+ describe "for operating system family unsupported" do
+ let(:facts) {{
+ :osfamily => 'unsupported',
+ }}
+
+ it { expect{ subject }.to raise_error(
+ /^The ntp module is not supported on an unsupported based system./
+ )}
+ end
+ end
+
+ describe 'for virtual machines' do
+ let(:facts) {{ :osfamily => 'Archlinux',
+ :is_virtual => 'true' }}
+
+ it 'should not use local clock as a time source' do
+ should_not contain_file('/etc/ntp.conf').with({
+ 'content' => /server.*127.127.1.0.*fudge.*127.127.1.0 stratum 10/,
+ })
+ end
+
+ it 'allows large clock skews' do
+ should contain_file('/etc/ntp.conf').with({
+ 'content' => /tinker panic 0/,
+ })
+ end
+ end
+
+ describe 'for physical machines' do
+ let(:facts) {{ :osfamily => 'Archlinux',
+ :is_virtual => 'false' }}
+
+ it 'disallows large clock skews' do
+ should_not contain_file('/etc/ntp.conf').with({
+ 'content' => /tinker panic 0/,
+ })
+ end
+ end
+ end
+
+end
diff --git a/puppet/modules/ntp/spec/fixtures/modules/my_ntp/templates/ntp.conf.erb b/puppet/modules/ntp/spec/fixtures/modules/my_ntp/templates/ntp.conf.erb
new file mode 100644
index 00000000..40cf67c6
--- /dev/null
+++ b/puppet/modules/ntp/spec/fixtures/modules/my_ntp/templates/ntp.conf.erb
@@ -0,0 +1,4 @@
+#my uber ntp config
+#
+
+server foobar
diff --git a/puppet/modules/ntp/spec/spec.opts b/puppet/modules/ntp/spec/spec.opts
new file mode 100644
index 00000000..91cd6427
--- /dev/null
+++ b/puppet/modules/ntp/spec/spec.opts
@@ -0,0 +1,6 @@
+--format
+s
+--colour
+--loadby
+mtime
+--backtrace
diff --git a/puppet/modules/ntp/spec/spec_helper.rb b/puppet/modules/ntp/spec/spec_helper.rb
new file mode 100644
index 00000000..2c6f5664
--- /dev/null
+++ b/puppet/modules/ntp/spec/spec_helper.rb
@@ -0,0 +1 @@
+require 'puppetlabs_spec_helper/module_spec_helper'
diff --git a/puppet/modules/ntp/spec/spec_helper_system.rb b/puppet/modules/ntp/spec/spec_helper_system.rb
new file mode 100644
index 00000000..d5208463
--- /dev/null
+++ b/puppet/modules/ntp/spec/spec_helper_system.rb
@@ -0,0 +1,26 @@
+require 'rspec-system/spec_helper'
+require 'rspec-system-puppet/helpers'
+require 'rspec-system-serverspec/helpers'
+include Serverspec::Helper::RSpecSystem
+include Serverspec::Helper::DetectOS
+include RSpecSystemPuppet::Helpers
+
+RSpec.configure do |c|
+ # Project root
+ proj_root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
+
+ # Enable colour
+ c.tty = true
+
+ c.include RSpecSystemPuppet::Helpers
+
+ # This is where we 'setup' the nodes before running our tests
+ c.before :suite do
+ # Install puppet
+ puppet_install
+
+ # Install modules and dependencies
+ puppet_module_install(:source => proj_root, :module_name => 'ntp')
+ shell('puppet module install puppetlabs-stdlib')
+ end
+end
diff --git a/puppet/modules/ntp/spec/system/basic_spec.rb b/puppet/modules/ntp/spec/system/basic_spec.rb
new file mode 100644
index 00000000..7b717a04
--- /dev/null
+++ b/puppet/modules/ntp/spec/system/basic_spec.rb
@@ -0,0 +1,13 @@
+require 'spec_helper_system'
+
+# Here we put the more basic fundamental tests, ultra obvious stuff.
+describe "basic tests:" do
+ context 'make sure we have copied the module across' do
+ # No point diagnosing any more if the module wasn't copied properly
+ context shell 'ls /etc/puppet/modules/ntp' do
+ its(:stdout) { should =~ /Modulefile/ }
+ its(:stderr) { should be_empty }
+ its(:exit_code) { should be_zero }
+ end
+ end
+end
diff --git a/puppet/modules/ntp/spec/system/class_spec.rb b/puppet/modules/ntp/spec/system/class_spec.rb
new file mode 100644
index 00000000..49dfc641
--- /dev/null
+++ b/puppet/modules/ntp/spec/system/class_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper_system'
+
+describe "ntp class:" do
+ context 'should run successfully' do
+ pp = "class { 'ntp': }"
+
+ context puppet_apply(pp) do
+ its(:stderr) { should be_empty }
+ its(:exit_code) { should_not == 1 }
+ its(:refresh) { should be_nil }
+ its(:stderr) { should be_empty }
+ its(:exit_code) { should be_zero }
+ end
+ end
+
+ context 'service_ensure => stopped:' do
+ pp = "class { 'ntp': service_ensure => stopped }"
+
+ context puppet_apply(pp) do
+ its(:stderr) { should be_empty }
+ its(:exit_code) { should_not == 1 }
+ its(:refresh) { should be_nil }
+ its(:stderr) { should be_empty }
+ its(:exit_code) { should be_zero }
+ end
+ end
+
+ context 'service_ensure => running:' do
+ pp = "class { 'ntp': service_ensure => running }"
+
+ context puppet_apply(pp) do |r|
+ its(:stderr) { should be_empty }
+ its(:exit_code) { should_not == 1 }
+ its(:refresh) { should be_nil }
+ its(:stderr) { should be_empty }
+ its(:exit_code) { should be_zero }
+ end
+ end
+end
diff --git a/puppet/modules/ntp/spec/system/ntp_config_spec.rb b/puppet/modules/ntp/spec/system/ntp_config_spec.rb
new file mode 100644
index 00000000..194cdf10
--- /dev/null
+++ b/puppet/modules/ntp/spec/system/ntp_config_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper_system'
+
+describe 'ntp::config class' do
+ let(:os) {
+ node.facts['osfamily']
+ }
+
+ puppet_apply(%{
+ class { 'ntp': }
+ })
+
+ case node.facts['osfamily']
+ when 'FreeBSD'
+ line = '0.freebsd.pool.ntp.org iburst maxpoll 9'
+ when 'Debian'
+ line = '0.debian.pool.ntp.org iburst'
+ when 'RedHat'
+ line = '0.centos.pool.ntp.org'
+ when 'SuSE'
+ line = '0.opensuse.pool.ntp.org'
+ when 'Linux'
+ case node.facts['operatingsystem']
+ when 'ArchLinux'
+ line = '0.pool.ntp.org'
+ when 'Gentoo'
+ line = '0.gentoo.pool.ntp.org'
+ end
+ end
+
+ describe file('/etc/ntp.conf') do
+ it { should be_file }
+ it { should contain line }
+ end
+
+end
diff --git a/puppet/modules/ntp/spec/system/ntp_install_spec.rb b/puppet/modules/ntp/spec/system/ntp_install_spec.rb
new file mode 100644
index 00000000..39759c5e
--- /dev/null
+++ b/puppet/modules/ntp/spec/system/ntp_install_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper_system'
+
+
+describe 'ntp::install class' do
+ let(:os) {
+ node.facts['osfamily']
+ }
+
+ case node.facts['osfamily']
+ when 'FreeBSD'
+ packagename = 'net/ntp'
+ when 'Linux'
+ case node.facts['operatingsystem']
+ when 'ArchLinux'
+ packagename = 'ntp'
+ when 'Gentoo'
+ packagename = 'net-misc/ntp'
+ end
+ else
+ packagename = 'ntp'
+ end
+
+ puppet_apply(%{
+ class { 'ntp': }
+ })
+
+ describe package(packagename) do
+ it { should be_installed }
+ end
+
+end
diff --git a/puppet/modules/ntp/spec/system/ntp_service_spec.rb b/puppet/modules/ntp/spec/system/ntp_service_spec.rb
new file mode 100644
index 00000000..b97e2a4e
--- /dev/null
+++ b/puppet/modules/ntp/spec/system/ntp_service_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper_system'
+
+
+describe 'ntp::service class' do
+ let(:os) {
+ node.facts['osfamily']
+ }
+
+ case node.facts['osfamily']
+ when 'RedHat', 'FreeBSD', 'Linux'
+ servicename = 'ntpd'
+ else
+ servicename = 'ntp'
+ end
+
+ puppet_apply(%{
+ class { 'ntp': }
+ })
+
+ describe service(servicename) do
+ it { should be_enabled }
+ it { should be_running }
+ end
+
+end
diff --git a/puppet/modules/ntp/spec/system/preferred_servers_spec.rb b/puppet/modules/ntp/spec/system/preferred_servers_spec.rb
new file mode 100644
index 00000000..686861bc
--- /dev/null
+++ b/puppet/modules/ntp/spec/system/preferred_servers_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper_system'
+
+describe 'preferred servers' do
+ it 'applies cleanly' do
+ puppet_apply(%{
+ class { '::ntp':
+ servers => ['a', 'b', 'c', 'd'],
+ preferred_servers => ['c', 'd'],
+ }
+ })
+ end
+
+ describe file('/etc/ntp.conf') do
+ it { should be_file }
+ it { should contain 'server a' }
+ it { should contain 'server b' }
+ it { should contain 'server c prefer' }
+ it { should contain 'server d prefer' }
+ end
+end
diff --git a/puppet/modules/ntp/spec/system/restrict_spec.rb b/puppet/modules/ntp/spec/system/restrict_spec.rb
new file mode 100644
index 00000000..ae23bc01
--- /dev/null
+++ b/puppet/modules/ntp/spec/system/restrict_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper_system'
+
+describe "ntp class with restrict:" do
+ context 'should run successfully' do
+ pp = "class { 'ntp': restrict => ['test restrict']}"
+
+ context puppet_apply(pp) do
+ its(:stderr) { should be_empty }
+ its(:exit_code) { should_not == 1 }
+ its(:refresh) { should be_nil }
+ its(:stderr) { should be_empty }
+ its(:exit_code) { should be_zero }
+ end
+ end
+
+ describe file('/etc/ntp.conf') do
+ it { should contain('test restrict') }
+ end
+
+end
diff --git a/puppet/modules/ntp/spec/unit/puppet/provider/README.markdown b/puppet/modules/ntp/spec/unit/puppet/provider/README.markdown
new file mode 100644
index 00000000..70258502
--- /dev/null
+++ b/puppet/modules/ntp/spec/unit/puppet/provider/README.markdown
@@ -0,0 +1,4 @@
+Provider Specs
+==============
+
+Define specs for your providers under this directory.
diff --git a/puppet/modules/ntp/spec/unit/puppet/type/README.markdown b/puppet/modules/ntp/spec/unit/puppet/type/README.markdown
new file mode 100644
index 00000000..1ee19ac8
--- /dev/null
+++ b/puppet/modules/ntp/spec/unit/puppet/type/README.markdown
@@ -0,0 +1,4 @@
+Resource Type Specs
+===================
+
+Define specs for your resource types in this directory.
diff --git a/puppet/modules/ntp/templates/ntp.conf.erb b/puppet/modules/ntp/templates/ntp.conf.erb
new file mode 100644
index 00000000..94b36755
--- /dev/null
+++ b/puppet/modules/ntp/templates/ntp.conf.erb
@@ -0,0 +1,43 @@
+# ntp.conf: Managed by puppet.
+#
+<% if @panic == false -%>
+# Keep ntpd from panicking in the event of a large clock skew
+# when a VM guest is suspended and resumed.
+tinker panic 0
+<% end -%>
+
+<% if @restrict != [] -%>
+# Permit time synchronization with our time source, but do not'
+# permit the source to query or modify the service on this system.'
+<% @restrict.flatten.each do |restrict| -%>
+<%= restrict %>
+<% end %>
+<% end -%>
+
+# Servers
+<% [@servers].flatten.each do |server| -%>
+server <%= server %><% if @preferred_servers.include?(server) -%> prefer<% end %>
+<% end -%>
+
+<% if scope.lookupvar('::is_virtual') == "false" -%>
+# Undisciplined Local Clock. This is a fake driver intended for backup
+# and when no outside source of synchronized time is available.
+server 127.127.1.0 # local clock
+fudge 127.127.1.0 stratum 10
+<% end -%>
+
+# Driftfile.
+driftfile <%= @driftfile %>
+
+<% if @keys_enable -%>
+keys <%= @keys_file %>
+<% unless @keys_trusted.empty? -%>
+trustedkey <%= @keys_trusted.join(' ') %>
+<% end -%>
+<% if @keys_requestkey != '' -%>
+requestkey <%= @keys_requestkey %>
+<% end -%>
+<% if @keys_controlkey != '' -%>
+controlkey <%= @keys_controlkey %>
+<% end -%>
+<% end -%>
diff --git a/puppet/modules/ntp/tests/init.pp b/puppet/modules/ntp/tests/init.pp
new file mode 100644
index 00000000..e6d9b537
--- /dev/null
+++ b/puppet/modules/ntp/tests/init.pp
@@ -0,0 +1,11 @@
+node default {
+
+ notify { 'enduser-before': }
+ notify { 'enduser-after': }
+
+ class { 'ntp':
+ require => Notify['enduser-before'],
+ before => Notify['enduser-after'],
+ }
+
+}
diff --git a/puppet/modules/obfsproxy/files/obfsproxy_init b/puppet/modules/obfsproxy/files/obfsproxy_init
new file mode 100755
index 00000000..01c8013a
--- /dev/null
+++ b/puppet/modules/obfsproxy/files/obfsproxy_init
@@ -0,0 +1,93 @@
+#!/bin/sh
+
+### BEGIN INIT INFO
+# Provides: obfsproxy daemon
+# Required-Start: $remote_fs $syslog
+# Required-Stop: $remote_fs $syslog
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: obfsproxy daemon
+# Description: obfsproxy daemon
+### END INIT INFO
+
+. /lib/lsb/init-functions
+
+DAEMON=/usr/bin/obfsproxy
+NAME=obfsproxy
+DESC="obfsproxy daemon"
+USER=obfsproxy
+DATDIR=/etc/obfsproxy
+PIDFILE=/var/run/obfsproxy.pid
+CONF=$DATDIR/obfsproxy.conf
+LOGFILE=/var/log/obfsproxy.log
+
+# If the daemon is not there, then exit.
+test -x $DAEMON || exit 0
+
+if [ -f $CONF ] ; then
+ . $CONF
+else
+ echo "Obfsproxy configuration file is missing, aborting..."
+ exit 2
+fi
+
+DAEMONARGS=" --log-min-severity=$LOG --log-file=$LOGFILE --data-dir=$DATDIR \
+ $TRANSPORT $PARAM --dest=$DEST_IP:$DEST_PORT server $BINDADDR:$PORT"
+
+start_obfsproxy() {
+ start-stop-daemon --start --quiet --oknodo -m --pidfile $PIDFILE \
+ -b -c $USER --startas $DAEMON --$DAEMONARGS
+}
+
+stop_obfsproxy() {
+ start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE
+}
+
+status_obfsproxy() {
+ status_of_proc -p $PIDFILE $DAEMON $NAME
+}
+
+case $1 in
+ start)
+ if [ -e $PIDFILE ]; then
+ status_obfsproxy
+ if [ $? = "0" ]; then
+ exit
+ fi
+ fi
+ log_begin_msg "Starting $DESC"
+ start_obfsproxy
+ log_end_msg $?
+ ;;
+ stop)
+ if [ -e $PIDFILE ]; then
+ status_obfsproxy
+ if [ $? = "0" ]; then
+ log_begin_msg "Stopping $DESC"
+ stop_obfsproxy
+ rm -f $PIDFILE
+ log_end_msg $?
+ fi
+ else
+ status_obfsproxy
+ fi
+ ;;
+ restart)
+ $0 stop && sleep 2 && $0 start
+ ;;
+ status)
+ status_obfsproxy
+ ;;
+ reload)
+ if [ -e $PIDFILE ]; then
+ start-stop-daemon --stop --signal USR1 --quiet --pidfile $PIDFILE --name $NAME
+ log_success_msg "$DESC reloaded successfully"
+ else
+ log_failure_msg "$PIDFILE does not exist"
+ fi
+ ;;
+ *)
+ echo "Usage: $0 {start|stop|restart|reload|status}"
+ exit 2
+ ;;
+esac
diff --git a/puppet/modules/obfsproxy/files/obfsproxy_logrotate b/puppet/modules/obfsproxy/files/obfsproxy_logrotate
new file mode 100644
index 00000000..e5679d0c
--- /dev/null
+++ b/puppet/modules/obfsproxy/files/obfsproxy_logrotate
@@ -0,0 +1,14 @@
+/var/log/obfsproxy.log {
+ daily
+ missingok
+ rotate 3
+ compress
+ delaycompress
+ notifempty
+ create 600 obfsproxy obfsproxy
+ postrotate
+ if [ -f /var/run/obfsproxy.pid ]; then
+ /etc/init.d/obfsproxy restart > /dev/null
+ fi
+ endscript
+}
diff --git a/puppet/modules/obfsproxy/manifests/init.pp b/puppet/modules/obfsproxy/manifests/init.pp
new file mode 100644
index 00000000..6a3d2c72
--- /dev/null
+++ b/puppet/modules/obfsproxy/manifests/init.pp
@@ -0,0 +1,86 @@
+# deploy obfsproxy service
+class obfsproxy (
+ $transport,
+ $bind_address,
+ $port,
+ $param,
+ $dest_ip,
+ $dest_port,
+ $log_level = 'info'
+){
+
+ $user = 'obfsproxy'
+ $conf = '/etc/obfsproxy/obfsproxy.conf'
+
+ user { $user:
+ ensure => present,
+ system => true,
+ gid => $user,
+ }
+
+ group { $user:
+ ensure => present,
+ system => true,
+ }
+
+ file { '/etc/init.d/obfsproxy':
+ ensure => present,
+ path => '/etc/init.d/obfsproxy',
+ source => 'puppet:///modules/obfsproxy/obfsproxy_init',
+ owner => 'root',
+ group => 'root',
+ mode => '0750',
+ require => File[$conf],
+ }
+
+ file { $conf :
+ ensure => present,
+ path => $conf,
+ owner => 'root',
+ group => 'root',
+ mode => '0600',
+ content => template('obfsproxy/etc_conf.erb'),
+ }
+
+ file { '/etc/obfsproxy':
+ ensure => directory,
+ owner => $user,
+ group => $user,
+ mode => '0700',
+ require => User[$user],
+ }
+
+ file { '/var/log/obfsproxy.log':
+ ensure => present,
+ owner => $user,
+ group => $user,
+ mode => '0640',
+ require => User[$user],
+ }
+
+ file { '/etc/logrotate.d/obfsproxy':
+ ensure => present,
+ source => 'puppet:///modules/obfsproxy/obfsproxy_logrotate',
+ owner => 'root',
+ group => 'root',
+ mode => '0644',
+ require => File['/var/log/obfsproxy.log'],
+ }
+
+ package { 'obfsproxy':
+ ensure => present
+ }
+
+ service { 'obfsproxy':
+ ensure => running,
+ subscribe => File[$conf],
+ require => [
+ Package['obfsproxy'],
+ File['/etc/init.d/obfsproxy'],
+ User[$user],
+ Group[$user]]
+ }
+
+
+}
+
diff --git a/puppet/modules/obfsproxy/templates/etc_conf.erb b/puppet/modules/obfsproxy/templates/etc_conf.erb
new file mode 100644
index 00000000..8959ef78
--- /dev/null
+++ b/puppet/modules/obfsproxy/templates/etc_conf.erb
@@ -0,0 +1,11 @@
+TRANSPORT=<%= @transport %>
+PORT=<%= @port %>
+DEST_IP=<%= @dest_ip %>
+DEST_PORT=<%= @dest_port %>
+<% if @transport == "scramblesuit" -%>
+PARAM=--password=<%= @param %>
+<% else -%>
+PARAM=<%= @param %>
+<% end -%>
+LOG=<%= @log_level %>
+BINDADDR=<%= @bind_address %>
diff --git a/puppet/modules/opendkim/manifests/init.pp b/puppet/modules/opendkim/manifests/init.pp
new file mode 100644
index 00000000..4d4c5312
--- /dev/null
+++ b/puppet/modules/opendkim/manifests/init.pp
@@ -0,0 +1,67 @@
+#
+# I am not sure about what issues might arise with DKIM key sizes
+# larger than 2048. It might or might not be supported. See:
+# http://dkim.org/specs/rfc4871-dkimbase.html#rfc.section.3.3.3
+#
+class opendkim {
+
+ $domain_hash = hiera('domain')
+ $domain = $domain_hash['full_suffix']
+ $mx = hiera('mx')
+ $dkim = $mx['dkim']
+ $selector = $dkim['selector']
+ $dkim_cert = $dkim['public_key']
+ $dkim_key = $dkim['private_key']
+
+ ensure_packages(['opendkim', 'libvbr2'])
+
+ # postfix user needs to be in the opendkim group
+ # in order to access the opendkim socket located at:
+ # local:/var/run/opendkim/opendkim.sock
+ user { 'postfix':
+ groups => 'opendkim',
+ require => Package['opendkim'];
+ }
+
+ service { 'opendkim':
+ ensure => running,
+ enable => true,
+ hasstatus => true,
+ hasrestart => true,
+ subscribe => File[$dkim_key];
+ }
+
+ file {
+ '/etc/opendkim.conf':
+ ensure => file,
+ content => template('opendkim/opendkim.conf'),
+ mode => '0644',
+ owner => root,
+ group => root,
+ notify => Service['opendkim'],
+ require => Package['opendkim'];
+
+ '/etc/default/opendkim.conf':
+ ensure => file,
+ content => 'SOCKET="inet:8891@localhost" # listen on loopback on port 8891',
+ mode => '0644',
+ owner => root,
+ group => root,
+ notify => Service['opendkim'],
+ require => Package['opendkim'];
+
+ $dkim_key:
+ ensure => file,
+ mode => '0600',
+ owner => 'opendkim',
+ group => 'opendkim',
+ require => Package['opendkim'];
+
+ $dkim_cert:
+ ensure => file,
+ mode => '0600',
+ owner => 'opendkim',
+ group => 'opendkim',
+ require => Package['opendkim'];
+ }
+}
diff --git a/puppet/modules/opendkim/templates/opendkim.conf b/puppet/modules/opendkim/templates/opendkim.conf
new file mode 100644
index 00000000..5a948229
--- /dev/null
+++ b/puppet/modules/opendkim/templates/opendkim.conf
@@ -0,0 +1,45 @@
+# This is a basic configuration that can easily be adapted to suit a standard
+# installation. For more advanced options, see opendkim.conf(5) and/or
+# /usr/share/doc/opendkim/examples/opendkim.conf.sample.
+
+# Log to syslog
+Syslog yes
+SyslogSuccess yes
+LogWhy no
+# Required to use local socket with MTAs that access the socket as a non-
+# privileged user (e.g. Postfix)
+UMask 002
+
+Domain <%= @domain %>
+SubDomains yes
+
+# set internal hosts to all the known hosts, like mydomains?
+
+# can we generate a larger key and get it in dns?
+KeyFile <%= @dkim_key %>
+
+Selector <%= @selector %>
+
+# Commonly-used options; the commented-out versions show the defaults.
+Canonicalization relaxed
+#Mode sv
+#ADSPDiscard no
+
+SignatureAlgorithm rsa-sha256
+
+# Always oversign From (sign using actual From and a null From to prevent
+# malicious signatures header fields (From and/or others) between the signer
+# and the verifier. From is oversigned by default in the Debian pacakge
+# because it is often the identity key used by reputation systems and thus
+# somewhat security sensitive.
+OversignHeaders From
+
+# List domains to use for RFC 6541 DKIM Authorized Third-Party Signatures
+# (ATPS) (experimental)
+
+#ATPSDomains example.com
+
+RemoveOldSignatures yes
+
+Mode sv
+BaseDirectory /var/tmp
diff --git a/puppet/modules/openvpn/.fixtures.yml b/puppet/modules/openvpn/.fixtures.yml
new file mode 100644
index 00000000..1125ecca
--- /dev/null
+++ b/puppet/modules/openvpn/.fixtures.yml
@@ -0,0 +1,6 @@
+fixtures:
+ repositories:
+ concat: git://github.com/ripienaar/puppet-concat.git
+ symlinks:
+ openvpn: "#{source_dir}"
+
diff --git a/puppet/modules/openvpn/.gitignore b/puppet/modules/openvpn/.gitignore
new file mode 100644
index 00000000..6fd248b3
--- /dev/null
+++ b/puppet/modules/openvpn/.gitignore
@@ -0,0 +1,3 @@
+pkg
+spec/fixtures
+.vagrant
diff --git a/puppet/modules/openvpn/.rvmrc b/puppet/modules/openvpn/.rvmrc
new file mode 100644
index 00000000..6fbfb7f1
--- /dev/null
+++ b/puppet/modules/openvpn/.rvmrc
@@ -0,0 +1,38 @@
+#!/usr/bin/env bash
+
+# This is an RVM Project .rvmrc file, used to automatically load the ruby
+# development environment upon cd'ing into the directory
+
+# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional,
+# Only full ruby name is supported here, for short names use:
+# echo "rvm use 1.9.3" > .rvmrc
+environment_id="ruby-1.9.3-p194@puppet"
+
+# Uncomment the following lines if you want to verify rvm version per project
+# rvmrc_rvm_version="1.15.8 (stable)" # 1.10.1 seams as a safe start
+# eval "$(echo ${rvm_version}.${rvmrc_rvm_version} | awk -F. '{print "[[ "$1*65536+$2*256+$3" -ge "$4*65536+$5*256+$6" ]]"}' )" || {
+# echo "This .rvmrc file requires at least RVM ${rvmrc_rvm_version}, aborting loading."
+# return 1
+# }
+
+# First we attempt to load the desired environment directly from the environment
+# file. This is very fast and efficient compared to running through the entire
+# CLI and selector. If you want feedback on which environment was used then
+# insert the word 'use' after --create as this triggers verbose mode.
+if [[ -d "${rvm_path:-$HOME/.rvm}/environments"
+ && -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
+then
+ \. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
+ [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]] &&
+ \. "${rvm_path:-$HOME/.rvm}/hooks/after_use" || true
+ if [[ $- == *i* ]] # check for interactive shells
+ then echo "Using: $(tput setaf 2)$GEM_HOME$(tput sgr0)" # show the user the ruby and gemset they are using in green
+ else echo "Using: $GEM_HOME" # don't use colors in non-interactive shells
+ fi
+else
+ # If the environment file has not yet been created, use the RVM CLI to select.
+ rvm --create use "$environment_id" || {
+ echo "Failed to create RVM environment '${environment_id}'."
+ return 1
+ }
+fi
diff --git a/puppet/modules/openvpn/.travis.yml b/puppet/modules/openvpn/.travis.yml
new file mode 100644
index 00000000..da5c389d
--- /dev/null
+++ b/puppet/modules/openvpn/.travis.yml
@@ -0,0 +1,29 @@
+language: ruby
+bundler_args: --without development
+script: "bundle exec rake spec SPEC_OPTS='--format documentation'"
+rvm:
+ - 1.8.7
+ - 1.9.3
+ - 2.0.0
+script:
+ - "rake lint"
+ - "rake spec SPEC_OPTS='--format documentation'"
+env:
+ - PUPPET_VERSION="~> 2.7.0"
+ - PUPPET_VERSION="~> 3.0.0"
+ - PUPPET_VERSION="~> 3.1.0"
+ - PUPPET_VERSION="~> 3.2.0"
+matrix:
+ exclude:
+ - rvm: 1.9.3
+ env: PUPPET_VERSION="~> 2.7.0"
+ - rvm: 2.0.0
+ env: PUPPET_VERSION="~> 2.7.0"
+ - rvm: 2.0.0
+ env: PUPPET_VERSION="~> 3.0.0"
+ - rvm: 2.0.0
+ env: PUPPET_VERSION="~> 3.1.0"
+notifications:
+ email: false
+ on_success: always
+ on_failure: always
diff --git a/puppet/modules/openvpn/Gemfile b/puppet/modules/openvpn/Gemfile
new file mode 100644
index 00000000..68e10e7d
--- /dev/null
+++ b/puppet/modules/openvpn/Gemfile
@@ -0,0 +1,7 @@
+source :rubygems
+
+puppetversion = ENV['PUPPET_VERSION']
+gem 'puppet', puppetversion, :require => false
+gem 'puppet-lint'
+gem 'rspec-puppet'
+gem 'puppetlabs_spec_helper'
diff --git a/puppet/modules/openvpn/Gemfile.lock b/puppet/modules/openvpn/Gemfile.lock
new file mode 100644
index 00000000..9fce3f98
--- /dev/null
+++ b/puppet/modules/openvpn/Gemfile.lock
@@ -0,0 +1,36 @@
+GEM
+ remote: http://rubygems.org/
+ specs:
+ diff-lcs (1.1.3)
+ facter (1.6.17)
+ hiera (1.0.0)
+ metaclass (0.0.1)
+ mocha (0.13.1)
+ metaclass (~> 0.0.1)
+ puppet (3.0.2)
+ facter (~> 1.6.11)
+ hiera (~> 1.0.0)
+ puppetlabs_spec_helper (0.4.0)
+ mocha (>= 0.10.5)
+ rake
+ rspec (>= 2.9.0)
+ rspec-puppet (>= 0.1.1)
+ rake (10.0.3)
+ rspec (2.12.0)
+ rspec-core (~> 2.12.0)
+ rspec-expectations (~> 2.12.0)
+ rspec-mocks (~> 2.12.0)
+ rspec-core (2.12.2)
+ rspec-expectations (2.12.1)
+ diff-lcs (~> 1.1.3)
+ rspec-mocks (2.12.1)
+ rspec-puppet (0.1.5)
+ rspec
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ puppet
+ puppetlabs_spec_helper
+ rspec-puppet
diff --git a/puppet/modules/openvpn/LICENSE b/puppet/modules/openvpn/LICENSE
new file mode 100644
index 00000000..f433b1a5
--- /dev/null
+++ b/puppet/modules/openvpn/LICENSE
@@ -0,0 +1,177 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
diff --git a/puppet/modules/openvpn/Modulefile b/puppet/modules/openvpn/Modulefile
new file mode 100644
index 00000000..679e7e64
--- /dev/null
+++ b/puppet/modules/openvpn/Modulefile
@@ -0,0 +1,11 @@
+name 'luxflux-openvpn'
+version '2.1.0'
+source 'https://github.com/luxflux/puppet-openvpn'
+author 'luxflux'
+license 'Apache 2.0'
+summary 'OpenVPN server puppet module'
+description 'Puppet module to manage OpenVPN servers'
+project_page 'https://github.com/luxflux/puppet-openvpn'
+
+## Add dependencies, if any:
+dependency 'ripienaar/concat', '0.2.0'
diff --git a/puppet/modules/openvpn/Rakefile b/puppet/modules/openvpn/Rakefile
new file mode 100644
index 00000000..14f1c246
--- /dev/null
+++ b/puppet/modules/openvpn/Rakefile
@@ -0,0 +1,2 @@
+require 'rubygems'
+require 'puppetlabs_spec_helper/rake_tasks'
diff --git a/puppet/modules/openvpn/Readme.markdown b/puppet/modules/openvpn/Readme.markdown
new file mode 100644
index 00000000..6bcf49ea
--- /dev/null
+++ b/puppet/modules/openvpn/Readme.markdown
@@ -0,0 +1,54 @@
+# OpenVPN Puppet module
+
+Puppet module to manage OpenVPN servers
+
+## Features:
+
+* Client-specific rules and access policies
+* Generated client configurations and SSL-Certificates
+* Downloadable client configurations and SSL-Certificates for easy client configuration
+* Support for multiple server instances
+
+Tested on Ubuntu Precise Pangolin, CentOS 6, RedHat 6.
+
+
+## Dependencies
+ - [puppet-concat](https://github.com/ripienaar/puppet-concat)
+
+
+## Example
+
+```puppet
+ # add a server instance
+ openvpn::server { 'winterthur':
+ country => 'CH',
+ province => 'ZH',
+ city => 'Winterthur',
+ organization => 'example.org',
+ email => 'root@example.org',
+ server => '10.200.200.0 255.255.255.0'
+ }
+
+ # define clients
+ openvpn::client { 'client1':
+ server => 'winterthur'
+ }
+ openvpn::client { 'client2':
+ server => 'winterthur'
+ }
+
+ openvpn::client_specific_config { 'client1':
+ server => 'winterthur',
+ ifconfig => '10.200.200.50 255.255.255.0'
+ }
+```
+
+Don't forget the [sysctl](https://github.com/luxflux/puppet-sysctl) directive ```net.ipv4.ip_forward```!
+
+
+# Contributors
+
+These fine folks helped to get this far with this module:
+* [@jlambert121](https://github.com/jlambert121)
+* [@jlk](https://github.com/jlk)
+* [@elisiano](https://github.com/elisiano)
diff --git a/puppet/modules/openvpn/Vagrantfile b/puppet/modules/openvpn/Vagrantfile
new file mode 100644
index 00000000..88875ff8
--- /dev/null
+++ b/puppet/modules/openvpn/Vagrantfile
@@ -0,0 +1,42 @@
+# -*- mode: ruby -*-
+# vi: set ft=ruby :
+
+def server_config(config)
+ config.vm.provision :puppet, :module_path => '..' do |puppet|
+ puppet.manifests_path = "vagrant"
+ puppet.manifest_file = "server.pp"
+ end
+end
+
+def client_config(config)
+ config.vm.provision :puppet, :module_path => '..' do |puppet|
+ puppet.manifests_path = "vagrant"
+ puppet.manifest_file = "client.pp"
+ end
+end
+
+Vagrant::Config.run do |config|
+
+ config.vm.define :server_ubuntu do |c|
+ c.vm.box = 'precise64'
+ server_config c
+ c.vm.network :hostonly, '10.255.255.10'
+ end
+
+ config.vm.define :server_centos do |c|
+ c.vm.box = 'centos63'
+
+ c.vm.provision :shell, :inline => 'if [ ! -f rpmforge-release-0.5.2-2.el6.rf.x86_64.rpm ]; then wget -q http://pkgs.repoforge.org/rpmforge-release/rpmforge-release-0.5.2-2.el6.rf.x86_64.rpm; fi'
+ c.vm.provision :shell, :inline => 'yum install -y rpmforge-release-0.5.2-2.el6.rf.x86_64.rpm || exit 0'
+
+ server_config c
+ c.vm.network :hostonly, '10.255.255.11'
+ end
+
+ config.vm.define :client_ubuntu do |c|
+ c.vm.box = 'precise64'
+ client_config c
+ c.vm.network :hostonly, '10.255.255.20'
+ end
+
+end
diff --git a/puppet/modules/openvpn/manifests/client.pp b/puppet/modules/openvpn/manifests/client.pp
new file mode 100644
index 00000000..92c6aa4e
--- /dev/null
+++ b/puppet/modules/openvpn/manifests/client.pp
@@ -0,0 +1,187 @@
+# == Define: openvpn::client
+#
+# This define creates the client certs for a specified openvpn server as well
+# as creating a tarball that can be directly imported into openvpn clients
+#
+#
+# === Parameters
+#
+# [*server*]
+# String. Name of the corresponding openvpn endpoint
+# Required
+#
+# [*compression*]
+# String. Which compression algorithim to use
+# Default: comp-lzo
+# Options: comp-lzo or '' (disable compression)
+#
+# [*dev*]
+# String. Device method
+# Default: tun
+# Options: tun (routed connections), tap (bridged connections)
+#
+# [*mute*]
+# Integer. Set log mute level
+# Default: 20
+#
+# [*mute_replay_warnings*]
+# Boolean. Silence duplicate packet warnings (common on wireless networks)
+# Default: true
+#
+# [*nobind*]
+# Boolean. Whether or not to bind to a specific port number
+# Default: true
+#
+# [*persist_key*]
+# Boolean. Try to retain access to resources that may be unavailable
+# because of privilege downgrades
+# Default: true
+#
+# [*persist_tun*]
+# Boolean. Try to retain access to resources that may be unavailable
+# because of privilege downgrades
+# Default: true
+#
+# [*port*]
+# Integer. The port the openvpn server service is running on
+# Default: 1194
+#
+# [*proto*]
+# String. What IP protocol is being used.
+# Default: tcp
+# Options: tcp or udp
+#
+# [*remote_host*]
+# String. The IP or hostname of the openvpn server service
+# Default: FQDN
+#
+# [*resolv_retry*]
+# Integer/String. How many seconds should the openvpn client try to resolve
+# the server's hostname
+# Default: infinite
+# Options: Integer or infinite
+#
+# [*verb*]
+# Integer. Level of logging verbosity
+# Default: 3
+#
+#
+# === Examples
+#
+# openvpn::client {
+# 'my_user':
+# server => 'contractors',
+# remote_host => 'vpn.mycompany.com'
+# }
+#
+# * Removal:
+# Manual process right now, todo for the future
+#
+#
+# === Authors
+#
+# * Raffael Schmid <mailto:raffael@yux.ch>
+# * John Kinsella <mailto:jlkinsel@gmail.com>
+# * Justin Lambert <mailto:jlambert@letsevenup.com>
+#
+# === License
+#
+# Copyright 2013 Raffael Schmid, <raffael@yux.ch>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+define openvpn::client(
+ $server,
+ $compression = 'comp-lzo',
+ $dev = 'tun',
+ $mute = '20',
+ $mute_replay_warnings = true,
+ $nobind = true,
+ $persist_key = true,
+ $persist_tun = true,
+ $port = '1194',
+ $proto = 'tcp',
+ $remote_host = $::fqdn,
+ $resolv_retry = 'infinite',
+ $verb = '3',
+) {
+
+ Openvpn::Server[$server] ->
+ Openvpn::Client[$name]
+
+ exec {
+ "generate certificate for ${name} in context of ${server}":
+ command => ". ./vars && ./pkitool ${name}",
+ cwd => "/etc/openvpn/${server}/easy-rsa",
+ creates => "/etc/openvpn/${server}/easy-rsa/keys/${name}.crt",
+ provider => 'shell';
+ }
+
+ file {
+ [ "/etc/openvpn/${server}/download-configs/${name}",
+ "/etc/openvpn/${server}/download-configs/${name}/keys"]:
+ ensure => directory;
+
+ "/etc/openvpn/${server}/download-configs/${name}/keys/${name}.crt":
+ ensure => link,
+ target => "/etc/openvpn/${server}/easy-rsa/keys/${name}.crt",
+ require => Exec["generate certificate for ${name} in context of ${server}"];
+
+ "/etc/openvpn/${server}/download-configs/${name}/keys/${name}.key":
+ ensure => link,
+ target => "/etc/openvpn/${server}/easy-rsa/keys/${name}.key",
+ require => Exec["generate certificate for ${name} in context of ${server}"];
+
+ "/etc/openvpn/${server}/download-configs/${name}/keys/ca.crt":
+ ensure => link,
+ target => "/etc/openvpn/${server}/easy-rsa/keys/ca.crt",
+ require => Exec["generate certificate for ${name} in context of ${server}"];
+
+ "/etc/openvpn/${server}/download-configs/${name}/${name}.conf":
+ owner => root,
+ group => root,
+ mode => '0444',
+ content => template('openvpn/client.erb'),
+ notify => Exec["tar the thing ${server} with ${name}"];
+ }
+
+ exec {
+ "tar the thing ${server} with ${name}":
+ cwd => "/etc/openvpn/${server}/download-configs/",
+ command => "/bin/rm ${name}.tar.gz; tar --exclude=\\*.conf.d -chzvf ${name}.tar.gz ${name}",
+ refreshonly => true,
+ require => [ File["/etc/openvpn/${server}/download-configs/${name}/${name}.conf"],
+ File["/etc/openvpn/${server}/download-configs/${name}/keys/ca.crt"],
+ File["/etc/openvpn/${server}/download-configs/${name}/keys/${name}.key"],
+ File["/etc/openvpn/${server}/download-configs/${name}/keys/${name}.crt"]
+ ],
+ notify => Exec["generate ${name}.ovpn in ${server}"];
+ }
+
+ exec {
+ "generate ${name}.ovpn in ${server}":
+ cwd => "/etc/openvpn/${server}/download-configs/",
+ command => "/bin/rm ${name}.ovpn; cat ${name}/${name}.conf|perl -lne 'if(m|^ca keys/ca.crt|){ chomp(\$ca=`cat ${name}/keys/ca.crt`); print \"<ca>\n\$ca\n</ca>\"} elsif(m|^cert keys/${name}.crt|) { chomp(\$crt=`cat ${name}/keys/${name}.crt`); print \"<cert>\n\$crt\n</cert>\"} elsif(m|^key keys/${name}.key|){ chomp(\$key=`cat ${name}/keys/${name}.key`); print \"<key>\n\$key\n</key>\"} else { print} ' > ${name}.ovpn",
+ refreshonly => true,
+ require => [ File["/etc/openvpn/${server}/download-configs/${name}/${name}.conf"],
+ File["/etc/openvpn/${server}/download-configs/${name}/keys/ca.crt"],
+ File["/etc/openvpn/${server}/download-configs/${name}/keys/${name}.key"],
+ File["/etc/openvpn/${server}/download-configs/${name}/keys/${name}.crt"],
+ ],
+ }
+
+ file { "/etc/openvpn/${server}/download-configs/${name}.ovpn":
+ mode => '0400',
+ require => Exec["generate ${name}.ovpn in ${server}"],
+ }
+}
diff --git a/puppet/modules/openvpn/manifests/client_specific_config.pp b/puppet/modules/openvpn/manifests/client_specific_config.pp
new file mode 100644
index 00000000..4287421a
--- /dev/null
+++ b/puppet/modules/openvpn/manifests/client_specific_config.pp
@@ -0,0 +1,79 @@
+# == Define: openvpn::client_specific_config
+#
+# This define configures options which will be pushed by the server to a
+# specific client only. This feature is explained here:
+# http://openvpn.net/index.php/open-source/documentation/howto.html#policy
+#
+# === Parameters
+#
+# All the parameters are explained in the openvpn documentation:
+# http://openvpn.net/index.php/open-source/documentation/howto.html#policy
+#
+# [*server*]
+# String. Name of the corresponding openvpn endpoint
+# Required
+#
+# [*iroute*]
+# Array. Array of iroute combinations.
+# Default: []
+#
+# [*ifconfig*]
+# String. IP configuration to push to the client.
+# Default: false
+#
+# [*dhcp_options]
+# Array. DHCP options to push to the client.
+# Default: []
+#
+#
+# === Examples
+#
+# openvpn::client_specific_config {
+# 'vpn_client':
+# server => 'contractors',
+# iroute => ['10.0.1.0 255.255.255.0'],
+# ifconfig => '10.10.10.1 10.10.10.2',
+# dhcp_options => ['DNS 8.8.8.8']
+# }
+#
+# * Removal:
+# Manual process right now, todo for the future
+#
+#
+# === Authors
+#
+# * Raffael Schmid <mailto:raffael@yux.ch>
+#
+# === License
+#
+# Copyright 2013 Raffael Schmid, <raffael@yux.ch>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+define openvpn::client_specific_config(
+ $server,
+ $iroute = [],
+ $ifconfig = false,
+ $dhcp_options = []
+) {
+
+ Openvpn::Server[$server] ->
+ Openvpn::Client[$name] ->
+ Openvpn::Client_specific_config[$name]
+
+ file { "/etc/openvpn/${server}/client-configs/${name}":
+ ensure => present,
+ content => template('openvpn/client_specific_config.erb')
+ }
+
+}
diff --git a/puppet/modules/openvpn/manifests/config.pp b/puppet/modules/openvpn/manifests/config.pp
new file mode 100644
index 00000000..32b32094
--- /dev/null
+++ b/puppet/modules/openvpn/manifests/config.pp
@@ -0,0 +1,52 @@
+# == Class: openvpn::config
+#
+# This class sets up the openvpn enviornment as well as the default config file
+#
+#
+# === Examples
+#
+# This class should not be directly invoked
+#
+# === Authors
+#
+# * Raffael Schmid <mailto:raffael@yux.ch>
+# * John Kinsella <mailto:jlkinsel@gmail.com>
+# * Justin Lambert <mailto:jlambert@letsevenup.com>
+#
+# === License
+#
+# Copyright 2013 Raffael Schmid, <raffael@yux.ch>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+class openvpn::config {
+
+ if $::osfamily == 'Debian' {
+ include concat::setup
+
+ concat {
+ '/etc/default/openvpn':
+ owner => root,
+ group => root,
+ mode => 644,
+ warn => true;
+ }
+
+ concat::fragment {
+ 'openvpn.default.header':
+ content => template('openvpn/etc-default-openvpn.erb'),
+ target => '/etc/default/openvpn',
+ order => 01;
+ }
+ }
+}
diff --git a/puppet/modules/openvpn/manifests/init.pp b/puppet/modules/openvpn/manifests/init.pp
new file mode 100644
index 00000000..7e07f025
--- /dev/null
+++ b/puppet/modules/openvpn/manifests/init.pp
@@ -0,0 +1,43 @@
+# == Class: openvpn
+#
+# This module installs the openvpn service, configures vpn endpoints, generates
+# client certificates, and generates client config files
+#
+#
+# === Examples
+#
+# * Installation:
+# class { 'openvpn': }
+#
+#
+# === Authors
+#
+# * Raffael Schmid <mailto:raffael@yux.ch>
+# * John Kinsella <mailto:jlkinsel@gmail.com>
+# * Justin Lambert <mailto:jlambert@letsevenup.com>
+#
+# === License
+#
+# Copyright 2013 Raffael Schmid, <raffael@yux.ch>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+class openvpn {
+
+ class {'openvpn::params': } ->
+ class {'openvpn::install': } ->
+ class {'openvpn::config': } ~>
+ class {'openvpn::service': } ->
+ Class['openvpn']
+
+}
diff --git a/puppet/modules/openvpn/manifests/install.pp b/puppet/modules/openvpn/manifests/install.pp
new file mode 100644
index 00000000..a230373a
--- /dev/null
+++ b/puppet/modules/openvpn/manifests/install.pp
@@ -0,0 +1,46 @@
+# == Class: openvpn
+#
+# This module installs the openvpn service, configures vpn endpoints, generates
+# client certificates, and generates client config files
+#
+#
+# === Examples
+#
+# This class should not be directly invoked
+#
+#
+# === Authors
+#
+# * Raffael Schmid <mailto:raffael@yux.ch>
+# * John Kinsella <mailto:jlkinsel@gmail.com>
+# * Justin Lambert <mailto:jlambert@letsevenup.com>
+#
+# === License
+#
+# Copyright 2013 Raffael Schmid, <raffael@yux.ch>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+class openvpn::install {
+
+ package {
+ 'openvpn':
+ ensure => installed;
+ }
+
+ file {
+ [ '/etc/openvpn', '/etc/openvpn/keys' ]:
+ ensure => directory,
+ require => Package['openvpn'];
+ }
+}
diff --git a/puppet/modules/openvpn/manifests/params.pp b/puppet/modules/openvpn/manifests/params.pp
new file mode 100644
index 00000000..33495270
--- /dev/null
+++ b/puppet/modules/openvpn/manifests/params.pp
@@ -0,0 +1,37 @@
+# === License
+#
+# Copyright 2013 Raffael Schmid, <raffael@yux.ch>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+class openvpn::params {
+
+ $group = $::osfamily ? {
+ 'RedHat' => 'nobody',
+ default => 'nogroup'
+ }
+
+ $easyrsa_source = $::osfamily ? {
+ 'RedHat' => $::operatingsystemmajrelease ? {
+ 6 => '/usr/share/openvpn/easy-rsa/2.0',
+ default => '/usr/share/doc/openvpn-2.2.2/easy-rsa/2.0'
+ },
+ default => '/usr/share/doc/openvpn/examples/easy-rsa/2.0'
+ }
+
+ $link_openssl_cnf = $::osfamily ? {
+ /(Debian|RedHat)/ => true,
+ default => false
+ }
+
+}
diff --git a/puppet/modules/openvpn/manifests/server.pp b/puppet/modules/openvpn/manifests/server.pp
new file mode 100644
index 00000000..649048c4
--- /dev/null
+++ b/puppet/modules/openvpn/manifests/server.pp
@@ -0,0 +1,233 @@
+# == Define: openvpn::server
+#
+# This define creates the openvpn server instance and ssl certificates
+#
+#
+# === Parameters
+#
+# [*country*]
+# String. Country to be used for the SSL certificate
+#
+# [*province*]
+# String. Province to be used for the SSL certificate
+#
+# [*city*]
+# String. City to be used for the SSL certificate
+#
+# [*organization*]
+# String. Organization to be used for the SSL certificate
+#
+# [*email*]
+# String. Email address to be used for the SSL certificate
+#
+# [*compression*]
+# String. Which compression algorithim to use
+# Default: comp-lzo
+# Options: comp-lzo or '' (disable compression)
+#
+# [*dev*]
+# String. Device method
+# Default: tun
+# Options: tun (routed connections), tap (bridged connections)
+#
+# [*user*]
+# String. Group to drop privileges to after startup
+# Default: nobody
+#
+# [*group*]
+# String. User to drop privileges to after startup
+# Default: depends on your $::osfamily
+#
+# [*ipp*]
+# Boolean. Persist ifconfig information to a file to retain client IP
+# addresses between sessions
+# Default: false
+#
+# [*local*]
+# String. Interface for openvpn to bind to.
+# Default: $::ipaddress_eth0
+# Options: An IP address or '' to bind to all ip addresses
+#
+# [*logfile*]
+# String. Logfile for this openvpn server
+# Default: false
+# Options: false (syslog) or log file name
+#
+# [*port*]
+# Integer. The port the openvpn server service is running on
+# Default: 1194
+#
+# [*proto*]
+# String. What IP protocol is being used.
+# Default: tcp
+# Options: tcp or udp
+#
+# [*status_log*]
+# String. Logfile for periodic dumps of the vpn service status
+# Default: "${name}/openvpn-status.log"
+#
+# [*server*]
+# String. Network to assign client addresses out of
+# Default: None. Required in tun mode, not in tap mode
+#
+# [*push*]
+# Array. Options to push out to the client. This can include routes, DNS
+# servers, DNS search domains, and many other options.
+# Default: []
+#
+#
+# === Examples
+#
+# openvpn::client {
+# 'my_user':
+# server => 'contractors',
+# remote_host => 'vpn.mycompany.com'
+# }
+#
+# * Removal:
+# Manual process right now, todo for the future
+#
+#
+# === Authors
+#
+# * Raffael Schmid <mailto:raffael@yux.ch>
+# * John Kinsella <mailto:jlkinsel@gmail.com>
+# * Justin Lambert <mailto:jlambert@letsevenup.com>
+#
+# === License
+#
+# Copyright 2013 Raffael Schmid, <raffael@yux.ch>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+define openvpn::server(
+ $country,
+ $province,
+ $city,
+ $organization,
+ $email,
+ $compression = 'comp-lzo',
+ $dev = 'tun0',
+ $user = 'nobody',
+ $group = false,
+ $ipp = false,
+ $ip_pool = [],
+ $local = $::ipaddress_eth0,
+ $logfile = false,
+ $port = '1194',
+ $proto = 'tcp',
+ $status_log = "${name}/openvpn-status.log",
+ $server = '',
+ $push = []
+) {
+
+ include openvpn
+ Class['openvpn::install'] ->
+ Openvpn::Server[$name] ~>
+ Class['openvpn::service']
+
+ $tls_server = $proto ? {
+ /tcp/ => true,
+ default => false
+ }
+
+ $group_to_set = $group ? {
+ false => $openvpn::params::group,
+ default => $group
+ }
+
+ file {
+ ["/etc/openvpn/${name}", "/etc/openvpn/${name}/client-configs", "/etc/openvpn/${name}/download-configs" ]:
+ ensure => directory;
+ }
+
+ exec {
+ "copy easy-rsa to openvpn config folder ${name}":
+ command => "/bin/cp -r ${openvpn::params::easyrsa_source} /etc/openvpn/${name}/easy-rsa",
+ creates => "/etc/openvpn/${name}/easy-rsa",
+ notify => Exec["fix_easyrsa_file_permissions_${name}"],
+ require => File["/etc/openvpn/${name}"];
+ }
+
+ exec {
+ "fix_easyrsa_file_permissions_${name}":
+ refreshonly => true,
+ command => "/bin/chmod 755 /etc/openvpn/${name}/easy-rsa/*";
+ }
+
+ file {
+ "/etc/openvpn/${name}/easy-rsa/vars":
+ ensure => present,
+ content => template('openvpn/vars.erb'),
+ require => Exec["copy easy-rsa to openvpn config folder ${name}"];
+ }
+
+ file {
+ "/etc/openvpn/${name}/easy-rsa/openssl.cnf":
+ require => Exec["copy easy-rsa to openvpn config folder ${name}"];
+ }
+
+ if $openvpn::params::link_openssl_cnf == true {
+ File["/etc/openvpn/${name}/easy-rsa/openssl.cnf"] {
+ ensure => link,
+ target => "/etc/openvpn/${name}/easy-rsa/openssl-1.0.0.cnf"
+ }
+ }
+
+ exec {
+ "generate dh param ${name}":
+ command => '. ./vars && ./clean-all && ./build-dh',
+ cwd => "/etc/openvpn/${name}/easy-rsa",
+ creates => "/etc/openvpn/${name}/easy-rsa/keys/dh1024.pem",
+ provider => 'shell',
+ require => File["/etc/openvpn/${name}/easy-rsa/vars"];
+
+ "initca ${name}":
+ command => '. ./vars && ./pkitool --initca',
+ cwd => "/etc/openvpn/${name}/easy-rsa",
+ creates => "/etc/openvpn/${name}/easy-rsa/keys/ca.key",
+ provider => 'shell',
+ require => [ Exec["generate dh param ${name}"], File["/etc/openvpn/${name}/easy-rsa/openssl.cnf"] ];
+
+ "generate server cert ${name}":
+ command => '. ./vars && ./pkitool --server server',
+ cwd => "/etc/openvpn/${name}/easy-rsa",
+ creates => "/etc/openvpn/${name}/easy-rsa/keys/server.key",
+ provider => 'shell',
+ require => Exec["initca ${name}"];
+ }
+
+ file {
+ "/etc/openvpn/${name}/keys":
+ ensure => link,
+ target => "/etc/openvpn/${name}/easy-rsa/keys",
+ require => Exec["copy easy-rsa to openvpn config folder ${name}"];
+ }
+
+ if $::osfamily == 'Debian' {
+ concat::fragment {
+ "openvpn.default.autostart.${name}":
+ content => "AUTOSTART=\"\$AUTOSTART ${name}\"\n",
+ target => '/etc/default/openvpn',
+ order => 10;
+ }
+ }
+
+ file {
+ "/etc/openvpn/${name}.conf":
+ owner => root,
+ group => root,
+ mode => '0444',
+ content => template('openvpn/server.erb');
+ }
+}
diff --git a/puppet/modules/openvpn/manifests/service.pp b/puppet/modules/openvpn/manifests/service.pp
new file mode 100644
index 00000000..54e8db7d
--- /dev/null
+++ b/puppet/modules/openvpn/manifests/service.pp
@@ -0,0 +1,36 @@
+# == Class: openvpn::config
+#
+# This class maintains the openvpn service
+#
+#
+# === Examples
+#
+# This class should not be directly invoked
+#
+# === Authors
+#
+# * Raffael Schmid <mailto:raffael@yux.ch>
+# * John Kinsella <mailto:jlkinsel@gmail.com>
+# * Justin Lambert <mailto:jlambert@letsevenup.com>
+#
+# === License
+#
+# Copyright 2013 Raffael Schmid, <raffael@yux.ch>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# lied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+class openvpn::service {
+ service {
+ 'openvpn':
+ ensure => running,
+ enable => true,
+ hasrestart => true,
+ hasstatus => true;
+ }
+}
diff --git a/puppet/modules/openvpn/spec/classes/openvpn_config_spec.rb b/puppet/modules/openvpn/spec/classes/openvpn_config_spec.rb
new file mode 100644
index 00000000..bbb63a77
--- /dev/null
+++ b/puppet/modules/openvpn/spec/classes/openvpn_config_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+describe 'openvpn::config', :type => :class do
+
+ it { should create_class('openvpn::config') }
+
+ context "on Debian based machines" do
+ let (:facts) { { :osfamily => 'Debian', :concat_basedir => '/var/lib/puppet/concat' } }
+
+ it { should contain_class('concat::setup') }
+ it { should contain_concat('/etc/default/openvpn') }
+ it { should contain_concat__fragment('openvpn.default.header') }
+ end
+
+end
diff --git a/puppet/modules/openvpn/spec/classes/openvpn_init_spec.rb b/puppet/modules/openvpn/spec/classes/openvpn_init_spec.rb
new file mode 100644
index 00000000..45dcc9bf
--- /dev/null
+++ b/puppet/modules/openvpn/spec/classes/openvpn_init_spec.rb
@@ -0,0 +1,9 @@
+require 'spec_helper'
+
+describe 'openvpn', :type => :class do
+
+ let (:facts) { { :concat_basedir => '/var/lib/puppet/concat' } }
+
+ it { should create_class('openvpn') }
+
+end
diff --git a/puppet/modules/openvpn/spec/classes/openvpn_install_spec.rb b/puppet/modules/openvpn/spec/classes/openvpn_install_spec.rb
new file mode 100644
index 00000000..cdb31358
--- /dev/null
+++ b/puppet/modules/openvpn/spec/classes/openvpn_install_spec.rb
@@ -0,0 +1,11 @@
+require 'spec_helper'
+
+describe 'openvpn::install', :type => :class do
+
+ it { should create_class('openvpn::install') }
+ it { should contain_package('openvpn') }
+
+ it { should contain_file('/etc/openvpn').with('ensure' => 'directory') }
+ it { should contain_file('/etc/openvpn/keys').with('ensure' => 'directory') }
+
+end
diff --git a/puppet/modules/openvpn/spec/classes/openvpn_service_spec.rb b/puppet/modules/openvpn/spec/classes/openvpn_service_spec.rb
new file mode 100644
index 00000000..f427e7f1
--- /dev/null
+++ b/puppet/modules/openvpn/spec/classes/openvpn_service_spec.rb
@@ -0,0 +1,13 @@
+require 'spec_helper'
+
+describe 'openvpn::service', :type => :class do
+
+ let (:facts) { { :concat_basedir => '/var/lib/puppet/concat' } }
+
+ it { should create_class('openvpn::service') }
+ it { should contain_service('openvpn').with(
+ 'ensure' => 'running',
+ 'enable' => true
+ ) }
+
+end
diff --git a/puppet/modules/openvpn/spec/defines/openvpn_client_spec.rb b/puppet/modules/openvpn/spec/defines/openvpn_client_spec.rb
new file mode 100644
index 00000000..a4b580e8
--- /dev/null
+++ b/puppet/modules/openvpn/spec/defines/openvpn_client_spec.rb
@@ -0,0 +1,88 @@
+require 'spec_helper'
+
+describe 'openvpn::client', :type => :define do
+ let(:title) { 'test_client' }
+ let(:params) { { 'server' => 'test_server' } }
+ let(:facts) { { :fqdn => 'somehost', :concat_basedir => '/var/lib/puppet/concat' } }
+ let(:pre_condition) do
+ 'openvpn::server { "test_server":
+ country => "CO",
+ province => "ST",
+ city => "Some City",
+ organization => "example.org",
+ email => "testemail@example.org"
+ }'
+ end
+
+ it { should contain_exec('generate certificate for test_client in context of test_server') }
+
+ [ 'test_client', 'test_client/keys'].each do |directory|
+ it { should contain_file("/etc/openvpn/test_server/download-configs/#{directory}") }
+ end
+
+ [ 'test_client.crt', 'test_client.key', 'ca.crt' ].each do |file|
+ it { should contain_file("/etc/openvpn/test_server/download-configs/test_client/keys/#{file}").with(
+ 'ensure' => 'link',
+ 'target' => "/etc/openvpn/test_server/easy-rsa/keys/#{file}"
+ )}
+ end
+
+ it { should contain_exec('tar the thing test_server with test_client').with(
+ 'cwd' => '/etc/openvpn/test_server/download-configs/',
+ 'command' => '/bin/rm test_client.tar.gz; tar --exclude=\*.conf.d -chzvf test_client.tar.gz test_client'
+ ) }
+
+ context "setting the minimum parameters" do
+ let(:params) { { 'server' => 'test_server' } }
+ let(:facts) { { :fqdn => 'somehost', :concat_basedir => '/var/lib/puppet/concat' } }
+
+ it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^client$/)}
+ it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^ca\s+keys\/ca\.crt$/)}
+ it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^cert\s+keys\/test_client.crt$/)}
+ it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^key\s+keys\/test_client\.key$/)}
+ it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^dev\s+tun$/)}
+ it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^proto\s+tcp$/)}
+ it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^remote\s+somehost\s+1194$/)}
+ it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^comp-lzo$/)}
+ it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^resolv-retry\s+infinite$/)}
+ it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^nobind$/)}
+ it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^persist-key$/)}
+ it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^persist-tun$/)}
+ it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^mute-replay-warnings$/)}
+ it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^ns\-cert\-type\s+server$/)}
+ it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^verb\s+3$/)}
+ it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^mute\s+20$/)}
+ end
+
+ context "setting all of the parameters" do
+ let(:params) { {
+ 'server' => 'test_server',
+ 'compression' => 'comp-something',
+ 'dev' => 'tap',
+ 'mute' => 10,
+ 'mute_replay_warnings' => false,
+ 'nobind' => false,
+ 'persist_key' => false,
+ 'persist_tun' => false,
+ 'port' => '123',
+ 'proto' => 'udp',
+ 'remote_host' => 'somewhere',
+ 'resolv_retry' => '2m',
+ 'verb' => '1'
+ } }
+ let(:facts) { { :fqdn => 'somehost', :concat_basedir => '/var/lib/puppet/concat' } }
+
+ it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^client$/)}
+ it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^ca\s+keys\/ca\.crt$/)}
+ it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^cert\s+keys\/test_client.crt$/)}
+ it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^key\s+keys\/test_client\.key$/)}
+ it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^dev\s+tap$/)}
+ it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^proto\s+udp$/)}
+ it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^remote\s+somewhere\s+123$/)}
+ it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^comp-something$/)}
+ it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^resolv-retry\s+2m$/)}
+ it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^verb\s+1$/)}
+ it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^mute\s+10$/)}
+ end
+
+end
diff --git a/puppet/modules/openvpn/spec/defines/openvpn_client_specific_config_spec.rb b/puppet/modules/openvpn/spec/defines/openvpn_client_specific_config_spec.rb
new file mode 100644
index 00000000..cfdab389
--- /dev/null
+++ b/puppet/modules/openvpn/spec/defines/openvpn_client_specific_config_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+describe 'openvpn::client_specific_config', :type => :define do
+ let(:title) { 'test_client' }
+ let(:params) { { 'server' => 'test_server' } }
+ let(:facts) { { :fqdn => 'somehost', :concat_basedir => '/var/lib/puppet/concat' } }
+ let(:pre_condition) do
+ [
+ 'openvpn::server { "test_server":
+ country => "CO",
+ province => "ST",
+ city => "Some City",
+ organization => "example.org",
+ email => "testemail@example.org"
+ }',
+ 'openvpn::client { "test_client":
+ server => "test_server"
+ }'
+ ].join
+ end
+
+ it { should contain_file('/etc/openvpn/test_server/client-configs/test_client') }
+
+ describe "setting no paramter at all" do
+ it { should contain_file('/etc/openvpn/test_server/client-configs/test_client').with_content(/\A\n\z/) }
+ end
+
+ describe "setting all parameters" do
+ let(:params) do
+ {:server => 'test_server',
+ :iroute => ['10.0.1.0 255.255.255.0'],
+ :ifconfig => '10.10.10.2 255.255.255.0',
+ :dhcp_options => ['DNS 8.8.8.8']}
+ end
+
+ it { should contain_file('/etc/openvpn/test_server/client-configs/test_client').with_content(/^iroute 10.0.1.0 255.255.255.0$/) }
+ it { should contain_file('/etc/openvpn/test_server/client-configs/test_client').with_content(/^ifconfig-push 10.10.10.2 255.255.255.0$/) }
+ it { should contain_file('/etc/openvpn/test_server/client-configs/test_client').with_content(/^push dhcp-option DNS 8.8.8.8$/) }
+ end
+end
diff --git a/puppet/modules/openvpn/spec/defines/openvpn_server_spec.rb b/puppet/modules/openvpn/spec/defines/openvpn_server_spec.rb
new file mode 100644
index 00000000..467be6aa
--- /dev/null
+++ b/puppet/modules/openvpn/spec/defines/openvpn_server_spec.rb
@@ -0,0 +1,165 @@
+require 'spec_helper'
+
+describe 'openvpn::server', :type => :define do
+
+ let(:title) { 'test_server' }
+
+ context "creating a server with the minimum parameters" do
+ let(:params) { {
+ 'country' => 'CO',
+ 'province' => 'ST',
+ 'city' => 'Some City',
+ 'organization' => 'example.org',
+ 'email' => 'testemail@example.org'
+ } }
+
+ let (:facts) { {
+ :ipaddress_eth0 => '1.2.3.4',
+ :network_eth0 => '1.2.3.0',
+ :netmask_eth0 => '255.255.255.0',
+ :concat_basedir => '/var/lib/puppet/concat',
+ :osfamily => 'anything_else'
+ } }
+
+ # Files associated with a server config
+ it { should contain_file('/etc/openvpn/test_server').with('ensure' => 'directory')}
+ it { should contain_file('/etc/openvpn/test_server/client-configs').with('ensure' => 'directory')}
+ it { should contain_file('/etc/openvpn/test_server/download-configs').with('ensure' => 'directory')}
+ it { should contain_file('/etc/openvpn/test_server/easy-rsa/vars')}
+ it { should contain_file('/etc/openvpn/test_server/easy-rsa/openssl.cnf')}
+ it { should contain_file('/etc/openvpn/test_server/keys').with(
+ 'ensure' => 'link',
+ 'target' => '/etc/openvpn/test_server/easy-rsa/keys'
+ )}
+
+ # Execs to working with certificates
+ it { should contain_exec('copy easy-rsa to openvpn config folder test_server').with(
+ 'command' => '/bin/cp -r /usr/share/doc/openvpn/examples/easy-rsa/2.0 /etc/openvpn/test_server/easy-rsa'
+ )}
+ it { should contain_exec('generate dh param test_server') }
+ it { should contain_exec('initca test_server') }
+ it { should contain_exec('generate server cert test_server') }
+
+ # VPN server config file itself
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^mode\s+server$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^client\-config\-dir\s+\/etc\/openvpn\/test_server\/client\-configs$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^ca\s+\/etc\/openvpn\/test_server\/keys\/ca.crt$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^cert\s+\/etc\/openvpn\/test_server\/keys\/server.crt$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^key\s+\/etc\/openvpn\/test_server\/keys\/server.key$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^dh\s+\/etc\/openvpn\/test_server\/keys\/dh1024.pem$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^proto\s+tcp-server$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^tls-server$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^port\s+1194$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^comp-lzo$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^group\s+nogroup$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^user\s+nobody$/) }
+ it { should_not contain_file('/etc/openvpn/test_server.conf').with_content(/^log\-append\s+test_server\/openvpn\.log$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^status\s+test_server\/openvpn\-status\.log$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^dev\s+tun0$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^local\s+1\.2\.3\.4$/) }
+ it { should_not contain_file('/etc/openvpn/test_server.conf').with_content(/^ifconfig-pool-persist/) }
+ end
+
+ context "creating a server setting all parameters" do
+ let(:params) { {
+ 'country' => 'CO',
+ 'province' => 'ST',
+ 'city' => 'Some City',
+ 'organization' => 'example.org',
+ 'email' => 'testemail@example.org',
+ 'compression' => 'fake_compression',
+ 'port' => '123',
+ 'proto' => 'udp',
+ 'group' => 'someone',
+ 'user' => 'someone',
+ 'logfile' => '/var/log/openvpn/test_server.log',
+ 'status_log' => '/var/log/openvpn/test_server_status.log',
+ 'dev' => 'tun1',
+ 'local' => '2.3.4.5',
+ 'ipp' => true,
+ 'server' => '2.3.4.0 255.255.0.0',
+ 'push' => [ 'dhcp-option DNS 172.31.0.30', 'route 172.31.0.0 255.255.0.0' ]
+ } }
+
+ let (:facts) { {
+ :ipaddress_eth0 => '1.2.3.4',
+ :network_eth0 => '1.2.3.0',
+ :netmask_eth0 => '255.255.255.0',
+ :concat_basedir => '/var/lib/puppet/concat'
+ } }
+
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^mode\s+server$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^client\-config\-dir\s+\/etc\/openvpn\/test_server\/client\-configs$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^ca\s+\/etc\/openvpn\/test_server\/keys\/ca.crt$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^cert\s+\/etc\/openvpn\/test_server\/keys\/server.crt$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^key\s+\/etc\/openvpn\/test_server\/keys\/server.key$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^dh\s+\/etc\/openvpn\/test_server\/keys\/dh1024.pem$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^proto\s+udp$/) }
+ it { should_not contain_file('/etc/openvpn/test_server.conf').with_content(/^proto\s+tls-server$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^port\s+123$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^fake_compression$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^group\s+someone$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^user\s+someone$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^log\-append\s+\/var\/log\/openvpn\/test_server\.log$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^status\s+\/var\/log\/openvpn\/test_server_status\.log$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^dev\s+tun1$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^local\s+2\.3\.4\.5$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^server\s+2\.3\.4\.0\s+255\.255\.0\.0$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^push\s+dhcp-option\s+DNS\s+172\.31\.0\.30$/) }
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^push\s+route\s+172\.31\.0\.0\s+255\.255\.0\.0$/) }
+ end
+
+ context "when RedHat based machine" do
+ let(:params) { {
+ 'country' => 'CO',
+ 'province' => 'ST',
+ 'city' => 'Some City',
+ 'organization' => 'example.org',
+ 'email' => 'testemail@example.org'
+ } }
+
+ let(:facts) { { :osfamily => 'RedHat', :concat_basedir => '/var/lib/puppet/concat' } }
+
+ it { should contain_file('/etc/openvpn/test_server/easy-rsa/openssl.cnf').with(
+ 'ensure' => 'link',
+ 'target' => '/etc/openvpn/test_server/easy-rsa/openssl-1.0.0.cnf'
+ )}
+
+ it { should contain_exec('copy easy-rsa to openvpn config folder test_server').with(
+ 'command' => '/bin/cp -r /usr/share/doc/openvpn-2.2.2/easy-rsa/2.0 /etc/openvpn/test_server/easy-rsa'
+ )}
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^group\s+nobody$/) }
+
+ end
+
+ context "when Debian based machine" do
+ let(:params) { {
+ 'country' => 'CO',
+ 'province' => 'ST',
+ 'city' => 'Some City',
+ 'organization' => 'example.org',
+ 'email' => 'testemail@example.org'
+ } }
+
+ let(:facts) { { :osfamily => 'Debian', :concat_basedir => '/var/lib/puppet/concat' } }
+
+ it { should contain_file('/etc/openvpn/test_server/easy-rsa/openssl.cnf').with(
+ 'ensure' => 'link',
+ 'target' => '/etc/openvpn/test_server/easy-rsa/openssl-1.0.0.cnf'
+ )}
+
+ it { should contain_exec('copy easy-rsa to openvpn config folder test_server').with(
+ 'command' => '/bin/cp -r /usr/share/doc/openvpn/examples/easy-rsa/2.0 /etc/openvpn/test_server/easy-rsa'
+ )}
+
+ # Configure to start vpn session
+ it { should contain_concat__fragment('openvpn.default.autostart.test_server').with(
+ 'content' => "AUTOSTART=\"$AUTOSTART test_server\"\n",
+ 'target' => '/etc/default/openvpn'
+ )}
+
+ it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^group\s+nogroup$/) }
+
+ end
+
+end
diff --git a/puppet/modules/openvpn/spec/spec_helper.rb b/puppet/modules/openvpn/spec/spec_helper.rb
new file mode 100644
index 00000000..dc7e9f4a
--- /dev/null
+++ b/puppet/modules/openvpn/spec/spec_helper.rb
@@ -0,0 +1,2 @@
+require 'rubygems'
+require 'puppetlabs_spec_helper/module_spec_helper'
diff --git a/puppet/modules/openvpn/templates/client.erb b/puppet/modules/openvpn/templates/client.erb
new file mode 100644
index 00000000..021ed617
--- /dev/null
+++ b/puppet/modules/openvpn/templates/client.erb
@@ -0,0 +1,26 @@
+client
+ca keys/ca.crt
+cert keys/<%= scope.lookupvar('name') %>.crt
+key keys/<%= scope.lookupvar('name') %>.key
+dev <%= scope.lookupvar('dev') %>
+proto <%= scope.lookupvar('proto') %>
+remote <%= scope.lookupvar('remote_host') %> <%= scope.lookupvar('port') %>
+<% if scope.lookupvar('compression') != '' -%>
+<%= scope.lookupvar('compression') %>
+<% end -%>
+resolv-retry <%= scope.lookupvar('resolv_retry') %>
+<% if scope.lookupvar('nobind') -%>
+nobind
+<% end -%>
+<% if scope.lookupvar('persist_key') -%>
+persist-key
+<% end -%>
+<% if scope.lookupvar('persist_tun') -%>
+persist-tun
+<% end -%>
+<% if scope.lookupvar('mute_replay_warnings') -%>
+mute-replay-warnings
+<% end -%>
+ns-cert-type server
+verb <%= scope.lookupvar('verb') %>
+mute <%= scope.lookupvar('mute') %>
diff --git a/puppet/modules/openvpn/templates/client_specific_config.erb b/puppet/modules/openvpn/templates/client_specific_config.erb
new file mode 100644
index 00000000..62cc0e7a
--- /dev/null
+++ b/puppet/modules/openvpn/templates/client_specific_config.erb
@@ -0,0 +1,10 @@
+<% scope.lookupvar('iroute').each do |route| -%>
+iroute <%= route %>
+<% end -%>
+<% if ifconfig = scope.lookupvar('ifconfig') -%>
+ifconfig-push <%= ifconfig %>
+<% end -%>
+<% scope.lookupvar('dhcp_options').each do |option| -%>
+push dhcp-option <%= option %>
+<% end -%>
+
diff --git a/puppet/modules/openvpn/templates/etc-default-openvpn.erb b/puppet/modules/openvpn/templates/etc-default-openvpn.erb
new file mode 100644
index 00000000..310e462e
--- /dev/null
+++ b/puppet/modules/openvpn/templates/etc-default-openvpn.erb
@@ -0,0 +1,20 @@
+# This is the configuration file for /etc/init.d/openvpn
+
+#
+# Start only these VPNs automatically via init script.
+# Allowed values are "all", "none" or space separated list of
+# names of the VPNs. If empty, "all" is assumed.
+#
+#AUTOSTART="all"
+#AUTOSTART="none"
+#AUTOSTART="home office"
+#
+# Refresh interval (in seconds) of default status files
+# located in /var/run/openvpn.$NAME.status
+# Defaults to 10, 0 disables status file generation
+#
+#STATUSREFRESH=10
+#STATUSREFRESH=0
+# Optional arguments to openvpn's command line
+OPTARGS=""
+AUTOSTART=""
diff --git a/puppet/modules/openvpn/templates/server.erb b/puppet/modules/openvpn/templates/server.erb
new file mode 100644
index 00000000..6ef13263
--- /dev/null
+++ b/puppet/modules/openvpn/templates/server.erb
@@ -0,0 +1,37 @@
+mode server
+client-config-dir /etc/openvpn/<%= scope.lookupvar('name') %>/client-configs
+ca /etc/openvpn/<%= scope.lookupvar('name') %>/keys/ca.crt
+cert /etc/openvpn/<%= scope.lookupvar('name') %>/keys/server.crt
+key /etc/openvpn/<%= scope.lookupvar('name') %>/keys/server.key
+dh /etc/openvpn/<%= scope.lookupvar('name') %>/keys/dh1024.pem
+<% if scope.lookupvar('proto') == 'tcp' -%>
+proto <%= scope.lookupvar('proto') %>-server
+<% else -%>
+proto <%= scope.lookupvar('proto') %>
+<% end -%>
+port <%= scope.lookupvar('port') %>
+<% if scope.lookupvar('tls_server') -%>
+tls-server
+<% end -%>
+<% if scope.lookupvar('compression') != '' -%>
+<%= scope.lookupvar('compression') %>
+<% end -%>
+group <%= scope.lookupvar('group_to_set') %>
+user <%= scope.lookupvar('user') %>
+<% if scope.lookupvar('logfile') -%>
+log-append <%= scope.lookupvar('logfile') %>
+<% end -%>
+status <%= scope.lookupvar('status_log') %>
+dev <%= scope.lookupvar('dev') %>
+<% if scope.lookupvar('local') != '' -%>
+local <%= scope.lookupvar('local') %>
+<% end -%>
+<% if scope.lookupvar('ipp') -%>
+ifconfig-pool-persist <%= scope.lookupvar('name') %>/vpn-ipp.txt
+<% end -%>
+<% if scope.lookupvar('server') != '' -%>
+server <%= scope.lookupvar('server') %>
+<% end -%>
+<% scope.lookupvar('push').each do |item| -%>
+push <%= item %>
+<% end -%>
diff --git a/puppet/modules/openvpn/templates/vars.erb b/puppet/modules/openvpn/templates/vars.erb
new file mode 100644
index 00000000..20448b8b
--- /dev/null
+++ b/puppet/modules/openvpn/templates/vars.erb
@@ -0,0 +1,68 @@
+# easy-rsa parameter settings
+
+# NOTE: If you installed from an RPM,
+# don't edit this file in place in
+# /usr/share/openvpn/easy-rsa --
+# instead, you should copy the whole
+# easy-rsa directory to another location
+# (such as /etc/openvpn) so that your
+# edits will not be wiped out by a future
+# OpenVPN package upgrade.
+
+# This variable should point to
+# the top level of the easy-rsa
+# tree.
+export EASY_RSA="/etc/openvpn/<%= @name %>/easy-rsa"
+
+#
+# This variable should point to
+# the requested executables
+#
+export OPENSSL="openssl"
+export PKCS11TOOL="pkcs11-tool"
+export GREP="grep"
+
+
+# This variable should point to
+# the openssl.cnf file included
+# with easy-rsa.
+export KEY_CONFIG=`$EASY_RSA/whichopensslcnf $EASY_RSA`
+
+# Edit this variable to point to
+# your soon-to-be-created key
+# directory.
+#
+# WARNING: clean-all will do
+# a rm -rf on this directory
+# so make sure you define
+# it correctly!
+export KEY_DIR="$EASY_RSA/keys"
+
+# Issue rm -rf warning
+echo NOTE: If you run ./clean-all, I will be doing a rm -rf on $KEY_DIR
+
+# PKCS11 fixes
+export PKCS11_MODULE_PATH="dummy"
+export PKCS11_PIN="dummy"
+
+# Increase this to 2048 if you
+# are paranoid. This will slow
+# down TLS negotiation performance
+# as well as the one-time DH parms
+# generation process.
+export KEY_SIZE=1024
+
+# In how many days should the root CA key expire?
+export CA_EXPIRE=3650
+
+# In how many days should certificates expire?
+export KEY_EXPIRE=3650
+
+# These are the default values for fields
+# which will be placed in the certificate.
+# Don't leave any of these fields blank.
+export KEY_COUNTRY="<%= @country %>"
+export KEY_PROVINCE="<%= @province %>"
+export KEY_CITY="<%= @city %>"
+export KEY_ORG="<%= @organization %>"
+export KEY_EMAIL="<%= @email %>"
diff --git a/puppet/modules/openvpn/vagrant/client.pp b/puppet/modules/openvpn/vagrant/client.pp
new file mode 100644
index 00000000..7ebeb1d7
--- /dev/null
+++ b/puppet/modules/openvpn/vagrant/client.pp
@@ -0,0 +1,5 @@
+node default {
+
+ package { 'openvpn': ensure => installed; }
+
+}
diff --git a/puppet/modules/openvpn/vagrant/server.pp b/puppet/modules/openvpn/vagrant/server.pp
new file mode 100644
index 00000000..a95def06
--- /dev/null
+++ b/puppet/modules/openvpn/vagrant/server.pp
@@ -0,0 +1,23 @@
+node default {
+ openvpn::server { 'winterthur':
+ country => 'CH',
+ province => 'ZH',
+ city => 'Winterthur',
+ organization => 'example.org',
+ email => 'root@example.org',
+ server => '10.200.200.0 255.255.255.0'
+ }
+
+ openvpn::client { 'client1':
+ server => 'winterthur';
+ }
+
+ openvpn::client_specific_config { 'client1':
+ server => 'winterthur',
+ ifconfig => '10.200.200.100 255.255.255.0'
+ }
+
+ openvpn::client { 'client2':
+ server => 'winterthur';
+ }
+}
diff --git a/puppet/modules/passenger/README b/puppet/modules/passenger/README
new file mode 100644
index 00000000..549432e2
--- /dev/null
+++ b/puppet/modules/passenger/README
@@ -0,0 +1,42 @@
+Passenger (mod_rails) puppet module
+-----------------------------------
+
+This puppet module handles a passenger setup, it installs the
+packages, and configures some munin graphs.
+
+Dependencies
+------------
+
+This module expects you to have:
+ . apache module
+
+Optional:
+ . munin module
+
+Getting started
+---------------
+
+Simply do 'include passenger' and it will be installed.
+
+Configuration
+-------------
+
+If you need to install a specific version of passenger or
+librack-ruby, you can specify the version to be installed by providing
+a variable, for example:
+
+class { 'passenger':
+ passenger_ensure_version => '2.2.23-2~bpo50+1',
+ librack-ruby_ensure_version = "1.0.0-2~bpo50+1"
+}
+
+If you wish to use gems, pass 'use_gems => true'.
+
+By default munin will be used, but you can disable that by passing
+'use_munin => false'.
+
+If you need to set different munin plugin configuration values, you
+can also do so as follows:
+
+$passenger_memory_munin_config = "user root\nenv.passenger_memory_stats /opt/bin/passenger-memory-stats"
+$passenger_stats_munin_config = "user root\nenv.PASSENGER_TMPDIR /var/tmp\n"
diff --git a/puppet/modules/passenger/files/mod_passenger.conf b/puppet/modules/passenger/files/mod_passenger.conf
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/puppet/modules/passenger/files/mod_passenger.conf
diff --git a/puppet/modules/passenger/files/munin/passenger_memory_stats b/puppet/modules/passenger/files/munin/passenger_memory_stats
new file mode 100755
index 00000000..eb9b2843
--- /dev/null
+++ b/puppet/modules/passenger/files/munin/passenger_memory_stats
@@ -0,0 +1,123 @@
+#!/usr/bin/env ruby
+pod=<<-POD
+
+=head1 NAME
+passenger_memory_stats - Munin plugin to monitor the memory usage of passenger application servers.
+Monitors the memory consumed by passenger instances.
+
+=head1 APPLICABLE SYSTEMS
+All systems that have passenger installed.
+
+=head1 CONFIGURATION
+The plugin needs to execute passenger-memory-stats.
+This configuration section shows the defaults of the plugin:
+
+ [passenger_*]
+ user root
+ command /usr/local/bin/ruby %c
+
+Options
+ env.passenger_memory_stats '/path/to/passenger-memory-stats' # Path to passenger memory status.
+ env.graph_category 'App' # Graph Category. Defaults to Passenger.
+
+ln -s /usr/share/munin/plugins/passenger_memory_stats /etc/munin/plugins/passenger_memory_stats
+
+=head1 INTERPRETATION
+The plugin shows the memory consumed by passenger instances.
+
+=head1 MAGIC MARKERS
+ #%# family=auto
+ #%# capabilities=autoconf
+
+=head1 VERSION
+1.5
+
+=head1 BUGS
+None known
+
+=head1 AUTHOR
+Ilya Lityuga
+Bart ten Brinke - railsdoctors.com
+
+=head1 LICENSE
+MIT
+
+POD
+
+# Globals
+GRAPH_CATEGORY = ENV['graph_category'] || 'Passenger'
+PASSENGER_MEMORY_STATS = ENV['passenger_memory_stats'] || '/usr/local/bin/passenger-memory-stats'
+
+# Check if this plugin can run
+def autoconf
+ begin
+ require 'rubygems'
+ gem "passenger", ">=2.0"
+ rescue Exception => e
+ puts "no (Gem not found: #{e})"
+ exit 1
+ end
+
+ status = `#{PASSENGER_MEMORY_STATS}`
+ unless $?.success?
+ puts "no (error when executing #{PASSENGER_MEMORY_STATS})"
+ exit 1
+ end
+
+ puts "yes"
+ exit 0
+end
+
+# Describe the graph config
+def config
+ status = `#{PASSENGER_MEMORY_STATS}`
+ memory_info = open('/proc/meminfo', 'r') do |lines|
+ lines.inject({}) do |h, line|
+ matched = line.match(/^([\w_\(\)]+):\s+(\d+)/)
+ h[matched[1].to_sym] = matched[2].to_i * 1024
+ h
+ end
+ end
+ upper_limit = memory_info[:MemTotal]
+ puts <<-CONFIG
+graph_category #{GRAPH_CATEGORY}
+graph_title Passenger memory stats
+graph_vlabel Bytes
+graph_args --base 1000 -l 0 --upper-limit #{upper_limit}
+graph_info The memory used by passenger instances on this application server
+
+memory.label memory
+CONFIG
+ exit 0
+end
+
+
+# Collect the data
+# <tt>debug</tt> Show debug information
+def run(debug = false)
+ stats = `#{PASSENGER_MEMORY_STATS}`
+
+ unless $?.success?
+ $stderr.puts "failed executing passenger-memory-stats"
+ exit 1
+ end
+
+ puts stats if debug
+
+ #### Total private dirty RSS: 81.81 MB
+ stats =~ /RSS:\s*([\d\.]+)\s*MB\Z/m
+ memory = ($1.to_f * 1024 * 1024).to_i
+ puts "memory.value #{memory}"
+end
+
+
+# Main
+if ARGV[0] == "config"
+ config
+elsif ARGV[0] == "autoconf"
+ autoconf
+elsif ARGV[0] == "debug"
+ run(true)
+else
+ run
+end
diff --git a/puppet/modules/passenger/files/munin/passenger_stats b/puppet/modules/passenger/files/munin/passenger_stats
new file mode 100755
index 00000000..f06e88a0
--- /dev/null
+++ b/puppet/modules/passenger/files/munin/passenger_stats
@@ -0,0 +1,47 @@
+#!/usr/bin/env ruby
+
+PASSENGER_STATUS = ENV['passenger_status'] || '/usr/local/bin/passenger-status'
+
+def output_config
+ puts <<-END
+graph_category Passenger
+graph_title passenger status
+graph_vlabel count
+
+sessions.label sessions
+max.label max processes
+running.label running processes
+active.label active processes
+inactive.label inactive processes
+END
+ exit 0
+end
+
+def output_values
+ status = `#{PASSENGER_STATUS}`
+ unless $?.success?
+ $stderr.puts "failed executing passenger-status"
+ exit 1
+ end
+ status =~ /max\s+=\s+(\d+)/
+ puts "max.value #{$1}"
+
+ status =~ /count\s+=\s+(\d+)/
+ puts "running.value #{$1}"
+
+ status =~ /active\s+=\s+(\d+)/
+ puts "active.value #{$1}"
+
+ status =~ /inactive\s+=\s+(\d+)/
+ puts "inactive.value #{$1}"
+
+ total_sessions = 0
+ status.scan(/Sessions: (\d+)/).flatten.each { |count| total_sessions += count.to_i }
+ puts "sessions.value #{total_sessions}"
+end
+
+if ARGV[0] == "config"
+ output_config
+else
+ output_values
+end
diff --git a/puppet/modules/passenger/manifests/apache.pp b/puppet/modules/passenger/manifests/apache.pp
new file mode 100644
index 00000000..d4181ffe
--- /dev/null
+++ b/puppet/modules/passenger/manifests/apache.pp
@@ -0,0 +1,7 @@
+class passenger::apache{
+ case $operatingsystem {
+ centos: { include passenger::apache::centos }
+ debian: { include passenger::apache::debian }
+ defaults: { include passenger::apache::base }
+ }
+}
diff --git a/puppet/modules/passenger/manifests/apache/base.pp b/puppet/modules/passenger/manifests/apache/base.pp
new file mode 100644
index 00000000..441c9bd5
--- /dev/null
+++ b/puppet/modules/passenger/manifests/apache/base.pp
@@ -0,0 +1,4 @@
+class passenger::apache::base {
+ # Todo !
+ include apache
+}
diff --git a/puppet/modules/passenger/manifests/apache/centos.pp b/puppet/modules/passenger/manifests/apache/centos.pp
new file mode 100644
index 00000000..b7b80e3b
--- /dev/null
+++ b/puppet/modules/passenger/manifests/apache/centos.pp
@@ -0,0 +1,24 @@
+class passenger::apache::centos inherits passenger::apache::base {
+
+ package { 'mod_passenger':
+ ensure => installed,
+ require => Package['apache'],
+ }
+
+ file { '/var/www/passenger_buffer':
+ ensure => directory,
+ require => [ Package['apache'], Package['mod_passenger'] ],
+ owner => apache,
+ group => 0,
+ mode => '0600';
+ }
+
+ file{ '/etc/httpd/conf.d/mod_passenger_custom.conf':
+ content => "PassengerUploadBufferDir /var/www/passenger_buffer\n",
+ require => File['/var/www/passenger_buffer'],
+ notify => Service['apache'],
+ owner => root,
+ group => 0,
+ mode => '0644';
+ }
+}
diff --git a/puppet/modules/passenger/manifests/apache/debian.pp b/puppet/modules/passenger/manifests/apache/debian.pp
new file mode 100644
index 00000000..38eb3fa4
--- /dev/null
+++ b/puppet/modules/passenger/manifests/apache/debian.pp
@@ -0,0 +1,24 @@
+class passenger::apache::debian inherits passenger::apache::base {
+
+ package { 'libapache2-mod-passenger':
+ ensure => installed,
+ require => Package['apache2'],
+ }
+
+ file { '/var/www/passenger_buffer':
+ ensure => directory,
+ require => [ Package['apache2'], Package['libapache2-mod-passenger'] ],
+ owner => www-data,
+ group => 0,
+ mode => '0600';
+ }
+
+ file { '/etc/apache2/conf.d/mod_passenger_custom.conf':
+ content => "PassengerUploadBufferDir /var/www/passenger_buffer\n",
+ require => File['/var/www/passenger_buffer'],
+ notify => Service['apache2'],
+ owner => root,
+ group => 0,
+ mode => '0644';
+ }
+}
diff --git a/puppet/modules/passenger/manifests/init.pp b/puppet/modules/passenger/manifests/init.pp
new file mode 100644
index 00000000..ed9b8c31
--- /dev/null
+++ b/puppet/modules/passenger/manifests/init.pp
@@ -0,0 +1,75 @@
+# passenger module
+#
+# Copyright 2010, Riseup Networks
+# Micah Anderson micah(at)riseup.net
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+
+class passenger (
+ $use_gems = false, $manage_munin = false,
+ $passenger_ensure_version = 'installed',
+ $librack_ensure_version = 'installed',
+ $passenger_bin_path = '/usr/sbin' )
+{
+ Class['::apache'] -> Class['passenger']
+
+ if ! $use_gems {
+
+ apache::module { 'passenger':
+ ensure => $passenger_ensure_version,
+ package_name => 'libapache2-mod-passenger';
+ }
+
+ if !defined(Package['librack-ruby']) {
+ if $::lsbdistcodename == 'squeeze' {
+ package { 'librack-ruby1.8': ensure => $librack_ensure_version }
+ }
+ else {
+ package { 'ruby-rack':
+ ensure => $librack_ensure_version;
+ }
+ }
+ }
+ }
+ else {
+ package {
+ 'passenger':
+ ensure => $passenger_ensure_version,
+ provider => gem;
+ 'rack':
+ ensure => $librack_ensure_version,
+ provider => gem;
+ }
+ }
+
+ apache::config::file { 'mod_passenger':
+ ensure => present,
+ source => [ "puppet:///modules/site_passenger/${::fqdn}/mod_passenger.conf",
+ 'puppet:///modules/site_passenger/mod_passenger.conf',
+ 'puppet:///modules/passenger/mod_passenger.conf',
+ ],
+ }
+
+ if $manage_munin {
+ if $passenger_memory_munin_config == '' {
+ $passenger_memory_munin_config = "user root\nenv.passenger_memory_stats ${passenger_bin_path}/passenger-memory-stats"
+ }
+
+ if $passenger_stats_munin_config == '' {
+ $passenger_stats_munin_config = "user root\nenv.passenger_status ${passenger_bin_path}/passenger-status"
+ }
+
+ munin::plugin::deploy {
+ 'passenger_memory_stats':
+ source => 'passenger/munin/passenger_memory_stats',
+ config => $passenger_memory_munin_config;
+ 'passenger_stats':
+ source => 'passenger/munin/passenger_stats',
+ config => $passenger_stats_munin_config;
+ }
+ }
+
+}
+
diff --git a/puppet/modules/passenger/manifests/munin.pp b/puppet/modules/passenger/manifests/munin.pp
new file mode 100644
index 00000000..36bc53f2
--- /dev/null
+++ b/puppet/modules/passenger/manifests/munin.pp
@@ -0,0 +1,20 @@
+class passenger::munin {
+
+ case $passenger_memory_munin_config { '':
+ { $passenger_memory_munin_config = "user root\nenv.passenger_memory_stats /usr/sbin/passenger-memory-stats" }
+ }
+
+ case $passenger_stats_munin_config { '':
+ { $passenger_stats_munin_config = "user root\n" }
+ }
+
+ munin::plugin::deploy {
+ 'passenger_memory_stats':
+ source => 'passenger/munin/passenger_memory_stats',
+ config => $passenger_memory_munin_config;
+ 'passenger_stats':
+ source => 'passenger/munin/passenger_stats',
+ config => $passenger_stats_munin_config;
+ }
+
+}
diff --git a/puppet/modules/postfix/LICENSE b/puppet/modules/postfix/LICENSE
new file mode 100644
index 00000000..94a9ed02
--- /dev/null
+++ b/puppet/modules/postfix/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/puppet/modules/postfix/README.md b/puppet/modules/postfix/README.md
new file mode 100644
index 00000000..7a6b01fc
--- /dev/null
+++ b/puppet/modules/postfix/README.md
@@ -0,0 +1,224 @@
+Postfix Puppet module
+=====================
+
+This module will help install and configure postfix.
+
+A couple of classes will preconfigure postfix for common needs.
+
+This module needs:
+
+- the concat module: git://labs.riseup.net/shared-concat
+
+!! Upgrade Notice (01/2013) !!
+
+This module now uses parameterized classes, where it used global variables
+before. So please whatch out before pulling, you need to change the
+class declarations in your manifest !
+
+Issues
+------
+
+- Debian wheezy hosts (or below): If you get this error msg:
+
+ "Could not find template 'postfix/master.cf.debian-.erb' at /ssrv/leap/puppet/modules/postfix/manifests/init.pp:158 on node rew07plain1.rewire.org"
+
+ you need to use the facter package from wheezy-backports instead of the wheezy one. See https://gitlab.com/shared-puppet-modules-group/postfix/merge_requests/6#note_1892207 for more details.
+
+
+Deprecation notice
+------------------
+
+It used to be that one could drop header checks snippets into the
+following source directories:
+
+ "puppet:///modules/site-postfix/${fqdn}/header_checks.d"
+ "puppet:///modules/site-postfix/header_checks.d"
+ "puppet:///files/etc/postfix/header_checks.d"
+ "puppet:///modules/postfix/header_checks.d"
+
+... and TLS policy snippets into those:
+
+ "puppet:///modules/site-postfix/${fqdn}/tls_policy.d"
+ "puppet:///modules/site-postfix/tls_policy.d"
+ "puppet:///modules/postfix/tls_policy.d"
+
+This is not supported anymore.
+
+Every such snippet much now be configured using the (respectively)
+postfix::header_checks_snippet and postfix::tlspolicy_snippet defines.
+
+Note: You will need to set a global Exec { path => '...' } to a proper pathing
+in your manifests, or you will experience some issues such as:
+
+err: Failed to apply catalog: Parameter unless failed: 'test "x$(postconf -h relay_domains)" == 'xlocalhost host.foo.com'' is not qualified and no path was specified. Please qualify the command or specify a path.
+
+See: http://www.puppetcookbook.com/posts/set-global-exec-path.html for more
+information about how to do this
+
+Postfix class configuration parameters
+--------------------------------------
+
+ * use_amavisd => 'yes' - to include postfix::amavis
+
+ * anon_sasl => 'yes' - to hide the originating IP in email
+ relayed for an authenticated SASL client; this needs Postfix
+ 2.3 or later to work; beware! Postfix logs the header replacement
+ has been done, which means that you are storing this information,
+ unless you are anonymizing your logs.
+
+ * manage_header_checks => 'yes' - to manage header checks (see
+ postfix::header_checks for details)
+
+ * manage_transport_regexp => 'yes' - to manage header checks (see
+ postfix::transport_regexp for details)
+
+ * manage_virtual_regexp => 'yes' - to manage header checks (see
+ postfix::virtual_regexp for details)
+
+ * manage_tls_policy => 'yes - to manage TLS policy (see
+ postfix::tlspolicy for details)
+
+ * inet_interfaces: by default, postfix will bind to all interfaces, but
+ sometimes you don't want that. To bind to specific interfaces, use the
+ 'inet_interfaces' parameter and set it to exactly what would be in the
+ main.cf file.
+
+ * myorigin: some hosts have weird-looking host names (dedicated servers and VPSes). To
+ set the server's domain of origin, set the 'myorigin' parameter
+
+ * smtp_listen: address on which the smtp service will listen (Default: 127.0.0.1)
+
+ * root_mail_recipient: who will receive root's emails (Default: 'nobody')
+
+ * tls_fingerprint_digest: fingerprint digest for tls policy class (Default: 'sha1')
+
+ * use_dovecot_lda: include dovecot declaration at master.cf
+
+ * use_schleuder: whether to include schleuder portion at master.cf
+
+ * use_sympa: whether to include sympa portion at master.cf
+
+ * use_firma: whether to include firma portion at master.cf
+
+ * use_mlmmj: whether to include mlmmj portion at master.cf
+
+ * use_submission: set to "yes" to enable submission section at master.cf
+
+ * use_smtps: set to "yes" to enable smtps section at master.cf
+
+ * mastercf_tail: set this for additional content to be added at the end of master.cf
+
+== Examples:
+
+ class { 'postfix': }
+
+ class { 'postfix': anon_sasl => 'yes', myorigin => 'foo.bar.tz' }
+
+ postfix::config { "relay_domains": value => "localhost host.foo.com" }
+
+
+Convience classes
+=================
+
+postfix::config
+---------------
+this can be used to pass arbitrary postfix configurations by passing the $name
+to postconf to add/alter/remove options in main.cf
+
+Parameters:
+- *name*: name of the parameter.
+- *ensure*: present/absent. defaults to present.
+- *value*: value of the parameter.
+- *nonstandard*: inform postfix::config that this parameter is not recognized
+ by the "postconf" command. defaults to false.
+
+Requires:
+- Class["postfix"]
+
+Example usage:
+
+ postfix::config {
+ "smtp_use_tls" => "yes";
+ "smtp_sasl_auth_enable" => "yes";
+ "smtp_sasl_password_maps" => "hash:/etc/postfix/my_sasl_passwords";
+ "relayhost" => "[mail.example.com]:587";
+ }
+
+
+postfix::disable
+----------------
+If you include this class, the postfix package will be removed and the service
+stopped.
+
+
+postfix::hash
+-------------
+This can be used to create postfix hashed "map" files. It will create "${name}",
+and then build "${name}.db" using the "postmap" command. The map file can then
+be referred to using postfix::config.
+
+Parameters:
+- *name*: the name of the map file.
+- *ensure*: present/absent, defaults to present.
+- *source*: file source.
+
+Requires:
+- Class["postfix"]
+
+Example usage:
+
+ postfix::hash { "/etc/postfix/virtual":
+ ensure => present,
+ }
+ postfix::config { "virtual_alias_maps":
+ value => "hash:/etc/postfix/virtual"
+ }
+
+
+postfix::virtual
+----------------
+Manages content of the /etc/postfix/virtual map
+
+Parameters:
+- *name*: name of address postfix will lookup. See virtual(8).
+- *destination*: where the emails will be delivered to. See virtual(8).
+- *ensure*: present/absent, defaults to present.
+
+Requires:
+- Class["postfix"]
+- Postfix::Hash["/etc/postfix/virtual"]
+- Postfix::Config["virtual_alias_maps"]
+- common::line (from module common)
+
+Example usage:
+
+ postfix::hash { "/etc/postfix/virtual":
+ ensure => present,
+ }
+ postfix::config { "virtual_alias_maps":
+ value => "hash:/etc/postfix/virtual"
+ }
+ postfix::virtual { "user@example.com":
+ ensure => present,
+ destination => "root",
+ }
+
+postfix::mailalias
+------------------
+Wrapper around Puppet mailalias resource, provides newaliases executable.
+
+Parameters:
+- *name*: the name of the alias.
+- *ensure*: present/absent, defaults to present.
+- *recipient*: recipient of the alias.
+
+Requires:
+- Class["postfix"]
+
+Example usage:
+
+ postfix::mailalias { "postmaster":
+ ensure => present,
+ recipient => 'foo'
+ }
+
diff --git a/puppet/modules/postfix/files/header_checks.d/.ignore b/puppet/modules/postfix/files/header_checks.d/.ignore
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/puppet/modules/postfix/files/header_checks.d/.ignore
diff --git a/puppet/modules/postfix/files/main.cf b/puppet/modules/postfix/files/main.cf
new file mode 100644
index 00000000..ec649c71
--- /dev/null
+++ b/puppet/modules/postfix/files/main.cf
@@ -0,0 +1 @@
+# file managed by puppet
diff --git a/puppet/modules/postfix/files/tls_policy.d/.ignore b/puppet/modules/postfix/files/tls_policy.d/.ignore
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/puppet/modules/postfix/files/tls_policy.d/.ignore
diff --git a/puppet/modules/postfix/manifests/amavis.pp b/puppet/modules/postfix/manifests/amavis.pp
new file mode 100644
index 00000000..b6639234
--- /dev/null
+++ b/puppet/modules/postfix/manifests/amavis.pp
@@ -0,0 +1,5 @@
+class postfix::amavis {
+ postfix::config {
+ "content_filter": value => "amavis:[127.0.0.1]:10024";
+ }
+}
diff --git a/puppet/modules/postfix/manifests/anonsasl.pp b/puppet/modules/postfix/manifests/anonsasl.pp
new file mode 100644
index 00000000..ca97f199
--- /dev/null
+++ b/puppet/modules/postfix/manifests/anonsasl.pp
@@ -0,0 +1,18 @@
+class postfix::anonsasl {
+
+ include postfix::header_checks
+
+ postfix::config {
+ 'smtpd_sasl_authenticated_header':
+ value => 'yes';
+ }
+
+ postfix::header_checks_snippet {
+ 'anonsasl':
+ content => template("postfix/anonsasl_header_checks.erb"),
+ require => [
+ Postfix::Config['smtpd_sasl_authenticated_header'],
+ ];
+ }
+
+}
diff --git a/puppet/modules/postfix/manifests/config.pp b/puppet/modules/postfix/manifests/config.pp
new file mode 100644
index 00000000..ce7af9e4
--- /dev/null
+++ b/puppet/modules/postfix/manifests/config.pp
@@ -0,0 +1,49 @@
+/*
+== Definition: postfix::config
+
+Uses the "postconf" command to add/alter/remove options in postfix main
+configuation file (/etc/postfix/main.cf).
+
+Parameters:
+- *name*: name of the parameter.
+- *ensure*: present/absent. defaults to present.
+- *value*: value of the parameter.
+- *nonstandard*: inform postfix::config that this parameter is not recognized
+ by the "postconf" command. defaults to false.
+
+Requires:
+- Class["postfix"]
+
+Example usage:
+
+ node "toto.example.com" {
+
+ class { 'postfix': }
+
+ postfix::config {
+ "smtp_use_tls" => "yes";
+ "smtp_sasl_auth_enable" => "yes";
+ "smtp_sasl_password_maps" => "hash:/etc/postfix/my_sasl_passwords";
+ "relayhost" => "[mail.example.com]:587";
+ }
+ }
+
+*/
+define postfix::config ($ensure = present, $value, $nonstandard = false) {
+ case $ensure {
+ present: {
+ exec {"postconf -e ${name}='${value}'":
+ unless => $nonstandard ? {
+ false => "test \"x$(postconf -h ${name})\" = 'x${value}'",
+ true => "test \"x$(egrep '^${name} ' /etc/postfix/main.cf | cut -d= -f2 | cut -d' ' -f2)\" = 'x${value}'",
+ },
+ notify => Service["postfix"],
+ require => File["/etc/postfix/main.cf"],
+ }
+ }
+
+ absent: {
+ fail "postfix::config ensure => absent: Not implemented"
+ }
+ }
+}
diff --git a/puppet/modules/postfix/manifests/disable.pp b/puppet/modules/postfix/manifests/disable.pp
new file mode 100644
index 00000000..c233ec6d
--- /dev/null
+++ b/puppet/modules/postfix/manifests/disable.pp
@@ -0,0 +1,7 @@
+# remove postfix
+class postfix::disable {
+ case $::operatingsystem {
+ debian: { include postfix::disable::debian }
+ default: { include postfix::disable::base }
+ }
+}
diff --git a/puppet/modules/postfix/manifests/disable/base.pp b/puppet/modules/postfix/manifests/disable/base.pp
new file mode 100644
index 00000000..5c56c709
--- /dev/null
+++ b/puppet/modules/postfix/manifests/disable/base.pp
@@ -0,0 +1,12 @@
+class postfix::disable::base {
+
+ service{'postfix':
+ ensure => stopped,
+ enable => false,
+ }
+ package{'postfix':
+ ensure => absent,
+ require => Service['postfix'],
+ }
+
+}
diff --git a/puppet/modules/postfix/manifests/disable/debian.pp b/puppet/modules/postfix/manifests/disable/debian.pp
new file mode 100644
index 00000000..213efc50
--- /dev/null
+++ b/puppet/modules/postfix/manifests/disable/debian.pp
@@ -0,0 +1,11 @@
+# debian has some issues with absent
+# init scripts.
+# It's a bug in debian's provider that should be fixed in puppet, but in the
+# meantime we need this hack.
+#
+# see: https://projects.puppetlabs.com/issues/9381
+class postfix::disable::debian inherits postfix::disable::base {
+ Service['postfix']{
+ hasstatus => false,
+ }
+}
diff --git a/puppet/modules/postfix/manifests/hash.pp b/puppet/modules/postfix/manifests/hash.pp
new file mode 100644
index 00000000..006f8815
--- /dev/null
+++ b/puppet/modules/postfix/manifests/hash.pp
@@ -0,0 +1,71 @@
+/*
+== Definition: postfix::hash
+
+Creates postfix hashed "map" files. It will create "${name}", and then build
+"${name}.db" using the "postmap" command. The map file can then be referred to
+using postfix::config.
+
+Parameters:
+- *name*: the name of the map file.
+- *ensure*: present/absent, defaults to present.
+- *source*: file source.
+
+Requires:
+- Class["postfix"]
+
+Example usage:
+
+ node "toto.example.com" {
+
+ class { 'postfix': }
+
+ postfix::hash { "/etc/postfix/virtual":
+ ensure => present,
+ }
+ postfix::config { "virtual_alias_maps":
+ value => "hash:/etc/postfix/virtual"
+ }
+ }
+
+*/
+define postfix::hash ($ensure="present", $source = false) {
+ include ::postfix
+ case $source {
+ false: {
+ file {"${name}":
+ ensure => $ensure,
+ mode => 600,
+ owner => root,
+ group => root,
+ seltype => $postfix::postfix_seltype,
+ require => Package["postfix"],
+ }
+ }
+ default: {
+ file {"${name}":
+ ensure => $ensure,
+ mode => 600,
+ owner => root,
+ group => root,
+ source => $source,
+ seltype => $postfix::postfix_seltype,
+ require => Package["postfix"],
+ }
+ }
+ }
+
+ file {"${name}.db":
+ ensure => $ensure,
+ mode => 600,
+ require => [File["${name}"], Exec["generate ${name}.db"]],
+ seltype => $postfix::postfix_seltype,
+ }
+
+ exec {"generate ${name}.db":
+ command => "postmap ${name}",
+ #creates => "${name}.db", # this prevents postmap from being run !
+ subscribe => File["${name}"],
+ refreshonly => true,
+ require => Package["postfix"],
+ }
+}
diff --git a/puppet/modules/postfix/manifests/header_checks.pp b/puppet/modules/postfix/manifests/header_checks.pp
new file mode 100644
index 00000000..5b0c3c86
--- /dev/null
+++ b/puppet/modules/postfix/manifests/header_checks.pp
@@ -0,0 +1,32 @@
+#
+# == Class: postfix::header_checks
+#
+# Manages Postfix header_checks by merging snippets configured
+# via postfix::header_checks_snippet defines
+#
+# Note that this class is useless when used directly.
+# The postfix::header_checks_snippet defines takes care of importing
+# it anyway.
+#
+class postfix::header_checks {
+
+ concat { '/etc/postfix/header_checks':
+ owner => root,
+ group => root,
+ mode => '0600',
+ }
+
+ postfix::config { "header_checks":
+ value => 'regexp:/etc/postfix/header_checks',
+ require => Concat['/etc/postfix/header_checks'],
+ }
+
+ # Cleanup previous implementation's internal files
+ include common::moduledir
+ file { "${common::moduledir::module_dir_path}/postfix/header_checks":
+ ensure => absent,
+ recurse => true,
+ force => true,
+ }
+
+}
diff --git a/puppet/modules/postfix/manifests/header_checks_snippet.pp b/puppet/modules/postfix/manifests/header_checks_snippet.pp
new file mode 100644
index 00000000..05929a33
--- /dev/null
+++ b/puppet/modules/postfix/manifests/header_checks_snippet.pp
@@ -0,0 +1,60 @@
+/*
+== Definition: postfix::header_checks_snippet
+
+Adds a header_checks snippets to /etc/postfix/header_checks.
+See the postfix::header_checks class for details.
+
+Parameters:
+- *source* or *content*: source or content of the header_checks snippet
+- *ensure*: present (default) or absent
+
+Requires:
+- Class["postfix"]
+
+Example usage:
+
+ node "toto.example.com" {
+ class { 'postfix': }
+ postfix::header_checks_snippet {
+ 'wrong_date': content => 'FIXME';
+ 'bla': source => 'puppet:///files/etc/postfix/header_checks.d/bla';
+ }
+ }
+
+*/
+
+define postfix::header_checks_snippet (
+ $ensure = "present",
+ $source = '',
+ $content = undef
+) {
+
+ if $source == '' and $content == undef {
+ fail("One of \$source or \$content must be specified for postfix::header_checks_snippet ${name}")
+ }
+
+ if $source != '' and $content != undef {
+ fail("Only one of \$source or \$content must specified for postfix::header_checks_snippet ${name}")
+ }
+
+ include postfix::header_checks
+
+ $fragment = "postfix_header_checks_${name}"
+
+ concat::fragment { "$fragment":
+ ensure => "$ensure",
+ target => '/etc/postfix/header_checks',
+ }
+
+ if $source {
+ Concat::Fragment["$fragment"] {
+ source => $source,
+ }
+ }
+ else {
+ Concat::Fragment["$fragment"] {
+ content => $content,
+ }
+ }
+
+}
diff --git a/puppet/modules/postfix/manifests/init.pp b/puppet/modules/postfix/manifests/init.pp
new file mode 100644
index 00000000..45c8e0c9
--- /dev/null
+++ b/puppet/modules/postfix/manifests/init.pp
@@ -0,0 +1,221 @@
+#
+# == Class: postfix
+#
+# This class provides a basic setup of postfix with local and remote
+# delivery and an SMTP server listening on the loopback interface.
+#
+# Parameters:
+# - *$smtp_listen*: address on which the smtp service will listen to. defaults to 127.0.0.1
+# - *$root_mail_recipient*: who will recieve root's emails. defaults to "nobody"
+# - *$anon_sasl*: set $anon_sasl="yes" to hide the originating IP in email
+# - *$manage_header_checks*: manage header checks
+# - *$manage_tls_policy*: manage tls policy
+# - *$manage_transport_regexp*: manage transport regexps
+# - *$manage_virtual_regexp*: manage virtual regexps
+# - *$tls_fingerprint_digest*: fingerprint digest for tls policy class
+# - *$use_amavisd*: set to "yes" to configure amavis
+# - *$use_dovecot_lda*: include dovecot declaration at master.cf
+# - *$use_schleuder*: whether to include schleuder portion at master.cf
+# - *$use_sympa*: whether to include sympa portion at master.cf
+# - *$use_firma*: whether to include firma portion at master.cf
+# - *$use_mlmmj*: whether to include mlmmj portion at master.cf
+# - *$use_submission*: set to "yes" to enable submission section at master.cf
+# - *$use_smtps*: set to "yes" to enable smtps section at master.cf
+# - *$mastercf_tail*: set this for additional content to be added at the end of master.cf
+# - *$inet_interfaces*: which inet interface postfix should listen on
+# - *$myorigin*: sets postfix $myorigin configuration
+#
+# Example usage:
+#
+# node "toto.example.com" {
+# class { 'postfix':
+# smtp_listen => "192.168.1.10"
+# }
+# }
+#
+class postfix(
+ $smtp_listen = '127.0.0.1',
+ $root_mail_recipient = 'nobody',
+ $anon_sasl = 'no',
+ $manage_header_checks = 'no',
+ $manage_tls_policy = 'no',
+ $manage_transport_regexp = 'no',
+ $manage_virtual_regexp = 'no',
+ $tls_fingerprint_digest = 'sha1',
+ $use_amavisd = 'no',
+ $use_dovecot_lda = 'no',
+ $use_schleuder = 'no',
+ $use_sympa = 'no',
+ $use_firma = 'no',
+ $use_mlmmj = 'no',
+ $use_postscreen = 'no',
+ $use_submission = 'no',
+ $use_smtps = 'no',
+ $mastercf_tail = '',
+ $inet_interfaces = 'all',
+ $myorigin = $::fqdn,
+ $mailname = $::fqdn,
+ $preseed = false,
+ $default_alias_maps = true
+) {
+
+ case $::operatingsystem {
+
+ 'RedHat', 'CentOS': {
+ $master_cf_template = 'postfix/master.cf.redhat5.erb'
+
+ # selinux labels differ from one distribution to another
+ case $::operatingsystemmajrelease {
+ '4': { $postfix_seltype = 'etc_t' }
+ '5': { $postfix_seltype = 'postfix_etc_t' }
+ default: { $postfix_seltype = undef }
+ }
+
+ postfix::config {
+ 'sendmail_path': value => '/usr/sbin/sendmail.postfix';
+ 'newaliases_path': value => '/usr/bin/newaliases.postfix';
+ 'mailq_path': value => '/usr/bin/mailq.postfix';
+ }
+ }
+
+ 'Debian': {
+ case $::operatingsystemrelease {
+ /^5.*/: {
+ $master_cf_template = 'postfix/master.cf.debian-5.erb'
+ }
+ /^6.*/: {
+ $master_cf_template = 'postfix/master.cf.debian-6.erb'
+ }
+ /^7.*/: {
+ $master_cf_template = 'postfix/master.cf.debian-7.erb'
+ }
+ default: {
+ $master_cf_template = "postfix/master.cf.debian-${::operatingsystemmajrelease}.erb"
+ }
+ }
+ }
+
+ 'Ubuntu': {
+ $master_cf_template = 'postfix/master.cf.debian-sid.erb'
+ }
+
+ default: {
+ $postfix_seltype = undef
+ $master_cf_template = undef
+ }
+ }
+
+
+ # Bootstrap moduledir
+ include common::moduledir
+ common::module_dir{'postfix': }
+
+ # Include optional classes
+ if $anon_sasl == 'yes' {
+ include postfix::anonsasl
+ }
+ # this global variable needs to get parameterized as well
+ if $::header_checks == 'yes' {
+ include postfix::header_checks
+ }
+ if $manage_tls_policy == 'yes' {
+ class { 'postfix::tlspolicy':
+ fingerprint_digest => $tls_fingerprint_digest,
+ }
+ }
+ if $use_amavisd == 'yes' {
+ include postfix::amavis
+ }
+ if $manage_transport_regexp == 'yes' {
+ include postfix::transport_regexp
+ }
+ if $manage_virtual_regexp == 'yes' {
+ include postfix::virtual_regexp
+ }
+
+ package { 'mailx':
+ ensure => installed
+ }
+
+ if ( $preseed ) {
+ apt::preseeded_package { 'postfix':
+ ensure => installed,
+ }
+ } else {
+ package { 'postfix':
+ ensure => installed
+ }
+ }
+
+ if $::operatingsystem == 'debian' {
+ Package[mailx] { name => 'bsd-mailx' }
+ }
+
+ service { 'postfix':
+ ensure => running,
+ require => Package['postfix'],
+ }
+
+ file { '/etc/mailname':
+ ensure => present,
+ content => "${::fqdn}\n",
+ seltype => $postfix_seltype,
+ }
+
+ # Aliases
+ file { '/etc/aliases':
+ ensure => present,
+ content => "# file managed by puppet\n",
+ replace => false,
+ seltype => $postfix_seltype,
+ notify => Exec['newaliases'],
+ }
+
+ # Aliases
+ exec { 'newaliases':
+ command => '/usr/bin/newaliases',
+ refreshonly => true,
+ require => Package['postfix'],
+ subscribe => File['/etc/aliases'],
+ }
+
+ # Config files
+ file { '/etc/postfix/master.cf':
+ ensure => present,
+ owner => 'root',
+ group => 'root',
+ mode => '0644',
+ content => template($master_cf_template),
+ seltype => $postfix_seltype,
+ notify => Service['postfix'],
+ require => Package['postfix'],
+ }
+
+ # Config files
+ file { '/etc/postfix/main.cf':
+ ensure => present,
+ owner => 'root',
+ group => 'root',
+ mode => '0644',
+ source => 'puppet:///modules/postfix/main.cf',
+ replace => false,
+ seltype => $postfix_seltype,
+ notify => Service['postfix'],
+ require => Package['postfix'],
+ }
+
+ # Default configuration parameters
+ if $default_alias_maps {
+ postfix::config {
+ 'alias_maps': value => 'hash:/etc/aliases';
+ }
+ }
+ postfix::config {
+ 'myorigin': value => $myorigin;
+ 'inet_interfaces': value => $inet_interfaces;
+ }
+
+ postfix::mailalias {'root':
+ recipient => $root_mail_recipient,
+ }
+}
diff --git a/puppet/modules/postfix/manifests/mailalias.pp b/puppet/modules/postfix/manifests/mailalias.pp
new file mode 100644
index 00000000..2f239ac3
--- /dev/null
+++ b/puppet/modules/postfix/manifests/mailalias.pp
@@ -0,0 +1,32 @@
+/*
+== Definition: postfix::mailalias
+
+Wrapper around Puppet mailalias resource, provides newaliases executable.
+
+Parameters:
+- *name*: the name of the alias.
+- *ensure*: present/absent, defaults to present.
+- *recipient*: recipient of the alias.
+
+Requires:
+- Class["postfix"]
+
+Example usage:
+
+ node "toto.example.com" {
+
+ class { 'postfix': }
+
+ postfix::mailalias { "postmaster":
+ ensure => present,
+ recipient => 'foo'
+ }
+
+*/
+define postfix::mailalias ($ensure = 'present', $recipient) {
+ mailalias { "${name}":
+ ensure => $ensure,
+ recipient => $recipient,
+ notify => Exec['newaliases']
+ }
+}
diff --git a/puppet/modules/postfix/manifests/mailman.pp b/puppet/modules/postfix/manifests/mailman.pp
new file mode 100644
index 00000000..8c6ee32c
--- /dev/null
+++ b/puppet/modules/postfix/manifests/mailman.pp
@@ -0,0 +1,34 @@
+#
+# == Class: postfix::mailman
+#
+# Configures a basic smtp server, able to work for the mailman mailing-list
+# manager.
+#
+# Example usage:
+#
+# node "toto.example.com" {
+# include mailman
+# class { 'postfix::mailman': }
+# }
+#
+class postfix::mailman {
+ class { 'postfix':
+ smtp_listen => "0.0.0.0",
+ }
+
+ postfix::config {
+ "mydestination": value => "";
+ "virtual_alias_maps": value => "hash:/etc/postfix/virtual";
+ "transport_maps": value => "hash:/etc/postfix/transport";
+ "mailman_destination_recipient_limit": value => "1", nonstandard => true;
+ }
+
+ postfix::hash { "/etc/postfix/virtual":
+ ensure => present,
+ }
+
+ postfix::hash { "/etc/postfix/transport":
+ ensure => present,
+ }
+
+}
diff --git a/puppet/modules/postfix/manifests/mta.pp b/puppet/modules/postfix/manifests/mta.pp
new file mode 100644
index 00000000..f7a865db
--- /dev/null
+++ b/puppet/modules/postfix/manifests/mta.pp
@@ -0,0 +1,70 @@
+#
+# == Class: postfix::mta
+#
+# This class configures a minimal MTA, listening on
+# $postfix_smtp_listen (default to localhost) and delivering mail to
+# $postfix_mydestination (default to $fqdn).
+#
+# A valid relay host is required ($postfix_relayhost) for outbound email.
+#
+# transport & virtual maps get configured and can be populated with
+# postfix::transport and postfix::virtual
+#
+# Parameters:
+# - *$postfix_relayhost*
+# - *$postfix_mydestination*
+# - every global variable which works for class "postfix" will work here.
+#
+# Requires:
+# - Class["postfix"]
+#
+# Example usage:
+#
+# node "toto.example.com" {
+#
+# class { 'postfix':
+# smtp_listen => "0.0.0.0",
+# }
+#
+# class { 'postfix::mta':
+# relayhost => "mail.example.com",
+# mydestination => "\$myorigin, myapp.example.com",
+# }
+#
+# postfix::transport { "myapp.example.com":
+# ensure => present,
+# destination => "local:",
+# }
+# }
+#
+class postfix::mta(
+ $mydestination = '',
+ $relayhost = ''
+) {
+
+ #case $relayhost {
+ # "": { fail("Required relayhost parameter is not defined.") }
+ #}
+
+ case $mydestination {
+ "": { $postfix_mydestination = "\$myorigin" }
+ default: { $postfix_mydestination = "$mydestination" }
+ }
+
+ postfix::config {
+ "mydestination": value => $postfix_mydestination;
+ "mynetworks": value => "127.0.0.0/8";
+ "relayhost": value => $relayhost;
+ "virtual_alias_maps": value => "hash:/etc/postfix/virtual";
+ "transport_maps": value => "hash:/etc/postfix/transport";
+ }
+
+ postfix::hash { "/etc/postfix/virtual":
+ ensure => present,
+ }
+
+ postfix::hash { "/etc/postfix/transport":
+ ensure => present,
+ }
+
+}
diff --git a/puppet/modules/postfix/manifests/satellite.pp b/puppet/modules/postfix/manifests/satellite.pp
new file mode 100644
index 00000000..c6c1a0e4
--- /dev/null
+++ b/puppet/modules/postfix/manifests/satellite.pp
@@ -0,0 +1,49 @@
+#
+# == Class: postfix::satellite
+#
+# This class configures all local email (cron, mdadm, etc) to be forwarded
+# to $root_mail_recipient, using $postfix_relayhost as a relay.
+#
+# $valid_fqdn can be set to override $fqdn in the case where the FQDN is
+# not recognized as valid by the destination server.
+#
+# Parameters:
+# - *valid_fqdn*
+# - every global variable which works for class "postfix" will work here.
+#
+# Example usage:
+#
+# node "toto.local.lan" {
+# class { 'postfix::satellite':
+# relayhost => "mail.example.com"
+# valid_fqdn => "toto.example.com"
+# root_mail_recipient => "the.sysadmin@example.com"
+# }
+# }
+#
+class postfix::satellite(
+ $relayhost = '',
+ $valid_fqdn = $::fqdn,
+ $root_mail_recipient = ''
+) {
+
+ # If $valid_fqdn is provided, use it to override $fqdn
+ if $valid_fqdn != $::fdqn {
+ $fqdn = $valid_fqdn
+ }
+
+ class { 'postfix':
+ root_mail_recipient => $root_mail_recipient,
+ myorigin => $valid_fqdn,
+ mailname => $valid_fqdn
+ }
+
+ class { 'postfix::mta':
+ relayhost => $relayhost,
+ }
+
+ postfix::virtual {"@${valid_fqdn}":
+ ensure => present,
+ destination => "root",
+ }
+}
diff --git a/puppet/modules/postfix/manifests/smtp_auth.pp b/puppet/modules/postfix/manifests/smtp_auth.pp
new file mode 100644
index 00000000..b553fb5b
--- /dev/null
+++ b/puppet/modules/postfix/manifests/smtp_auth.pp
@@ -0,0 +1,37 @@
+# == Definition: postfix::smtp_auth
+#
+# Manages content of the /etc/postfix/smtp_auth map.
+#
+# Requires:
+# - Class["postfix"]
+# - Postfix::Hash["/etc/postfix/smtp_auth"]
+# - file_line (from puppetlab's stdlib module)
+#
+# Example usage:
+#
+# node 'toto.example.com' {
+#
+# include postfix
+#
+# postfix::hash { '/etc/postfix/smtp_auth':
+# ensure => present,
+# }
+# postfix::config { 'smtp_auth_maps':
+# value => 'hash:/etc/postfix/smtp_auth'
+# }
+# postfix::smtp_auth { 'gmail.com':
+# ensure => present,
+# user => 'USER',
+# password => 'PW',
+# }
+# }
+
+define postfix::smtp_auth ($user, $password, $ensure=present) {
+ file_line { $name:
+ ensure => $ensure,
+ path => '/etc/postfix/smtp_auth',
+ line => "${name} ${user}:${password}",
+ notify => Exec['generate /etc/postfix/smtp_auth.db'],
+ require => Package['postfix'],
+ }
+}
diff --git a/puppet/modules/postfix/manifests/tlspolicy.pp b/puppet/modules/postfix/manifests/tlspolicy.pp
new file mode 100644
index 00000000..d9017108
--- /dev/null
+++ b/puppet/modules/postfix/manifests/tlspolicy.pp
@@ -0,0 +1,55 @@
+#
+# == Class: postfix::tlspolicy
+#
+# Manages Postfix TLS policy by merging policy snippets configured
+# via postfix::tlspolicy_snippet defines
+#
+# Parameters:
+# - $fingerprint_digest (defaults to sha1)
+#
+# Note that this class is useless when used directly.
+# The postfix::tlspolicy_snippet defines takes care of importing
+# it anyway.
+#
+class postfix::tlspolicy(
+ $fingerprint_digest = 'sha1'
+) {
+
+ include common::moduledir
+ common::module_dir{'postfix/tls_policy': }
+
+ $postfix_tlspolicy_dir = "${common::moduledir::module_dir_path}/postfix/tls_policy"
+ $postfix_merged_tlspolicy = "${postfix_tlspolicy_dir}/merged_tls_policy"
+
+ concat { "$postfix_merged_tlspolicy":
+ require => File[$postfix_tlspolicy_dir],
+ owner => root,
+ group => root,
+ mode => '0600',
+ }
+
+ postfix::hash { '/etc/postfix/tls_policy':
+ source => "$postfix_merged_tlspolicy",
+ subscribe => File["$postfix_merged_tlspolicy"],
+ }
+
+ postfix::config {
+ 'smtp_tls_fingerprint_digest': value => "$fingerprint_digest";
+ }
+
+ postfix::config { 'smtp_tls_policy_maps':
+ value => 'hash:/etc/postfix/tls_policy',
+ require => [
+ Postfix::Hash['/etc/postfix/tls_policy'],
+ Postfix::Config['smtp_tls_fingerprint_digest'],
+ ],
+ }
+
+ # Cleanup previous implementation's internal files
+ file { "${postfix_tlspolicy_dir}/tls_policy.d":
+ ensure => absent,
+ recurse => true,
+ force => true,
+ }
+
+}
diff --git a/puppet/modules/postfix/manifests/tlspolicy_snippet.pp b/puppet/modules/postfix/manifests/tlspolicy_snippet.pp
new file mode 100644
index 00000000..b63f812c
--- /dev/null
+++ b/puppet/modules/postfix/manifests/tlspolicy_snippet.pp
@@ -0,0 +1,45 @@
+/*
+== Definition: postfix::tlspolicy_snippet
+
+Adds a TLS policy snippets to /etc/postfix/tls_policy.
+See the postfix::tlspolicy class for details.
+
+Parameters:
+- *name*: name of destination domain Postfix will lookup. See TLS_README.
+- *value*: right-hand part of the tls_policy map
+- *ensure*: present/absent, defaults to present.
+
+Requires:
+- Class["postfix"]
+- Class["postfix::tlspolicy"]
+
+Example usage:
+
+ node "toto.example.com" {
+ class { 'postfix':
+ manage_tls_policy => 'yes',
+ }
+ postfix::tlspolicy_snippet {
+ 'example.com': value => 'encrypt';
+ '.example.com': value => 'encrypt';
+ 'nothing.com': value => 'fingerprint match=2A:FF:F0:EC:52:04:99:45:73:1B:C2:22:7F:FD:31:6B:8F:07:43:29';
+ }
+ }
+
+*/
+
+define postfix::tlspolicy_snippet ($ensure="present", $value = false) {
+
+ if ($value == false) and ($ensure == "present") {
+ fail("The value parameter must be set when using the postfix::tlspolicy_snippet define with ensure=present.")
+ }
+
+ include postfix::tlspolicy
+
+ concat::fragment { "postfix_tlspolicy_${name}":
+ ensure => "$ensure",
+ content => "${name} ${value}\n",
+ target => "$postfix::tlspolicy::postfix_merged_tlspolicy",
+ }
+
+}
diff --git a/puppet/modules/postfix/manifests/transport.pp b/puppet/modules/postfix/manifests/transport.pp
new file mode 100644
index 00000000..08b93e5e
--- /dev/null
+++ b/puppet/modules/postfix/manifests/transport.pp
@@ -0,0 +1,44 @@
+/*
+== Definition: postfix::transport
+
+Manages content of the /etc/postfix/transport map.
+
+Parameters:
+- *name*: name of address postfix will lookup. See transport(5).
+- *destination*: where the emails will be delivered to. See transport(5).
+- *ensure*: present/absent, defaults to present.
+
+Requires:
+- Class["postfix"]
+- Postfix::Hash["/etc/postfix/transport"]
+- Postfix::Config["transport_maps"]
+- file_line (from module stdlib)
+
+Example usage:
+
+ node "toto.example.com" {
+
+ class { 'postfix': }
+
+ postfix::hash { "/etc/postfix/transport":
+ ensure => present,
+ }
+ postfix::config { "transport_maps":
+ value => "hash:/etc/postfix/transport"
+ }
+ postfix::transport { "mailman.example.com":
+ ensure => present,
+ destination => "mailman",
+ }
+ }
+
+*/
+define postfix::transport ($ensure="present", $destination) {
+ file_line {"${name} ${destination}":
+ ensure => $ensure,
+ path => "/etc/postfix/transport",
+ line => "${name} ${destination}",
+ notify => Exec["generate /etc/postfix/transport.db"],
+ require => Package["postfix"],
+ }
+}
diff --git a/puppet/modules/postfix/manifests/transport_regexp.pp b/puppet/modules/postfix/manifests/transport_regexp.pp
new file mode 100644
index 00000000..4961141e
--- /dev/null
+++ b/puppet/modules/postfix/manifests/transport_regexp.pp
@@ -0,0 +1,56 @@
+#
+# == Class: postfix::transport_regexp
+#
+# Manages Postfix transport_regexp by merging snippets shipped:
+# - in the module's files/transport_regexp.d/ or puppet:///files/etc/postfix/transport_regexp.d
+# (the latter takes precedence if present); site_postfix module is supported
+# as well, see the source argument of file {"$postfix_transport_regexp_snippets_dir"
+# bellow for details.
+# - via postfix::transport_regexp_snippet defines
+#
+# Example usage:
+#
+# node "toto.example.com" {
+# class { 'postfix':
+# manage_transport_regexp => 'yes',
+# }
+# postfix::config { "transport_maps":
+# value => "hash:/etc/postfix/transport, regexp:/etc/postfix/transport_regexp",
+# }
+# }
+#
+class postfix::transport_regexp {
+
+ include common::moduledir
+ common::module_dir{'postfix/transport_regexp': }
+
+ $postfix_transport_regexp_dir = "${common::moduledir::module_dir_path}/postfix/transport_regexp"
+ $postfix_transport_regexp_snippets_dir = "${postfix_transport_regexp_dir}/transport_regexp.d"
+ $postfix_merged_transport_regexp = "${postfix_transport_regexp_dir}/merged_transport_regexp"
+
+ file {"$postfix_transport_regexp_snippets_dir":
+ ensure => 'directory',
+ owner => 'root',
+ group => '0',
+ mode => '700',
+ source => [
+ "puppet:///modules/site_postfix/${fqdn}/transport_regexp.d",
+ "puppet:///modules/site_postfix/transport_regexp.d",
+ "puppet:///files/etc/postfix/transport_regexp.d",
+ "puppet:///modules/postfix/transport_regexp.d",
+ ],
+ recurse => true,
+ purge => false,
+ }
+
+ concatenated_file { "$postfix_merged_transport_regexp":
+ dir => "${postfix_transport_regexp_snippets_dir}",
+ require => File["$postfix_transport_regexp_snippets_dir"],
+ }
+
+ config_file { '/etc/postfix/transport_regexp':
+ source => "$postfix_merged_transport_regexp",
+ subscribe => File["$postfix_merged_transport_regexp"],
+ }
+
+}
diff --git a/puppet/modules/postfix/manifests/transport_regexp_snippet.pp b/puppet/modules/postfix/manifests/transport_regexp_snippet.pp
new file mode 100644
index 00000000..2b13ed14
--- /dev/null
+++ b/puppet/modules/postfix/manifests/transport_regexp_snippet.pp
@@ -0,0 +1,67 @@
+/*
+== Definition: postfix::transport_regexp_snippet
+
+Adds a transport_regexp snippets to /etc/postfix/transport_regexp.
+See the postfix::transport_regexp class for details.
+
+Parameters:
+- *source* or *content*: source or content of the transport_regexp snippet
+- *ensure*: present (default) or absent
+
+Requires:
+- Class["postfix"]
+
+Example usage:
+
+ node "toto.example.com" {
+ class { 'postfix': }
+ postfix::transport_regexp {
+ 'wrong_date': content => 'FIXME';
+ 'bla': source => 'puppet:///files/etc/postfix/transport_regexp.d/bla';
+ }
+ }
+
+*/
+
+define postfix::transport_regexp_snippet (
+ $ensure = "present",
+ $source = '',
+ $content = undef
+) {
+
+ if $source == '' and $content == undef {
+ fail("One of \$source or \$content must be specified for postfix::transport_regexp_snippet ${name}")
+ }
+
+ if $source != '' and $content != undef {
+ fail("Only one of \$source or \$content must specified for postfix::transport_regexp_snippet ${name}")
+ }
+
+ if ($value == false) and ($ensure == "present") {
+ fail("The value parameter must be set when using the postfix::transport_regexp_snippet define with ensure=present.")
+ }
+
+ include postfix::transport_regexp
+
+ $snippetfile = "${postfix::transport_regexp::postfix_transport_regexp_snippets_dir}/${name}"
+
+ file { "$snippetfile":
+ ensure => "$ensure",
+ mode => 600,
+ owner => root,
+ group => 0,
+ notify => Exec["concat_${postfix::transport_regexp::postfix_merged_transport_regexp}"],
+ }
+
+ if $source {
+ File["$snippetfile"] {
+ source => $source,
+ }
+ }
+ else {
+ File["$snippetfile"] {
+ content => $content,
+ }
+ }
+
+}
diff --git a/puppet/modules/postfix/manifests/virtual.pp b/puppet/modules/postfix/manifests/virtual.pp
new file mode 100644
index 00000000..06df32ad
--- /dev/null
+++ b/puppet/modules/postfix/manifests/virtual.pp
@@ -0,0 +1,44 @@
+/*
+== Definition: postfix::virtual
+
+Manages content of the /etc/postfix/virtual map.
+
+Parameters:
+- *name*: name of address postfix will lookup. See virtual(8).
+- *destination*: where the emails will be delivered to. See virtual(8).
+- *ensure*: present/absent, defaults to present.
+
+Requires:
+- Class["postfix"]
+- Postfix::Hash["/etc/postfix/virtual"]
+- Postfix::Config["virtual_alias_maps"]
+- file_line (from module stdlib)
+
+Example usage:
+
+ node "toto.example.com" {
+
+ class { 'postfix': }
+
+ postfix::hash { "/etc/postfix/virtual":
+ ensure => present,
+ }
+ postfix::config { "virtual_alias_maps":
+ value => "hash:/etc/postfix/virtual"
+ }
+ postfix::virtual { "user@example.com":
+ ensure => present,
+ destination => "root",
+ }
+ }
+
+*/
+define postfix::virtual ($ensure="present", $destination) {
+ file_line {"${name} ${destination}":
+ ensure => $ensure,
+ path => "/etc/postfix/virtual",
+ line => "${name} ${destination}",
+ notify => Exec["generate /etc/postfix/virtual.db"],
+ require => Package["postfix"],
+ }
+}
diff --git a/puppet/modules/postfix/manifests/virtual_regexp.pp b/puppet/modules/postfix/manifests/virtual_regexp.pp
new file mode 100644
index 00000000..18bbd8ce
--- /dev/null
+++ b/puppet/modules/postfix/manifests/virtual_regexp.pp
@@ -0,0 +1,56 @@
+#
+# == Class: postfix::virtual_regexp
+#
+# Manages Postfix virtual_regexp by merging snippets shipped:
+# - in the module's files/virtual_regexp.d/ or puppet:///files/etc/postfix/virtual_regexp.d
+# (the latter takes precedence if present); site_postfix module is supported
+# as well, see the source argument of file {"$postfix_virtual_regexp_snippets_dir"
+# bellow for details.
+# - via postfix::virtual_regexp_snippet defines
+#
+# Example usage:
+#
+# node "toto.example.com" {
+# class { 'postfix':
+# manage_virtual_regexp => 'yes',
+# }
+# postfix::config { "virtual_alias_maps":
+# value => 'hash://postfix/virtual, regexp:/etc/postfix/virtual_regexp',
+# }
+# }
+#
+class postfix::virtual_regexp {
+
+ include common::moduledir
+ common::module_dir{'postfix/virtual_regexp': }
+
+ $postfix_virtual_regexp_dir = "${common::moduledir::module_dir_path}/postfix/virtual_regexp"
+ $postfix_virtual_regexp_snippets_dir = "${postfix_virtual_regexp_dir}/virtual_regexp.d"
+ $postfix_merged_virtual_regexp = "${postfix_virtual_regexp_dir}/merged_virtual_regexp"
+
+ file {"$postfix_virtual_regexp_snippets_dir":
+ ensure => 'directory',
+ owner => 'root',
+ group => '0',
+ mode => '700',
+ source => [
+ "puppet:///modules/site_postfix/${fqdn}/virtual_regexp.d",
+ "puppet:///modules/site_postfix/virtual_regexp.d",
+ "puppet:///files/etc/postfix/virtual_regexp.d",
+ "puppet:///modules/postfix/virtual_regexp.d",
+ ],
+ recurse => true,
+ purge => false,
+ }
+
+ concatenated_file { "$postfix_merged_virtual_regexp":
+ dir => "${postfix_virtual_regexp_snippets_dir}",
+ require => File["$postfix_virtual_regexp_snippets_dir"],
+ }
+
+ config_file { '/etc/postfix/virtual_regexp':
+ source => "$postfix_merged_virtual_regexp",
+ subscribe => File["$postfix_merged_virtual_regexp"],
+ }
+
+}
diff --git a/puppet/modules/postfix/manifests/virtual_regexp_snippet.pp b/puppet/modules/postfix/manifests/virtual_regexp_snippet.pp
new file mode 100644
index 00000000..bd9a982d
--- /dev/null
+++ b/puppet/modules/postfix/manifests/virtual_regexp_snippet.pp
@@ -0,0 +1,67 @@
+/*
+== Definition: postfix::virtual_regexp_snippet
+
+Adds a virtual_regexp snippets to /etc/postfix/virtual_regexp.
+See the postfix::virtual_regexp class for details.
+
+Parameters:
+- *source* or *content*: source or content of the virtual_regexp snippet
+- *ensure*: present (default) or absent
+
+Requires:
+- Class["postfix"]
+
+Example usage:
+
+ node "toto.example.com" {
+ class { 'postfix': }
+ postfix::virtual_regexp {
+ 'wrong_date': content => 'FIXME';
+ 'bla': source => 'puppet:///files/etc/postfix/virtual_regexp.d/bla';
+ }
+ }
+
+*/
+
+define postfix::virtual_regexp_snippet (
+ $ensure = "present",
+ $source = '',
+ $content = undef
+) {
+
+ if $source == '' and $content == undef {
+ fail("One of \$source or \$content must be specified for postfix::virtual_regexp_snippet ${name}")
+ }
+
+ if $source != '' and $content != undef {
+ fail("Only one of \$source or \$content must specified for postfix::virtual_regexp_snippet ${name}")
+ }
+
+ if ($value == false) and ($ensure == "present") {
+ fail("The value parameter must be set when using the postfix::virtual_regexp_snippet define with ensure=present.")
+ }
+
+ include postfix::virtual_regexp
+
+ $snippetfile = "${postfix::virtual_regexp::postfix_virtual_regexp_snippets_dir}/${name}"
+
+ file { "$snippetfile":
+ ensure => "$ensure",
+ mode => 600,
+ owner => root,
+ group => 0,
+ notify => Exec["concat_${postfix::virtual_regexp::postfix_merged_virtual_regexp}"],
+ }
+
+ if $source {
+ File["$snippetfile"] {
+ source => $source,
+ }
+ }
+ else {
+ File["$snippetfile"] {
+ content => $content,
+ }
+ }
+
+}
diff --git a/puppet/modules/postfix/templates/anonsasl_header_checks.erb b/puppet/modules/postfix/templates/anonsasl_header_checks.erb
new file mode 100644
index 00000000..bca59146
--- /dev/null
+++ b/puppet/modules/postfix/templates/anonsasl_header_checks.erb
@@ -0,0 +1,2 @@
+/^Received: from (.* \([-._[:alnum:]]+ \[[.[:digit:]]{7,15}\]\)).*?([[:space:]]+).*\(Authenticated sender: ([^)]+)\).*by (<%= fqdn.gsub(/\./, '\.') %>) \(([^)]+)\) with (E?SMTPS?A?) id ([A-F[:digit:]]+).*/
+ REPLACE Received: from [127.0.0.1] (localhost [127.0.0.1])$2(Authenticated sender: $3)${2}with $6 id $7
diff --git a/puppet/modules/postfix/templates/master.cf.debian-5.erb b/puppet/modules/postfix/templates/master.cf.debian-5.erb
new file mode 100644
index 00000000..50241b8b
--- /dev/null
+++ b/puppet/modules/postfix/templates/master.cf.debian-5.erb
@@ -0,0 +1,126 @@
+# file managed by puppet
+#
+# Postfix master process configuration file. For details on the format
+# of the file, see the master(5) manual page (command: "man 5 master").
+#
+# Do not forget to execute "postfix reload" after editing this file.
+#
+# ==========================================================================
+# service type private unpriv chroot wakeup maxproc command + args
+# (yes) (yes) (yes) (never) (100)
+# ==========================================================================
+<% if smtp_listen == 'all' %>smtp inet n - - - - smtpd
+<% else %><%= smtp_listen %>:smtp inet n - - - - smtpd<% end %>
+<% if use_submission == 'yes' %>submission inet n - - - - smtpd
+ -o smtpd_tls_security_level=encrypt
+ -o smtpd_sasl_auth_enable=yes
+ -o smtpd_client_restrictions=permit_sasl_authenticated,reject
+ -o milter_macro_daemon_name=ORIGINATING
+<% end %>
+<% if use_smtps == 'yes' %>smtps inet n - - - - smtpd
+ -o smtpd_tls_wrappermode=yes
+ -o smtpd_sasl_auth_enable=yes
+ -o smtpd_client_restrictions=permit_sasl_authenticated,reject
+ -o milter_macro_daemon_name=ORIGINATING
+<% end %>
+#628 inet n - - - - qmqpd
+pickup fifo n - - 60 1 pickup
+cleanup unix n - - - 0 cleanup
+qmgr fifo n - n 300 1 qmgr
+#qmgr fifo n - - 300 1 oqmgr
+tlsmgr unix - - - 1000? 1 tlsmgr
+rewrite unix - - - - - trivial-rewrite
+bounce unix - - - - 0 bounce
+defer unix - - - - 0 bounce
+trace unix - - - - 0 bounce
+verify unix - - - - 1 verify
+flush unix n - - 1000? 0 flush
+proxymap unix - - n - - proxymap
+proxywrite unix - - n - 1 proxymap
+smtp unix - - - - - smtp
+# When relaying mail as backup MX, disable fallback_relay to avoid MX loops
+relay unix - - - - - smtp
+ -o smtp_fallback_relay=
+# -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
+showq unix n - - - - showq
+error unix - - - - - error
+retry unix - - - - - error
+discard unix - - - - - discard
+local unix - n n - - local
+virtual unix - n n - - virtual
+lmtp unix - - - - - lmtp
+anvil unix - - - - 1 anvil
+scache unix - - - - 1 scache
+#
+# ====================================================================
+# Interfaces to non-Postfix software. Be sure to examine the manual
+# pages of the non-Postfix software to find out what options it wants.
+#
+# Many of the following services use the Postfix pipe(8) delivery
+# agent. See the pipe(8) man page for information about ${recipient}
+# and other message envelope options.
+# ====================================================================
+#
+# maildrop. See the Postfix MAILDROP_README file for details.
+# Also specify in main.cf: maildrop_destination_recipient_limit=1
+#
+maildrop unix - n n - - pipe
+ flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient}
+#
+# See the Postfix UUCP_README file for configuration details.
+#
+uucp unix - n n - - pipe
+ flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
+#
+# Other external delivery methods.
+#
+ifmail unix - n n - - pipe
+ flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
+bsmtp unix - n n - - pipe
+ flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient
+scalemail-backend unix - n n - 2 pipe
+ flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension}
+mailman unix - n n - - pipe
+ flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py
+ ${nexthop} ${user}
+<% if use_amavisd == 'yes' %>
+amavis unix - - - - 2 smtp
+ -o smtp_data_done_timeout=1200
+ -o smtp_send_xforward_command=yes
+
+127.0.0.1:10025 inet n - - - - smtpd
+ -o content_filter=
+ -o local_recipient_maps=
+ -o relay_recipient_maps=
+ -o smtpd_restriction_classes=
+ -o smtpd_client_restrictions=
+ -o smtpd_helo_restrictions=
+ -o smtpd_sender_restrictions=
+ -o smtpd_recipient_restrictions=permit_mynetworks,reject
+ -o mynetworks=127.0.0.0/8
+ -o strict_rfc821_envelopes=yes
+ -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks
+ -o smtpd_bind_address=127.0.0.1
+<% end %>
+<% if use_dovecot_lda == 'yes' %>
+dovecot unix - n n - - pipe
+ flags=DRhu user=vmail:vmail argv=/usr/lib/dovecot/deliver -f ${sender} -d ${user}@${nexthop} -n -m ${extension}
+<% end %>
+<% if use_schleuder == 'yes' %>
+schleuder unix - n n - - pipe
+ flags=DRhu user=schleuder argv=/usr/bin/schleuder ${user}
+<% end %>
+<% if use_sympa == 'yes' %>
+sympa unix - n n - - pipe
+ flags=R user=sympa argv=/usr/lib/sympa/bin/queue ${recipient}
+sympabounce unix - n n - - pipe
+ flags=R user=sympa argv=/usr/lib/sympa/bin/bouncequeue ${user}
+<% end %>
+<% if use_mlmmj == 'yes' %>
+mlmmj unix - n n - - pipe
+ flags=DORhu user=mlmmj argv=/usr/bin/mlmmj-recieve -F -L /var/spool/mlmmj/$nexthop/
+<%- end -%>
+
+<%- unless mastercf_tail.to_s.empty? then -%>
+<%= mastercf_tail %>
+<%- end -%>
diff --git a/puppet/modules/postfix/templates/master.cf.debian-6.erb b/puppet/modules/postfix/templates/master.cf.debian-6.erb
new file mode 100644
index 00000000..9ce32647
--- /dev/null
+++ b/puppet/modules/postfix/templates/master.cf.debian-6.erb
@@ -0,0 +1,158 @@
+#
+# Postfix master process configuration file. For details on the format
+# of the file, see the master(5) manual page (command: "man 5 master").
+#
+# Do not forget to execute "postfix reload" after editing this file.
+#
+# ==========================================================================
+# service type private unpriv chroot wakeup maxproc command + args
+# (yes) (yes) (yes) (never) (100)
+# ==========================================================================
+<% if smtp_listen == 'all' %>smtp inet n - - - - smtpd
+<% else %><%= smtp_listen %>:smtp inet n - - - - smtpd<% end %>
+<% if use_submission == 'yes' %>submission inet n - - - - smtpd
+ -o smtpd_enforce_tls=yes
+ -o smtpd_sasl_auth_enable=yes
+ -o smtpd_client_restrictions=permit_sasl_authenticated,reject
+<% end %>
+#smtp inet n - - - 1 postscreen
+#smtpd pass - - - - - smtpd
+#dnsblog unix - - - - 0 dnsblog
+# -o smtpd_tls_security_level=encrypt
+# -o smtpd_sasl_auth_enable=yes
+# -o smtpd_client_restrictions=permit_sasl_authenticated,reject
+# -o milter_macro_daemon_name=ORIGINATING
+<% if use_smtps == 'yes' %>smtps inet n - - - - smtpd
+ -o smtpd_tls_wrappermode=yes
+ -o smtpd_sasl_auth_enable=yes
+ -o smtpd_client_restrictions=permit_sasl_authenticated,reject
+ -o milter_macro_daemon_name=ORIGINATING
+<% end %>
+#628 inet n - - - - qmqpd
+pickup fifo n - - 60 1 pickup
+cleanup unix n - - - 0 cleanup
+qmgr fifo n - n 300 1 qmgr
+#qmgr fifo n - - 300 1 oqmgr
+tlsmgr unix - - - 1000? 1 tlsmgr
+rewrite unix - - - - - trivial-rewrite
+bounce unix - - - - 0 bounce
+defer unix - - - - 0 bounce
+trace unix - - - - 0 bounce
+verify unix - - - - 1 verify
+flush unix n - - 1000? 0 flush
+proxymap unix - - n - - proxymap
+proxywrite unix - - n - 1 proxymap
+smtp unix - - - - - smtp
+# When relaying mail as backup MX, disable fallback_relay to avoid MX loops
+relay unix - - - - - smtp
+ -o smtp_fallback_relay=
+# -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
+showq unix n - - - - showq
+error unix - - - - - error
+retry unix - - - - - error
+discard unix - - - - - discard
+local unix - n n - - local
+virtual unix - n n - - virtual
+lmtp unix - - - - - lmtp
+anvil unix - - - - 1 anvil
+scache unix - - - - 1 scache
+#
+# ====================================================================
+# Interfaces to non-Postfix software. Be sure to examine the manual
+# pages of the non-Postfix software to find out what options it wants.
+#
+# Many of the following services use the Postfix pipe(8) delivery
+# agent. See the pipe(8) man page for information about ${recipient}
+# and other message envelope options.
+# ====================================================================
+#
+# maildrop. See the Postfix MAILDROP_README file for details.
+# Also specify in main.cf: maildrop_destination_recipient_limit=1
+#
+maildrop unix - n n - - pipe
+ flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient}
+#
+# ====================================================================
+#
+# Recent Cyrus versions can use the existing "lmtp" master.cf entry.
+#
+# Specify in cyrus.conf:
+# lmtp cmd="lmtpd -a" listen="localhost:lmtp" proto=tcp4
+#
+# Specify in main.cf one or more of the following:
+# mailbox_transport = lmtp:inet:localhost
+# virtual_transport = lmtp:inet:localhost
+#
+# ====================================================================
+#
+# Cyrus 2.1.5 (Amos Gouaux)
+# Also specify in main.cf: cyrus_destination_recipient_limit=1
+#
+#cyrus unix - n n - - pipe
+# user=cyrus argv=/cyrus/bin/deliver -e -r ${sender} -m ${extension} ${user}
+#
+# ====================================================================
+#
+# See the Postfix UUCP_README file for configuration details.
+#
+uucp unix - n n - - pipe
+ flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
+#
+# Other external delivery methods.
+#
+ifmail unix - n n - - pipe
+ flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
+bsmtp unix - n n - - pipe
+ flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient
+scalemail-backend unix - n n - 2 pipe
+ flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension}
+mailman unix - n n - - pipe
+ flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py
+ ${nexthop} ${user}
+<% if use_amavisd == 'yes' %>
+amavis unix - - - - 2 smtp
+ -o smtp_data_done_timeout=1200
+ -o smtp_send_xforward_command=yes
+
+127.0.0.1:10025 inet n - - - - smtpd
+ -o content_filter=
+ -o local_recipient_maps=
+ -o relay_recipient_maps=
+ -o smtpd_restriction_classes=
+ -o smtpd_client_restrictions=
+ -o smtpd_helo_restrictions=
+ -o smtpd_sender_restrictions=
+ -o smtpd_recipient_restrictions=permit_mynetworks,reject
+ -o mynetworks=127.0.0.0/8
+ -o strict_rfc821_envelopes=yes
+ -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks
+ -o smtpd_bind_address=127.0.0.1
+<% end %>
+<% if use_dovecot_lda == 'yes' %>
+dovecot unix - n n - - pipe
+ flags=DRhu user=vmail:vmail argv=/usr/lib/dovecot/deliver -f ${sender} -d ${user}@${nexthop} -n -m ${extension}
+<% end %>
+<% if use_schleuder == 'yes' %>
+schleuder unix - n n - - pipe
+ flags=DRhu user=schleuder argv=/usr/bin/schleuder ${user}
+<% end %>
+<% if use_sympa == 'yes' %>
+sympa unix - n n - - pipe
+ flags=R user=sympa argv=/usr/lib/sympa/bin/queue ${recipient}
+sympabounce unix - n n - - pipe
+ flags=R user=sympa argv=/usr/lib/sympa/bin/bouncequeue ${user}
+<% end %>
+<% if use_mlmmj == 'yes' %>
+mlmmj unix - n n - - pipe
+ flags=DORhu user=mlmmj argv=/usr/bin/mlmmj-recieve -F -L /var/spool/mlmmj/$nexthop/
+<%- end -%>
+<% if use_firma == 'yes' %>
+firma unix - n n - - pipe
+ flags=DRhu user=firma argv=/var/lib/firma/firma -p ${user}
+firmarequest unix - n n - - pipe
+ flags=DRhu user=firma argv=/var/lib/firma/firma -e ${user}
+<% end %>
+
+<%- unless mastercf_tail.to_s.empty? then -%>
+<%= mastercf_tail %>
+<%- end -%>
diff --git a/puppet/modules/postfix/templates/master.cf.debian-7.erb b/puppet/modules/postfix/templates/master.cf.debian-7.erb
new file mode 100644
index 00000000..d243a93e
--- /dev/null
+++ b/puppet/modules/postfix/templates/master.cf.debian-7.erb
@@ -0,0 +1,161 @@
+#
+# Postfix master process configuration file. For details on the format
+# of the file, see the master(5) manual page (command: "man 5 master").
+#
+# Do not forget to execute "postfix reload" after editing this file.
+#
+# ==========================================================================
+# service type private unpriv chroot wakeup maxproc command + args
+# (yes) (yes) (yes) (never) (100)
+# ==========================================================================
+#
+<% if @use_postscreen == 'yes' and @smtp_listen == 'all' %>smtpd pass - - n - - smtpd
+smtp inet n - n - 1 postscreen
+tlsproxy unix - - n - 0 tlsproxy
+<% elsif @use_postscreen == 'no' and @smtp_listen == 'all' %>smtp inet n - - - - smtpd
+<% else %><%= @smtp_listen %>:smtp inet n - - - - smtpd<% end %>
+#smtp inet n - - - 1 postscreen
+#smtpd pass - - - - - smtpd
+#dnsblog unix - - - - 0 dnsblog
+#tlsproxy unix - - - - 0 tlsproxy
+<% if @use_submission == 'yes' %>submission inet n - - - - smtpd
+ -o smtpd_tls_security_level=encrypt
+ -o smtpd_sasl_auth_enable=yes
+ -o smtpd_client_restrictions=permit_sasl_authenticated,reject
+ -o milter_macro_daemon_name=ORIGINATING
+<% end %>
+<% if @use_smtps == 'yes' %>smtps inet n - - - - smtpd
+ -o smtpd_tls_wrappermode=yes
+ -o smtpd_sasl_auth_enable=yes
+ -o smtpd_client_restrictions=permit_sasl_authenticated,reject
+ -o milter_macro_daemon_name=ORIGINATING
+<% end %>
+#628 inet n - - - - qmqpd
+pickup fifo n - - 60 1 pickup
+cleanup unix n - - - 0 cleanup
+qmgr fifo n - n 300 1 qmgr
+#qmgr fifo n - - 300 1 oqmgr
+tlsmgr unix - - - 1000? 1 tlsmgr
+rewrite unix - - - - - trivial-rewrite
+bounce unix - - - - 0 bounce
+defer unix - - - - 0 bounce
+trace unix - - - - 0 bounce
+verify unix - - - - 1 verify
+flush unix n - - 1000? 0 flush
+proxymap unix - - n - - proxymap
+proxywrite unix - - n - 1 proxymap
+smtp unix - - - - - smtp
+# When relaying mail as backup MX, disable fallback_relay to avoid MX loops
+relay unix - - - - - smtp
+ -o smtp_fallback_relay=
+# -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
+showq unix n - - - - showq
+error unix - - - - - error
+retry unix - - - - - error
+discard unix - - - - - discard
+local unix - n n - - local
+virtual unix - n n - - virtual
+lmtp unix - - - - - lmtp
+anvil unix - - - - 1 anvil
+scache unix - - - - 1 scache
+#
+# ====================================================================
+# Interfaces to non-Postfix software. Be sure to examine the manual
+# pages of the non-Postfix software to find out what options it wants.
+#
+# Many of the following services use the Postfix pipe(8) delivery
+# agent. See the pipe(8) man page for information about ${recipient}
+# and other message envelope options.
+# ====================================================================
+#
+# maildrop. See the Postfix MAILDROP_README file for details.
+# Also specify in main.cf: maildrop_destination_recipient_limit=1
+#
+maildrop unix - n n - - pipe
+ flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient}
+#
+# ====================================================================
+#
+# Recent Cyrus versions can use the existing "lmtp" master.cf entry.
+#
+# Specify in cyrus.conf:
+# lmtp cmd="lmtpd -a" listen="localhost:lmtp" proto=tcp4
+#
+# Specify in main.cf one or more of the following:
+# mailbox_transport = lmtp:inet:localhost
+# virtual_transport = lmtp:inet:localhost
+#
+# ====================================================================
+#
+# Cyrus 2.1.5 (Amos Gouaux)
+# Also specify in main.cf: cyrus_destination_recipient_limit=1
+#
+#cyrus unix - n n - - pipe
+# user=cyrus argv=/cyrus/bin/deliver -e -r ${sender} -m ${extension} ${user}
+#
+# ====================================================================
+# Old example of delivery via Cyrus.
+#
+#old-cyrus unix - n n - - pipe
+# flags=R user=cyrus argv=/cyrus/bin/deliver -e -m ${extension} ${user}
+#
+# ====================================================================
+#
+# See the Postfix UUCP_README file for configuration details.
+#
+uucp unix - n n - - pipe
+ flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
+#
+# Other external delivery methods.
+#
+ifmail unix - n n - - pipe
+ flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
+bsmtp unix - n n - - pipe
+ flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient
+scalemail-backend unix - n n - 2 pipe
+ flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension}
+mailman unix - n n - - pipe
+ flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py
+ ${nexthop} ${user}
+
+<% if @use_amavisd == 'yes' %>
+amavis unix - - - - 2 smtp
+ -o smtp_data_done_timeout=1200
+ -o smtp_send_xforward_command=yes
+
+127.0.0.1:10025 inet n - - - - smtpd
+ -o content_filter=
+ -o local_recipient_maps=
+ -o relay_recipient_maps=
+ -o smtpd_restriction_classes=
+ -o smtpd_client_restrictions=
+ -o smtpd_helo_restrictions=
+ -o smtpd_sender_restrictions=
+ -o smtpd_recipient_restrictions=permit_mynetworks,reject
+ -o mynetworks=127.0.0.0/8
+ -o strict_rfc821_envelopes=yes
+ -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks
+ -o smtpd_bind_address=127.0.0.1
+<% end %>
+<% if @use_dovecot_lda == 'yes' %>
+dovecot unix - n n - - pipe
+ flags=DRhu user=vmail:vmail argv=/usr/lib/dovecot/deliver -f ${sender} -d ${user}@${nexthop} -n -m ${extension}
+<% end %>
+<% if @use_schleuder == 'yes' %>
+schleuder unix - n n - - pipe
+ flags=DRhu user=schleuder argv=/usr/bin/schleuder ${user}
+<% end %>
+<% if @use_sympa == 'yes' %>
+sympa unix - n n - - pipe
+ flags=R user=sympa argv=/usr/lib/sympa/bin/queue ${recipient}
+sympabounce unix - n n - - pipe
+ flags=R user=sympa argv=/usr/lib/sympa/bin/bouncequeue ${user}
+<% end %>
+<% if @use_mlmmj == 'yes' %>
+mlmmj unix - n n - - pipe
+ flags=DORhu user=mlmmj argv=/usr/bin/mlmmj-recieve -F -L /var/spool/mlmmj/$nexthop/
+<%- end -%>
+
+<%- unless @mastercf_tail.to_s.empty? then -%>
+<%= @mastercf_tail %>
+<%- end -%>
diff --git a/puppet/modules/postfix/templates/master.cf.debian-8.erb b/puppet/modules/postfix/templates/master.cf.debian-8.erb
new file mode 100644
index 00000000..e613ac1f
--- /dev/null
+++ b/puppet/modules/postfix/templates/master.cf.debian-8.erb
@@ -0,0 +1,160 @@
+#
+# Postfix master process configuration file. For details on the format
+# of the file, see the master(5) manual page (command: "man 5 master").
+#
+# Do not forget to execute "postfix reload" after editing this file.
+#
+# ==========================================================================
+# service type private unpriv chroot wakeup maxproc command + args
+# (yes) (yes) (yes) (never) (100)
+ # ==========================================================================
+<% if @use_postscreen == 'yes' and @smtp_listen == 'all' %>smtpd pass - - n - - smtpd
+smtp inet n - n - 1 postscreen
+tlsproxy unix - - n - 0 tlsproxy
+<% elsif @use_postscreen == 'no' and @smtp_listen == 'all' %>smtp inet n - - - - smtpd
+<% else %><%= @smtp_listen %>:smtp inet n - - - - smtpd<% end %>
+#smtp inet n - - - 1 postscreen
+#smtpd pass - - - - - smtpd
+#dnsblog unix - - - - 0 dnsblog
+#tlsproxy unix - - - - 0 tlsproxy
+<% if @use_submission == 'yes' %>submission inet n - - - - smtpd
+ -o smtpd_tls_security_level=encrypt
+ -o smtpd_sasl_auth_enable=yes
+ -o smtpd_client_restrictions=permit_sasl_authenticated,reject
+ -o milter_macro_daemon_name=ORIGINATING
+<% end %>
+<% if @use_smtps == 'yes' %>smtps inet n - - - - smtpd
+ -o smtpd_tls_wrappermode=yes
+ -o smtpd_sasl_auth_enable=yes
+ -o smtpd_client_restrictions=permit_sasl_authenticated,reject
+ -o milter_macro_daemon_name=ORIGINATING
+<% end %>
+#628 inet n - - - - qmqpd
+pickup fifo n - - 60 1 pickup
+cleanup unix n - - - 0 cleanup
+qmgr fifo n - n 300 1 qmgr
+#qmgr fifo n - - 300 1 oqmgr
+tlsmgr unix - - - 1000? 1 tlsmgr
+rewrite unix - - - - - trivial-rewrite
+bounce unix - - - - 0 bounce
+defer unix - - - - 0 bounce
+trace unix - - - - 0 bounce
+verify unix - - - - 1 verify
+flush unix n - - 1000? 0 flush
+proxymap unix - - n - - proxymap
+proxywrite unix - - n - 1 proxymap
+smtp unix - - - - - smtp
+# When relaying mail as backup MX, disable fallback_relay to avoid MX loops
+relay unix - - - - - smtp
+ -o smtp_fallback_relay=
+# -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
+showq unix n - - - - showq
+error unix - - - - - error
+retry unix - - - - - error
+discard unix - - - - - discard
+local unix - n n - - local
+virtual unix - n n - - virtual
+lmtp unix - - - - - lmtp
+anvil unix - - - - 1 anvil
+scache unix - - - - 1 scache
+#
+# ====================================================================
+# Interfaces to non-Postfix software. Be sure to examine the manual
+# pages of the non-Postfix software to find out what options it wants.
+#
+# Many of the following services use the Postfix pipe(8) delivery
+# agent. See the pipe(8) man page for information about ${recipient}
+# and other message envelope options.
+# ====================================================================
+#
+# maildrop. See the Postfix MAILDROP_README file for details.
+# Also specify in main.cf: maildrop_destination_recipient_limit=1
+#
+maildrop unix - n n - - pipe
+ flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient}
+#
+# ====================================================================
+#
+# Recent Cyrus versions can use the existing "lmtp" master.cf entry.
+#
+# Specify in cyrus.conf:
+# lmtp cmd="lmtpd -a" listen="localhost:lmtp" proto=tcp4
+#
+# Specify in main.cf one or more of the following:
+# mailbox_transport = lmtp:inet:localhost
+# virtual_transport = lmtp:inet:localhost
+#
+# ====================================================================
+#
+# Cyrus 2.1.5 (Amos Gouaux)
+# Also specify in main.cf: cyrus_destination_recipient_limit=1
+#
+#cyrus unix - n n - - pipe
+# user=cyrus argv=/cyrus/bin/deliver -e -r ${sender} -m ${extension} ${user}
+#
+# ====================================================================
+# Old example of delivery via Cyrus.
+#
+#old-cyrus unix - n n - - pipe
+# flags=R user=cyrus argv=/cyrus/bin/deliver -e -m ${extension} ${user}
+#
+# ====================================================================
+#
+# See the Postfix UUCP_README file for configuration details.
+#
+uucp unix - n n - - pipe
+ flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
+#
+# Other external delivery methods.
+#
+ifmail unix - n n - - pipe
+ flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
+bsmtp unix - n n - - pipe
+ flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient
+scalemail-backend unix - n n - 2 pipe
+ flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension}
+mailman unix - n n - - pipe
+ flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py
+ ${nexthop} ${user}
+
+<% if @use_amavisd == 'yes' %>
+amavis unix - - - - 2 smtp
+ -o smtp_data_done_timeout=1200
+ -o smtp_send_xforward_command=yes
+
+127.0.0.1:10025 inet n - - - - smtpd
+ -o content_filter=
+ -o local_recipient_maps=
+ -o relay_recipient_maps=
+ -o smtpd_restriction_classes=
+ -o smtpd_client_restrictions=
+ -o smtpd_helo_restrictions=
+ -o smtpd_sender_restrictions=
+ -o smtpd_recipient_restrictions=permit_mynetworks,reject
+ -o mynetworks=127.0.0.0/8
+ -o strict_rfc821_envelopes=yes
+ -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks
+ -o smtpd_bind_address=127.0.0.1
+<% end %>
+<% if @use_dovecot_lda == 'yes' %>
+dovecot unix - n n - - pipe
+ flags=DRhu user=vmail:vmail argv=/usr/lib/dovecot/deliver -f ${sender} -d ${user}@${nexthop} -n -m ${extension}
+<% end %>
+<% if @use_schleuder == 'yes' %>
+schleuder unix - n n - - pipe
+ flags=DRhu user=schleuder argv=/usr/bin/schleuder ${user}
+<% end %>
+<% if @use_sympa == 'yes' %>
+sympa unix - n n - - pipe
+ flags=R user=sympa argv=/usr/lib/sympa/bin/queue ${recipient}
+sympabounce unix - n n - - pipe
+ flags=R user=sympa argv=/usr/lib/sympa/bin/bouncequeue ${user}
+<% end %>
+<% if @use_mlmmj == 'yes' %>
+mlmmj unix - n n - - pipe
+ flags=DORhu user=mlmmj argv=/usr/bin/mlmmj-recieve -F -L /var/spool/mlmmj/$nexthop/
+<%- end -%>
+
+<%- unless @mastercf_tail.to_s.empty? then -%>
+<%= @mastercf_tail %>
+<%- end -%>
diff --git a/puppet/modules/postfix/templates/master.cf.debian-sid.erb b/puppet/modules/postfix/templates/master.cf.debian-sid.erb
new file mode 100644
index 00000000..7b653fb3
--- /dev/null
+++ b/puppet/modules/postfix/templates/master.cf.debian-sid.erb
@@ -0,0 +1,157 @@
+#
+# Postfix master process configuration file. For details on the format
+# of the file, see the master(5) manual page (command: "man 5 master").
+#
+# Do not forget to execute "postfix reload" after editing this file.
+#
+# ==========================================================================
+# service type private unpriv chroot wakeup maxproc command + args
+# (yes) (yes) (yes) (never) (100)
+# ==========================================================================
+<% if @smtp_listen == 'all' %>smtp inet n - - - - smtpd
+<% else %><%= @smtp_listen %>:smtp inet n - - - - smtpd<% end %>
+#smtp inet n - - - 1 postscreen
+#smtpd pass - - - - - smtpd
+#dnsblog unix - - - - 0 dnsblog
+#tlsproxy unix - - - - 0 tlsproxy
+<% if @use_submission == 'yes' %>submission inet n - - - - smtpd
+ -o smtpd_tls_security_level=encrypt
+ -o smtpd_sasl_auth_enable=yes
+ -o smtpd_client_restrictions=permit_sasl_authenticated,reject
+ -o milter_macro_daemon_name=ORIGINATING
+<% end %>
+<% if @use_smtps == 'yes' %>smtps inet n - - - - smtpd
+ -o smtpd_tls_wrappermode=yes
+ -o smtpd_sasl_auth_enable=yes
+ -o smtpd_client_restrictions=permit_sasl_authenticated,reject
+ -o milter_macro_daemon_name=ORIGINATING
+<% end %>
+#628 inet n - - - - qmqpd
+pickup fifo n - - 60 1 pickup
+cleanup unix n - - - 0 cleanup
+qmgr fifo n - n 300 1 qmgr
+#qmgr fifo n - - 300 1 oqmgr
+tlsmgr unix - - - 1000? 1 tlsmgr
+rewrite unix - - - - - trivial-rewrite
+bounce unix - - - - 0 bounce
+defer unix - - - - 0 bounce
+trace unix - - - - 0 bounce
+verify unix - - - - 1 verify
+flush unix n - - 1000? 0 flush
+proxymap unix - - n - - proxymap
+proxywrite unix - - n - 1 proxymap
+smtp unix - - - - - smtp
+# When relaying mail as backup MX, disable fallback_relay to avoid MX loops
+relay unix - - - - - smtp
+ -o smtp_fallback_relay=
+# -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
+showq unix n - - - - showq
+error unix - - - - - error
+retry unix - - - - - error
+discard unix - - - - - discard
+local unix - n n - - local
+virtual unix - n n - - virtual
+lmtp unix - - - - - lmtp
+anvil unix - - - - 1 anvil
+scache unix - - - - 1 scache
+#
+# ====================================================================
+# Interfaces to non-Postfix software. Be sure to examine the manual
+# pages of the non-Postfix software to find out what options it wants.
+#
+# Many of the following services use the Postfix pipe(8) delivery
+# agent. See the pipe(8) man page for information about ${recipient}
+# and other message envelope options.
+# ====================================================================
+#
+# maildrop. See the Postfix MAILDROP_README file for details.
+# Also specify in main.cf: maildrop_destination_recipient_limit=1
+#
+maildrop unix - n n - - pipe
+ flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient}
+#
+# ====================================================================
+#
+# Recent Cyrus versions can use the existing "lmtp" master.cf entry.
+#
+# Specify in cyrus.conf:
+# lmtp cmd="lmtpd -a" listen="localhost:lmtp" proto=tcp4
+#
+# Specify in main.cf one or more of the following:
+# mailbox_transport = lmtp:inet:localhost
+# virtual_transport = lmtp:inet:localhost
+#
+# ====================================================================
+#
+# Cyrus 2.1.5 (Amos Gouaux)
+# Also specify in main.cf: cyrus_destination_recipient_limit=1
+#
+#cyrus unix - n n - - pipe
+# user=cyrus argv=/cyrus/bin/deliver -e -r ${sender} -m ${extension} ${user}
+#
+# ====================================================================
+# Old example of delivery via Cyrus.
+#
+#old-cyrus unix - n n - - pipe
+# flags=R user=cyrus argv=/cyrus/bin/deliver -e -m ${extension} ${user}
+#
+# ====================================================================
+#
+# See the Postfix UUCP_README file for configuration details.
+#
+uucp unix - n n - - pipe
+ flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
+#
+# Other external delivery methods.
+#
+ifmail unix - n n - - pipe
+ flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
+bsmtp unix - n n - - pipe
+ flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient
+scalemail-backend unix - n n - 2 pipe
+ flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension}
+mailman unix - n n - - pipe
+ flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py
+ ${nexthop} ${user}
+
+<% if @use_amavisd == 'yes' %>
+amavis unix - - - - 2 smtp
+ -o smtp_data_done_timeout=1200
+ -o smtp_send_xforward_command=yes
+
+127.0.0.1:10025 inet n - - - - smtpd
+ -o content_filter=
+ -o local_recipient_maps=
+ -o relay_recipient_maps=
+ -o smtpd_restriction_classes=
+ -o smtpd_client_restrictions=
+ -o smtpd_helo_restrictions=
+ -o smtpd_sender_restrictions=
+ -o smtpd_recipient_restrictions=permit_mynetworks,reject
+ -o mynetworks=127.0.0.0/8
+ -o strict_rfc821_envelopes=yes
+ -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks
+ -o smtpd_bind_address=127.0.0.1
+<% end %>
+<% if @use_dovecot_lda == 'yes' %>
+dovecot unix - n n - - pipe
+ flags=DRhu user=vmail:vmail argv=/usr/lib/dovecot/deliver -f ${sender} -d ${user}@${nexthop} -n -m ${extension}
+<% end %>
+<% if @use_schleuder == 'yes' %>
+schleuder unix - n n - - pipe
+ flags=DRhu user=schleuder argv=/usr/bin/schleuder ${user}
+<% end %>
+<% if @use_sympa == 'yes' %>
+sympa unix - n n - - pipe
+ flags=R user=sympa argv=/usr/lib/sympa/bin/queue ${recipient}
+sympabounce unix - n n - - pipe
+ flags=R user=sympa argv=/usr/lib/sympa/bin/bouncequeue ${user}
+<% end %>
+<% if @use_mlmmj == 'yes' %>
+mlmmj unix - n n - - pipe
+ flags=DORhu user=mlmmj argv=/usr/bin/mlmmj-recieve -F -L /var/spool/mlmmj/$nexthop/
+<%- end -%>
+
+<%- unless @mastercf_tail.to_s.empty? then -%>
+<%= @mastercf_tail %>
+<%- end -%>
diff --git a/puppet/modules/postfix/templates/master.cf.redhat5.erb b/puppet/modules/postfix/templates/master.cf.redhat5.erb
new file mode 100644
index 00000000..01741e4e
--- /dev/null
+++ b/puppet/modules/postfix/templates/master.cf.redhat5.erb
@@ -0,0 +1,87 @@
+# file managed by puppet
+#
+# Postfix master process configuration file. For details on the format
+# of the file, see the master(5) manual page (command: "man 5 master").
+#
+# ==========================================================================
+# service type private unpriv chroot wakeup maxproc command + args
+# (yes) (yes) (yes) (never) (100)
+# ==========================================================================
+<%= smtp_listen %>:smtp inet n - n - - smtpd
+#smtp inet n - n - - smtpd
+<% if use_submission == 'yes' %>submission inet n - n - - smtpd
+ -o smtpd_enforce_tls=yes
+ -o smtpd_sasl_auth_enable=yes
+ -o smtpd_client_restrictions=permit_sasl_authenticated,reject
+<% end %>
+<% if use_smtps == 'yes' %>smtps inet n - n - - smtpd
+ -o smtpd_tls_wrappermode=yes
+ -o smtpd_sasl_auth_enable=yes
+ -o smtpd_client_restrictions=permit_sasl_authenticated,reject
+<% end %>
+#628 inet n - n - - qmqpd
+pickup fifo n - n 60 1 pickup
+cleanup unix n - n - 0 cleanup
+qmgr fifo n - n 300 1 qmgr
+#qmgr fifo n - n 300 1 oqmgr
+tlsmgr unix - - n 1000? 1 tlsmgr
+rewrite unix - - n - - trivial-rewrite
+bounce unix - - n - 0 bounce
+defer unix - - n - 0 bounce
+trace unix - - n - 0 bounce
+verify unix - - n - 1 verify
+flush unix n - n 1000? 0 flush
+proxymap unix - - n - - proxymap
+smtp unix - - n - - smtp
+# When relaying mail as backup MX, disable fallback_relay to avoid MX loops
+relay unix - - n - - smtp
+ -o fallback_relay=
+# -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
+showq unix n - n - - showq
+error unix - - n - - error
+discard unix - - n - - discard
+local unix - n n - - local
+virtual unix - n n - - virtual
+lmtp unix - - n - - lmtp
+anvil unix - - n - 1 anvil
+scache unix - - n - 1 scache
+#
+# ====================================================================
+# Interfaces to non-Postfix software. Be sure to examine the manual
+# pages of the non-Postfix software to find out what options it wants.
+#
+# Many of the following services use the Postfix pipe(8) delivery
+# agent. See the pipe(8) man page for information about ${recipient}
+# and other message envelope options.
+# ====================================================================
+#
+# maildrop. See the Postfix MAILDROP_README file for details.
+# Also specify in main.cf: maildrop_destination_recipient_limit=1
+#
+maildrop unix - n n - - pipe
+ flags=DRhu user=vmail argv=/usr/local/bin/maildrop -d ${recipient}
+#
+# The Cyrus deliver program has changed incompatibly, multiple times.
+#
+old-cyrus unix - n n - - pipe
+ flags=R user=cyrus argv=/usr/lib/cyrus-imapd/deliver -e -m ${extension} ${user}
+# Cyrus 2.1.5 (Amos Gouaux)
+# Also specify in main.cf: cyrus_destination_recipient_limit=1
+cyrus unix - n n - - pipe
+ user=cyrus argv=/usr/lib/cyrus-imapd/deliver -e -r ${sender} -m ${extension} ${user}
+#
+# See the Postfix UUCP_README file for configuration details.
+#
+uucp unix - n n - - pipe
+ flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
+#
+# Other external delivery methods.
+#
+ifmail unix - n n - - pipe
+ flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
+bsmtp unix - n n - - pipe
+ flags=Fq. user=foo argv=/usr/local/sbin/bsmtp -f $sender $nexthop $recipient
+
+<%- unless mastercf_tail.to_s.empty? then -%>
+<%= mastercf_tail %>
+<%- end -%>
diff --git a/puppet/modules/postfwd/files/postfwd_default b/puppet/modules/postfwd/files/postfwd_default
new file mode 100644
index 00000000..83742e40
--- /dev/null
+++ b/puppet/modules/postfwd/files/postfwd_default
@@ -0,0 +1,19 @@
+### This file managed by Puppet
+# Global options for postfwd(8).
+
+# Set to '1' to enable startup (daemon mode)
+STARTUP=1
+
+# Config file
+CONF=/etc/postfix/postfwd.cf
+# IP where listen to
+INET=127.0.0.1
+# Port where listen to
+PORT=10040
+# run as user postfwd
+RUNAS="postfw"
+# Arguments passed on start (--daemon implied)
+# disable summary and cache-no-size
+#ARGS="--summary=600 --cache=600 --cache-rdomain-only --cache-no-size"
+ARGS="--cache=600 --cache-rdomain-only --no-rulestats"
+
diff --git a/puppet/modules/postfwd/manifests/init.pp b/puppet/modules/postfwd/manifests/init.pp
new file mode 100644
index 00000000..6db3fa52
--- /dev/null
+++ b/puppet/modules/postfwd/manifests/init.pp
@@ -0,0 +1,43 @@
+# This class provides rate-limiting for outgoing SMTP, using postfwd
+# it is configured with some limits that seem reasonable for a generic
+# use-case. Each of the following applies to sasl_authenticated users:
+#
+# . 150 recipients at a time
+# . no more than 50 messages in 60 minutes
+# . no more than 250 recipients in 60 minutes.
+#
+# This class could be easily extended to add overrides to these rules,
+# maximum sizes per client, or additional rules
+class postfwd {
+
+ ensure_packages(['libnet-server-perl', 'libnet-dns-perl', 'postfwd'])
+
+ file {
+ '/etc/default/postfwd':
+ source => 'puppet:///modules/postfwd/postfwd_default',
+ mode => '0644',
+ owner => root,
+ group => root,
+ before => Package['postfwd'];
+
+ '/etc/postfix/postfwd.cf':
+ content => template('postfwd/postfwd.cf.erb'),
+ mode => '0644',
+ owner => root,
+ group => root,
+ require => Package['postfix'],
+ before => Package['postfwd'];
+ }
+
+ service {
+ 'postfwd':
+ ensure => running,
+ name => postfwd,
+ pattern => '/usr/sbin/postfwd',
+ enable => true,
+ hasrestart => true,
+ hasstatus => false,
+ require => [ File['/etc/default/postfwd'],
+ File['/etc/postfix/postfwd.cf']];
+ }
+}
diff --git a/puppet/modules/postfwd/templates/postfwd.cf.erb b/puppet/modules/postfwd/templates/postfwd.cf.erb
new file mode 100644
index 00000000..1c45dd03
--- /dev/null
+++ b/puppet/modules/postfwd/templates/postfwd.cf.erb
@@ -0,0 +1,28 @@
+### This file managed by Puppet
+# Before deploying a rule
+# 1. test with an additional "sender==test@domain.org;" in the rule so it
+# only applies to your test account
+# 2. then when ready to test for all users, use WARN and watch the logs
+# for a few days and make sure it working the way you like
+# 3. Then when ready to deploy for real set a proper error code
+
+## Overrides - make like the following example
+# id=exampleuser; sasl_username==exampleuser; action=dunno
+
+## Rules that apply to all senders
+# Recipient Per Message Limit
+# We only receive mail via smtp from sasl authenticated users
+# directly. We want to limit to a lower amount to prevent phished accounts
+# spamming
+id=RCPTSENDER; recipient_count=150; action=REJECT Too many recipients, please try again. Contact http://<%= @domain %>/tickets/new if this is in error. ERROR:RCPTSENDER
+
+# Message Rate Limit
+# This limits sasl authenticated users to no more than 50/60mins
+# NOTE: sasl_username needs to be set to something or this check will fail
+id=MSGRATE ; sasl_username=!!(^$); action==rate($$sasl_username/100/3600/450 4.7.1 exceeded message rate. Contact Contact http://<%= @domain %>/tickets/new if this is in error. ERROR:MSGRATE)
+
+# Total Recipient Rate Limit
+# This adds up the recipients for all the sasl authenticated users messages
+# and can't exceed more than 250/60min
+# NOTE: sasl_username needs to be set to something or this check will fail
+id=RCPTRATE ; sasl_username=!!(^$); action==rcpt($$sasl_username/500/3600/450 4.7.1 exceeded message rate. Contact http://<%= @domain %>/tickets/new if this is in error. ERROR:RCPTRATE)
diff --git a/puppet/modules/resolvconf/manifests/init.pp b/puppet/modules/resolvconf/manifests/init.pp
new file mode 100644
index 00000000..c22c4ea6
--- /dev/null
+++ b/puppet/modules/resolvconf/manifests/init.pp
@@ -0,0 +1,27 @@
+#
+# resolvconf module
+#
+# Copyright 2008, admin(at)immerda.ch
+# Copyright 2008, Puzzle ITC GmbH
+# Marcel Härry haerry+puppet(at)puzzle.ch
+# Simon Josi josi+puppet(at)puzzle.ch
+#
+# This program is free software; you can redistribute
+# it and/or modify it under the terms of the GNU
+# General Public License version 3 as published by
+# the Free Software Foundation.
+#
+
+class resolvconf(
+ $domain = $::domain,
+ $search = $::domain,
+ $nameservers = [ '8.8.8.8' ]
+) {
+ file{'/etc/resolv.conf':
+ content => $::operatingsystem ? {
+ openbsd => template("resolvconf/resolvconf.${::operatingsystem}.erb"),
+ default => template('resolvconf/resolvconf.erb'),
+ },
+ owner => root, group => 0, mode => 0444;
+ }
+}
diff --git a/puppet/modules/resolvconf/templates/resolvconf.OpenBSD.erb b/puppet/modules/resolvconf/templates/resolvconf.OpenBSD.erb
new file mode 100644
index 00000000..48daf279
--- /dev/null
+++ b/puppet/modules/resolvconf/templates/resolvconf.OpenBSD.erb
@@ -0,0 +1,5 @@
+# managed by puppet
+lookup file bind
+<% scope.lookupvar('resolvconf::nameservers').each do |nameserver| -%>
+nameserver <%= nameserver %>
+<% end -%>
diff --git a/puppet/modules/resolvconf/templates/resolvconf.erb b/puppet/modules/resolvconf/templates/resolvconf.erb
new file mode 100644
index 00000000..d8136bfb
--- /dev/null
+++ b/puppet/modules/resolvconf/templates/resolvconf.erb
@@ -0,0 +1,7 @@
+# managed by puppet
+domain <%= scope.lookupvar('resolvconf::domain') %>
+search <%= scope.lookupvar('resolvconf::search') %>
+
+<% scope.lookupvar('resolvconf::nameservers').each do |nameserver| -%>
+nameserver <%= nameserver %>
+<% end -%>
diff --git a/puppet/modules/rsyslog/.fixtures.yml b/puppet/modules/rsyslog/.fixtures.yml
new file mode 100644
index 00000000..b1fb3e0c
--- /dev/null
+++ b/puppet/modules/rsyslog/.fixtures.yml
@@ -0,0 +1,3 @@
+fixtures:
+ symlinks:
+ "rsyslog": "#{source_dir}"
diff --git a/puppet/modules/rsyslog/.gemfile b/puppet/modules/rsyslog/.gemfile
new file mode 100644
index 00000000..e9e12704
--- /dev/null
+++ b/puppet/modules/rsyslog/.gemfile
@@ -0,0 +1,14 @@
+source 'https://rubygems.org'
+
+group :development, :test do
+ gem 'rake', :require => false
+ gem 'puppet-lint', :require => false
+ gem 'rspec-puppet', :require => false
+ gem 'puppetlabs_spec_helper', :require => false
+end
+
+if puppetversion = ENV['PUPPET_GEM_VERSION']
+ gem 'puppet', puppetversion, :require => false
+else
+ gem 'puppet', :require => false
+end
diff --git a/puppet/modules/rsyslog/.gitignore b/puppet/modules/rsyslog/.gitignore
new file mode 100644
index 00000000..d51673f2
--- /dev/null
+++ b/puppet/modules/rsyslog/.gitignore
@@ -0,0 +1,5 @@
+pkg/
+*.swp
+.forge-releng
+/spec/fixtures
+.DS_Store
diff --git a/puppet/modules/rsyslog/.travis.yml b/puppet/modules/rsyslog/.travis.yml
new file mode 100644
index 00000000..bf7edebb
--- /dev/null
+++ b/puppet/modules/rsyslog/.travis.yml
@@ -0,0 +1,56 @@
+---
+branches:
+ only:
+ - master
+language: ruby
+bundler_args: --without development
+script: bundle exec rake spec SPEC_OPTS='--format documentation'
+after_success:
+ - git clone -q git://github.com/puppetlabs/ghpublisher.git .forge-releng
+ - .forge-releng/publish
+rvm:
+ - 1.8.7
+ - 1.9.3
+ - 2.0.0
+ - 2.1.1
+env:
+ matrix:
+ - PUPPET_GEM_VERSION="~> 2.7.0"
+ - PUPPET_GEM_VERSION="~> 3.0.0"
+ - PUPPET_GEM_VERSION="~> 3.1.0"
+ - PUPPET_GEM_VERSION="~> 3.2.0"
+ - PUPPET_GEM_VERSION="~> 3.3.0"
+ - PUPPET_GEM_VERSION="~> 3.4.0"
+ - PUPPET_GEM_VERSION="~> 3.5.0"
+ global:
+ - PUBLISHER_LOGIN=saz
+ - secure: |-
+ EmipIx5A93xnHKwdHfuMPGNLjLz0M0wND0IyeucWhIHE+KtZ48oT+mO2XhnJSpu1DH
+ JaSoYgjQpCILvniWg76o+HY1bTDEP3AmUlxNFgfDAOAQfv0RHv2cEcgNxNrxsddx6S
+ Ks0FCvVkFgY703X+kBiYTpjP4SBzRe0y9OudSvk=
+matrix:
+ fast_finish: true
+ exclude:
+ - rvm: 1.9.3
+ env: PUPPET_GEM_VERSION="~> 2.7.0"
+ - rvm: 2.0.0
+ env: PUPPET_GEM_VERSION="~> 2.7.0"
+ - rvm: 2.0.0
+ env: PUPPET_GEM_VERSION="~> 3.0.0"
+ - rvm: 2.0.0
+ env: PUPPET_GEM_VERSION="~> 3.1.0"
+ - rvm: 2.1.1
+ env: PUPPET_GEM_VERSION="~> 2.7.0"
+ - rvm: 2.1.1
+ env: PUPPET_GEM_VERSION="~> 3.0.0"
+ - rvm: 2.1.1
+ env: PUPPET_GEM_VERSION="~> 3.1.0"
+ - rvm: 2.1.1
+ env: PUPPET_GEM_VERSION="~> 3.2.0"
+ - rvm: 2.1.1
+ env: PUPPET_GEM_VERSION="~> 3.3.0"
+ - rvm: 2.1.1
+ env: PUPPET_GEM_VERSION="~> 3.4.0"
+notifications:
+ email: false
+gemfile: .gemfile
diff --git a/puppet/modules/rsyslog/LICENSE b/puppet/modules/rsyslog/LICENSE
new file mode 100644
index 00000000..d6456956
--- /dev/null
+++ b/puppet/modules/rsyslog/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/puppet/modules/rsyslog/README.md b/puppet/modules/rsyslog/README.md
new file mode 100644
index 00000000..d9292866
--- /dev/null
+++ b/puppet/modules/rsyslog/README.md
@@ -0,0 +1,202 @@
+# puppet-rsyslog [![Build Status](https://secure.travis-ci.org/saz/puppet-rsyslog.png)](https://travis-ci.org/saz/puppet-rsyslog)
+
+Manage rsyslog client and server via Puppet
+
+## REQUIREMENTS
+
+* Puppet >=2.6 if using parameterized classes
+* Currently supports Ubuntu >=11.04 & Debian running rsyslog >=4.5
+
+## USAGE
+
+### Client
+
+#### Using default values
+```
+ class { 'rsyslog::client': }
+```
+
+#### Variables and default values
+```
+ class { 'rsyslog::client':
+ log_remote => true,
+ spool_size => '1g',
+ remote_type => 'tcp',
+ remote_forward_format => 'RSYSLOG_ForwardFormat',
+ log_local => false,
+ log_auth_local => false,
+ custom_config => undef,
+ custom_params => undef,
+ server => 'log',
+ port => '514',
+ remote_servers => false,
+ ssl_ca => undef,
+ log_templates => false,
+ actionfiletemplate => false
+ }
+```
+for read from file
+```
+ rsyslog::imfile { 'my-imfile':
+ file_name => '/some/file',
+ file_tag => 'mytag',
+ file_facility => 'myfacility',
+ }
+
+```
+
+#### Defining custom logging templates
+
+The `log_templates` parameter can be used to set up custom logging templates, which can be used for local and/or remote logging. More detail on template formats can be found in the [rsyslog documentation](http://www.rsyslog.com/doc/rsyslog_conf_templates.html).
+
+The following examples sets up a custom logging template as per [RFC3164fmt](https://www.ietf.org/rfc/rfc3164.txt):
+
+```puppet
+class{'rsyslog::client':
+ log_templates => [
+ {
+ name => 'RFC3164fmt',
+ template => '<%PRI%>%TIMESTAMP% %HOSTNAME% %syslogtag%%msg%',
+ },
+ ]
+}
+```
+
+#### Logging to multiple remote servers
+
+The `remote_servers` parameter can be used to set up logging to multiple remote servers which are supplied as a list of key value pairs for each remote. There is an example configuration provided in `./test/multiple_hosts.pp`
+
+Using the `remote_servers` parameter over-rides the other remote sever parameters, and they will not be used in the client configuration file:
+* `log_remote`
+* `remote_type`
+* `server`
+* `port`
+
+The following example sets up three remote logging hosts for the client:
+
+```puppet
+class{'rsyslog::client':
+ remote_servers => [
+ {
+ host => 'logs.example.org',
+ },
+ {
+ port => '55514',
+ },
+ {
+ host => 'logs.somewhere.com',
+ port => '555',
+ pattern => '*.log',
+ protocol => 'tcp',
+ format => 'RFC3164fmt',
+ },
+ ]
+}
+```
+
+Each host has the following parameters:
+* *host*: Sets the address or hostname of the remote logging server. Defaults to `localhost`
+* *port*: Sets the port the host is listening on. Defaults to `514`
+* *pattern*: Sets the pattern to match logs. Defaults to `*.*`
+* *protocol*: Sets the protocol. Only recognises TCP and UDP. Defaults to UDP
+* *format*: Sets the log format. Defaults to not specifying log format, which defaults to the format set by `ActionFileDefaultTemplate` in the client configuration.
+
+#### Logging to a MySQL or PostgreSQL database
+
+Events can also be logged to a MySQL or PostgreSQL database. The database needs to be deployed separately, either locally or remotely. Schema are available from the `rsyslog` source:
+
+ * [MySQL schema](http://git.adiscon.com/?p=rsyslog.git;a=blob_plain;f=plugins/ommysql/createDB.sql)
+ * [PostgreSQL schema](http://git.adiscon.com/?p=rsyslog.git;a=blob_plain;f=plugins/ompgsql/createDB.sql)
+
+Declare the following to configure the connection:
+````
+ class { 'rsyslog::database':
+ backend => 'mysql',
+ server => 'localhost',
+ database => 'Syslog',
+ username => 'rsyslog',
+ password => 'secret',
+ }
+````
+### Server
+
+#### Using default values
+```
+ class { 'rsyslog::server': }
+```
+
+#### Variables and default values
+```
+ class { 'rsyslog::server':
+ enable_tcp => true,
+ enable_udp => true,
+ enable_onefile => false,
+ server_dir => '/srv/log/',
+ custom_config => undef,
+ high_precision_timestamps => false,
+ }
+```
+
+Both can be installed at the same time.
+
+## PARAMETERS
+
+The following lists all the class parameters this module accepts.
+
+ RSYSLOG::SERVER CLASS PARAMETERS VALUES DESCRIPTION
+ -------------------------------------------------------------------
+ enable_tcp true,false Enable TCP listener. Defaults to true.
+ enable_udp true,false Enable UDP listener. Defaults to true.
+ enable_onefile true,false Only one logfile per remote host. Defaults to false.
+ server_dir STRING Folder where logs will be stored on the server. Defaults to '/srv/log/'
+ custom_config STRING Specify your own template to use for server config. Defaults to undef. Example usage: custom_config => 'rsyslog/my_config.erb'
+ high_precision_timestamps true,false Whether or not to use high precision timestamps.
+ remote_servers HASH Provides a hash of multiple remote logging servers. Check documentation.
+
+ RSYSLOG::CLIENT CLASS PARAMETERS VALUES DESCRIPTION
+ -------------------------------------------------------------------
+ log_remote true,false Log Remotely. Defaults to true.
+ spool_size STRING Max size for disk queue if remote server failed. Defaults to '1g'.
+ remote_type 'tcp','udp' Which protocol to use when logging remotely. Defaults to 'tcp'.
+ remote_forward_format STRING Which forward format for remote servers should be used. Only used if remote_servers is false.
+ log_local true,false Log locally. Defaults to false.
+ log_auth_local true,false Just log auth facility locally. Defaults to false.
+ custom_config STRING Specify your own template to use for client config. Defaults to undef. Example usage: custom_config => 'rsyslog/my_config.erb'
+ custom_params TODO TODO
+ server STRING Rsyslog server to log to. Will be used in the client configuration file. Only used, if remote_servers is false.
+ port '514' Remote server port. Only used if remote_servers is false.
+ remote_servers Array of hashes Array of hashes with remote servers. See documentation above. Defaults to false.
+ ssl_ca STRING SSL CA file location. Defaults to undef.
+ log_templates HASH Provides a has defining custom logging templates using the `$template` configuration parameter.
+ actionfiletemplate STRING If set this defines the `ActionFileDefaultTemplate` which sets the default logging format for remote and local logging.
+
+ RSYSLOG::DATABASE CLASS PARAMETERS VALUES DESCRIPTION
+ -------------------------------------------------------------------
+ backend 'mysql','pgsql' Database backend (MySQL or PostgreSQL).
+ server STRING Database server.
+ database STRING Database name.
+ username STRING Database username.
+ password STRING Database password.
+
+### Other notes
+
+Due to a missing feature in current RELP versions (InputRELPServerBindRuleset option),
+remote logging is using TCP. You can switch between TCP and UDP. As soon as there is
+a new RELP version which supports setting Rulesets, I will add support for relp back.
+
+By default, rsyslog::server will strip numbers from hostnames. This means the logs of
+multiple servers with the same non-numerical name will be aggregrated in a single
+directory. i.e. www01 www02 and www02 would all log to the www directory.
+
+To log each host to a seperate directory, set the custom_config parameter to
+'rsyslog/server-hostname.conf.erb'
+
+If any of the following parameters are set to `false`, then the module will not
+manage the respective package:
+
+ gnutls_package_name
+ relp_package_name
+ rsyslog_package_name
+
+This can be used when using the adiscon PPA repository, that has merged rsyslog-gnutls
+with the main rsyslog package.
diff --git a/puppet/modules/rsyslog/Rakefile b/puppet/modules/rsyslog/Rakefile
new file mode 100644
index 00000000..469b83c6
--- /dev/null
+++ b/puppet/modules/rsyslog/Rakefile
@@ -0,0 +1,6 @@
+require 'puppetlabs_spec_helper/rake_tasks'
+
+# Enable puppet-lint for all manifests: rake lint
+require 'puppet-lint/tasks/puppet-lint'
+PuppetLint.configuration.send("disable_80chars") # no warnings on lines over 80 chars.
+PuppetLint.configuration.ignore_paths = ["spec/fixtures/**/*.pp"]
diff --git a/puppet/modules/rsyslog/lib/facter/rsyslog_version.rb b/puppet/modules/rsyslog/lib/facter/rsyslog_version.rb
new file mode 100644
index 00000000..de8531e8
--- /dev/null
+++ b/puppet/modules/rsyslog/lib/facter/rsyslog_version.rb
@@ -0,0 +1,38 @@
+# Fact: :syslog_package
+#
+# Purpose: retrieve installed rsyslog version
+#
+
+Facter.add(:rsyslog_version) do
+ setcode do
+ osfamily = Facter.value('osfamily')
+ case osfamily
+ when "Debian"
+ command='/usr/bin/dpkg-query -f \'${Status};${Version};\' -W rsyslog 2>/dev/null'
+ version = Facter::Util::Resolution.exec(command)
+ if version =~ /.*install ok installed;([^;]+);.*/
+ $1
+ else
+ nil
+ end
+ when "RedHat", "Suse"
+ command='rpm -qa --qf "%{VERSION}" "rsyslog"'
+ version = Facter::Util::Resolution.exec(command)
+ if version =~ /^(.+)$/
+ $1
+ else
+ nil
+ end
+ when "FreeBSD"
+ command='pkg query %v rsyslog'
+ version = Facter::Util::Resolution.exec(command)
+ if version =~ /^(.+)$/
+ $1
+ else
+ nil
+ end
+ else
+ nil
+ end
+ end
+end
diff --git a/puppet/modules/rsyslog/manifests/client.pp b/puppet/modules/rsyslog/manifests/client.pp
new file mode 100644
index 00000000..193aa336
--- /dev/null
+++ b/puppet/modules/rsyslog/manifests/client.pp
@@ -0,0 +1,64 @@
+# == Class: rsyslog::client
+#
+# Full description of class role here.
+#
+# === Parameters
+#
+# [*log_remote*]
+# [*spool_size*]
+# [*remote_type*]
+# [*remote_forward_format*]
+# [*log_local*]
+# [*log_auth_local*]
+# [*custom_config*]
+# [*custom_params*]
+# [*server*]
+# [*port*]
+# [*remote_servers*]
+# [*ssl_ca*]
+# [*log_templates*]
+# [*actionfiletemplate*]
+#
+# === Variables
+#
+# === Examples
+#
+# class { 'rsyslog::client': }
+#
+class rsyslog::client (
+ $log_remote = true,
+ $spool_size = '1g',
+ $remote_type = 'tcp',
+ $remote_forward_format = 'RSYSLOG_ForwardFormat',
+ $log_local = false,
+ $log_auth_local = false,
+ $custom_config = undef,
+ $custom_params = undef,
+ $server = 'log',
+ $port = '514',
+ $remote_servers = false,
+ $ssl_ca = undef,
+ $log_templates = false,
+ $actionfiletemplate = false
+) inherits rsyslog {
+
+ if $custom_config {
+ $content_real = template($custom_config)
+ } else {
+ $content_real = template("${module_name}/client.conf.erb")
+ }
+
+ rsyslog::snippet { $rsyslog::client_conf:
+ ensure => present,
+ content => $content_real,
+ }
+
+ if $rsyslog::ssl and $ssl_ca == undef {
+ fail('You need to define $ssl_ca in order to use SSL.')
+ }
+
+ if $rsyslog::ssl and $remote_type != 'tcp' {
+ fail('You need to enable tcp in order to use SSL.')
+ }
+
+}
diff --git a/puppet/modules/rsyslog/manifests/config.pp b/puppet/modules/rsyslog/manifests/config.pp
new file mode 100644
index 00000000..1aebe47b
--- /dev/null
+++ b/puppet/modules/rsyslog/manifests/config.pp
@@ -0,0 +1,51 @@
+# == Class: rsyslog::config
+#
+# Full description of class role here.
+#
+# === Parameters
+#
+# === Variables
+#
+# === Examples
+#
+# class { 'rsyslog::config': }
+#
+class rsyslog::config {
+ file { $rsyslog::rsyslog_d:
+ ensure => directory,
+ owner => 'root',
+ group => $rsyslog::run_group,
+ purge => $rsyslog::purge_rsyslog_d,
+ recurse => true,
+ force => true,
+ require => Class['rsyslog::install'],
+ }
+
+ file { $rsyslog::rsyslog_conf:
+ ensure => file,
+ owner => 'root',
+ group => $rsyslog::run_group,
+ content => template("${module_name}/rsyslog.conf.erb"),
+ require => Class['rsyslog::install'],
+ notify => Class['rsyslog::service'],
+ }
+
+ file { $rsyslog::rsyslog_default:
+ ensure => file,
+ owner => 'root',
+ group => $rsyslog::run_group,
+ content => template("${module_name}/${rsyslog::rsyslog_default_file}.erb"),
+ require => Class['rsyslog::install'],
+ notify => Class['rsyslog::service'],
+ }
+
+ file { $rsyslog::spool_dir:
+ ensure => directory,
+ owner => 'root',
+ group => $rsyslog::run_group,
+ seltype => 'syslogd_var_lib_t',
+ require => Class['rsyslog::install'],
+ notify => Class['rsyslog::service'],
+ }
+
+}
diff --git a/puppet/modules/rsyslog/manifests/database.pp b/puppet/modules/rsyslog/manifests/database.pp
new file mode 100644
index 00000000..fe6d6ac8
--- /dev/null
+++ b/puppet/modules/rsyslog/manifests/database.pp
@@ -0,0 +1,57 @@
+# == Class: rsyslog::database
+#
+# Full description of class role here.
+#
+# === Parameters
+#
+# [*backend*] - Which backend server to use (mysql|pgsql)
+# [*server*] - Server hostname
+# [*database*] - Database name
+# [*username*] - Database username
+# [*password*] - Database password
+#
+# === Variables
+#
+# === Examples
+#
+# class { 'rsyslog::database':
+# backend => 'mysql',
+# server => 'localhost',
+# database => 'mydb',
+# username => 'myuser',
+# password => 'mypass',
+# }
+#
+class rsyslog::database (
+ $backend,
+ $server,
+ $database,
+ $username,
+ $password
+) inherits rsyslog {
+
+ $db_module = "om${backend}"
+ $db_conf = "${rsyslog::rsyslog_d}${backend}.conf"
+
+ case $backend {
+ mysql: { $db_package = $rsyslog::mysql_package_name }
+ pgsql: { $db_package = $rsyslog::pgsql_package_name }
+ default: { fail("Unsupported backend: ${backend}. Only MySQL (mysql) and PostgreSQL (pgsql) are supported.") }
+ }
+
+ package { $db_package:
+ ensure => $rsyslog::package_status,
+ before => File[$db_conf],
+ }
+
+ file { $db_conf:
+ ensure => present,
+ owner => 'root',
+ group => $rsyslog::run_group,
+ mode => '0600',
+ content => template("${module_name}/database.conf.erb"),
+ require => Class['rsyslog::config'],
+ notify => Class['rsyslog::service'],
+ }
+
+}
diff --git a/puppet/modules/rsyslog/manifests/imfile.pp b/puppet/modules/rsyslog/manifests/imfile.pp
new file mode 100644
index 00000000..bd0afa36
--- /dev/null
+++ b/puppet/modules/rsyslog/manifests/imfile.pp
@@ -0,0 +1,48 @@
+# == Define: rsyslog::imfile
+#
+# Full description of class role here.
+#
+# === Parameters
+#
+# [*file_name*]
+# [*file_tag*]
+# [*file_facility*]
+# [*polling_interval*]
+# [*file_severity*]
+# [*run_file_monitor*]
+# [*persist_state_interval]
+#
+# === Variables
+#
+# === Examples
+#
+# rsyslog::imfile { 'my-imfile':
+# file_name => '/some/file',
+# file_tag => 'mytag',
+# file_facility => 'myfacility',
+# }
+#
+define rsyslog::imfile(
+ $file_name,
+ $file_tag,
+ $file_facility,
+ $polling_interval = 10,
+ $file_severity = 'notice',
+ $run_file_monitor = true,
+ $persist_state_interval = 0,
+) {
+
+
+ include rsyslog
+ $extra_modules = $rsyslog::extra_modules
+
+ file { "${rsyslog::rsyslog_d}${name}.conf":
+ ensure => file,
+ owner => 'root',
+ group => $rsyslog::run_group,
+ content => template('rsyslog/imfile.erb'),
+ require => Class['rsyslog::install'],
+ notify => Class['rsyslog::service'],
+ }
+
+}
diff --git a/puppet/modules/rsyslog/manifests/init.pp b/puppet/modules/rsyslog/manifests/init.pp
new file mode 100644
index 00000000..76d61023
--- /dev/null
+++ b/puppet/modules/rsyslog/manifests/init.pp
@@ -0,0 +1,54 @@
+# == Class: rsyslog
+#
+# Meta class to install rsyslog with a basic configuration.
+# You probably want rsyslog::client or rsyslog::server
+#
+# === Parameters
+#
+# === Variables
+#
+# === Examples
+#
+# class { 'rsyslog': }
+#
+class rsyslog (
+ $rsyslog_package_name = $rsyslog::params::rsyslog_package_name,
+ $relp_package_name = $rsyslog::params::relp_package_name,
+ $mysql_package_name = $rsyslog::params::mysql_package_name,
+ $pgsql_package_name = $rsyslog::params::pgsql_package_name,
+ $gnutls_package_name = $rsyslog::params::gnutls_package_name,
+ $package_status = $rsyslog::params::package_status,
+ $rsyslog_d = $rsyslog::params::rsyslog_d,
+ $purge_rsyslog_d = $rsyslog::params::purge_rsyslog_d,
+ $rsyslog_conf = $rsyslog::params::rsyslog_conf,
+ $rsyslog_default = $rsyslog::params::rsyslog_default,
+ $rsyslog_default_file = $rsyslog::params::default_config_file,
+ $run_user = $rsyslog::params::run_user,
+ $run_group = $rsyslog::params::run_group,
+ $log_user = $rsyslog::params::log_user,
+ $log_group = $rsyslog::params::log_group,
+ $log_style = $rsyslog::params::log_style,
+ $umask = $rsyslog::params::umask,
+ $perm_file = $rsyslog::params::perm_file,
+ $perm_dir = $rsyslog::params::perm_dir,
+ $spool_dir = $rsyslog::params::spool_dir,
+ $service_name = $rsyslog::params::service_name,
+ $service_hasrestart = $rsyslog::params::service_hasrestart,
+ $service_hasstatus = $rsyslog::params::service_hasstatus,
+ $client_conf = $rsyslog::params::client_conf,
+ $server_conf = $rsyslog::params::server_conf,
+ $ssl = $rsyslog::params::ssl,
+ $modules = $rsyslog::params::modules,
+ $preserve_fqdn = $rsyslog::params::preserve_fqdn,
+ $max_message_size = $rsyslog::params::max_message_size,
+ $extra_modules = $rsyslog::params::extra_modules
+) inherits rsyslog::params {
+ class { 'rsyslog::install': }
+ class { 'rsyslog::config': }
+
+ if $extra_modules != [] {
+ class { 'rsyslog::modload': }
+ }
+
+ class { 'rsyslog::service': }
+}
diff --git a/puppet/modules/rsyslog/manifests/install.pp b/puppet/modules/rsyslog/manifests/install.pp
new file mode 100644
index 00000000..9798b3f4
--- /dev/null
+++ b/puppet/modules/rsyslog/manifests/install.pp
@@ -0,0 +1,32 @@
+# == Class: rsyslog::install
+#
+# This class makes sure that the required packages are installed
+#
+# === Parameters
+#
+# === Variables
+#
+# === Examples
+#
+# class { 'rsyslog::install': }
+#
+class rsyslog::install {
+ if $rsyslog::rsyslog_package_name != false {
+ package { $rsyslog::rsyslog_package_name:
+ ensure => $rsyslog::package_status,
+ }
+ }
+
+ if $rsyslog::relp_package_name != false {
+ package { $rsyslog::relp_package_name:
+ ensure => $rsyslog::package_status
+ }
+ }
+
+ if $rsyslog::gnutls_package_name != false {
+ package { $rsyslog::gnutls_package_name:
+ ensure => $rsyslog::package_status
+ }
+ }
+
+}
diff --git a/puppet/modules/rsyslog/manifests/modload.pp b/puppet/modules/rsyslog/manifests/modload.pp
new file mode 100644
index 00000000..7a838af1
--- /dev/null
+++ b/puppet/modules/rsyslog/manifests/modload.pp
@@ -0,0 +1,15 @@
+# == Class: rsyslog::modload
+#
+
+class rsyslog::modload (
+ $modload_filename = '10-modload.conf',
+) {
+ file { "${rsyslog::rsyslog_d}${modload_filename}":
+ ensure => file,
+ owner => 'root',
+ group => $rsyslog::run_group,
+ content => template('rsyslog/modload.erb'),
+ require => Class['rsyslog::install'],
+ notify => Class['rsyslog::service'],
+ }
+}
diff --git a/puppet/modules/rsyslog/manifests/params.pp b/puppet/modules/rsyslog/manifests/params.pp
new file mode 100644
index 00000000..12a67cef
--- /dev/null
+++ b/puppet/modules/rsyslog/manifests/params.pp
@@ -0,0 +1,222 @@
+# == Class: rsyslog::params
+#
+# This defines default configuration values for rsyslog.
+# You don't want to use it directly.
+#
+# === Parameters
+#
+# === Variables
+#
+# === Examples
+#
+# class { 'rsyslog::params': }
+#
+class rsyslog::params {
+
+ $max_message_size = '2k'
+ $purge_rsyslog_d = false
+ $extra_modules = []
+ $run_user = 'root'
+ $log_user = 'root'
+ $preserve_fqdn = false
+
+ case $::osfamily {
+ debian: {
+ $rsyslog_package_name = 'rsyslog'
+ $relp_package_name = 'rsyslog-relp'
+ $mysql_package_name = 'rsyslog-mysql'
+ $pgsql_package_name = 'rsyslog-pgsql'
+ $gnutls_package_name = 'rsyslog-gnutls'
+ $package_status = 'latest'
+ $rsyslog_d = '/etc/rsyslog.d/'
+ $rsyslog_conf = '/etc/rsyslog.conf'
+ $rsyslog_default = '/etc/default/rsyslog'
+ $default_config_file = 'rsyslog_default'
+ $run_group = 'root'
+ $log_group = 'adm'
+ $log_style = 'debian'
+ $umask = false
+ $perm_file = '0640'
+ $perm_dir = '0755'
+ $spool_dir = '/var/spool/rsyslog'
+ $service_name = 'rsyslog'
+ $client_conf = 'client'
+ $server_conf = 'server'
+ $ssl = false
+ $modules = [
+ '$ModLoad imuxsock # provides support for local system logging',
+ '$ModLoad imklog # provides kernel logging support (previously done by rklogd)',
+ '#$ModLoad immark # provides --MARK-- message capability',
+ ]
+ $service_hasrestart = true
+ $service_hasstatus = true
+
+ }
+ redhat: {
+ if $::operatingsystem == 'Amazon' {
+ $rsyslog_package_name = 'rsyslog'
+ $mysql_package_name = 'rsyslog-mysql'
+ $pgsql_package_name = 'rsyslog-pgsql'
+ $gnutls_package_name = 'rsyslog-gnutls'
+ $relp_package_name = false
+ $default_config_file = 'rsyslog_default'
+ $modules = [
+ '$ModLoad imuxsock # provides support for local system logging',
+ '$ModLoad imklog # provides kernel logging support (previously done by rklogd)',
+ '#$ModLoad immark # provides --MARK-- message capability',
+ ]
+ }
+ elsif $::operatingsystemmajrelease == 6 {
+ $rsyslog_package_name = 'rsyslog'
+ $mysql_package_name = 'rsyslog-mysql'
+ $pgsql_package_name = 'rsyslog-pgsql'
+ $gnutls_package_name = 'rsyslog-gnutls'
+ $relp_package_name = 'rsyslog-relp'
+ $default_config_file = 'rsyslog_default'
+ $modules = [
+ '$ModLoad imuxsock # provides support for local system logging',
+ '$ModLoad imklog # provides kernel logging support (previously done by rklogd)',
+ '#$ModLoad immark # provides --MARK-- message capability',
+ ]
+ }
+ elsif $::operatingsystemmajrelease >= 7 {
+ $rsyslog_package_name = 'rsyslog'
+ $mysql_package_name = 'rsyslog-mysql'
+ $pgsql_package_name = 'rsyslog-pgsql'
+ $gnutls_package_name = 'rsyslog-gnutls'
+ $relp_package_name = 'rsyslog-relp'
+ $default_config_file = 'rsyslog_default_rhel7'
+ $modules = [
+ '$ModLoad imuxsock # provides support for local system logging',
+ '$ModLoad imjournal # provides access to the systemd journal',
+ '#$ModLoad imklog # provides kernel logging support (previously done by rklogd)',
+ '#$ModLoad immark # provides --MARK-- message capability',
+ ]
+ } else {
+ $rsyslog_package_name = 'rsyslog5'
+ $mysql_package_name = 'rsyslog5-mysql'
+ $pgsql_package_name = 'rsyslog5-pgsql'
+ $gnutls_package_name = 'rsyslog5-gnutls'
+ $relp_package_name = 'librelp'
+ $default_config_file = 'rsyslog_default'
+ $modules = [
+ '$ModLoad imuxsock # provides support for local system logging',
+ '$ModLoad imklog # provides kernel logging support (previously done by rklogd)',
+ '#$ModLoad immark # provides --MARK-- message capability',
+ ]
+ }
+ $package_status = 'latest'
+ $rsyslog_d = '/etc/rsyslog.d/'
+ $rsyslog_conf = '/etc/rsyslog.conf'
+ $rsyslog_default = '/etc/sysconfig/rsyslog'
+ $run_group = 'root'
+ $log_group = 'root'
+ $log_style = 'redhat'
+ $umask = '0000'
+ $perm_file = '0600'
+ $perm_dir = '0750'
+ $spool_dir = '/var/lib/rsyslog'
+ $service_name = 'rsyslog'
+ $client_conf = 'client'
+ $server_conf = 'server'
+ $ssl = false
+ $service_hasrestart = true
+ $service_hasstatus = true
+ }
+ suse: {
+ $rsyslog_package_name = 'rsyslog'
+ $relp_package_name = false
+ $mysql_package_name = false
+ $pgsql_package_name = false
+ $gnutls_package_name = false
+ $package_status = 'latest'
+ $rsyslog_d = '/etc/rsyslog.d/'
+ $rsyslog_conf = '/etc/rsyslog.conf'
+ $rsyslog_default = '/etc/sysconfig/syslog'
+ $run_group = 'root'
+ $log_group = 'root'
+ $log_style = 'debian'
+ $umask = false
+ $perm_file = '0600'
+ $perm_dir = '0750'
+ $spool_dir = '/var/spool/rsyslog/'
+ $service_name = 'syslog'
+ $client_conf = 'client'
+ $server_conf = 'server'
+ $modules = [
+ '$ModLoad imuxsock # provides support for local system logging',
+ '$ModLoad imklog # provides kernel logging support (previously done by rklogd)',
+ '#$ModLoad immark # provides --MARK-- message capability',
+ ]
+ }
+ freebsd: {
+ $rsyslog_package_name = 'sysutils/rsyslog5'
+ $relp_package_name = 'sysutils/rsyslog5-relp'
+ $mysql_package_name = 'sysutils/rsyslog5-mysql'
+ $pgsql_package_name = 'sysutils/rsyslog5-pgsql'
+ $gnutls_package_name = 'sysutils/rsyslog5-gnutls'
+ $package_status = 'present'
+ $rsyslog_d = '/etc/syslog.d/'
+ $rsyslog_conf = '/etc/syslog.conf'
+ $rsyslog_default = '/etc/defaults/syslogd'
+ $default_config_file = 'rsyslog_default'
+ $run_group = 'wheel'
+ $log_group = 'wheel'
+ $log_style = 'debian'
+ $umask = false
+ $perm_file = '0640'
+ $perm_dir = '0755'
+ $spool_dir = '/var/spool/syslog'
+ $service_name = 'syslogd'
+ $client_conf = 'client'
+ $server_conf = 'server'
+ $ssl = false
+ $modules = [
+ '$ModLoad imuxsock # provides support for local system logging',
+ '$ModLoad imklog # provides kernel logging support (previously done by rklogd)',
+ '#$ModLoad immark # provides --MARK-- message capability',
+ ]
+ $service_hasrestart = true
+ $service_hasstatus = true
+ }
+
+ default: {
+ case $::operatingsystem {
+ gentoo: {
+ $rsyslog_package_name = 'app-admin/rsyslog'
+ $relp_package_name = false
+ $mysql_package_name = 'rsyslog-mysql'
+ $pgsql_package_name = 'rsyslog-pgsql'
+ $gnutls_package_name = false
+ $package_status = 'latest'
+ $rsyslog_d = '/etc/rsyslog.d/'
+ $rsyslog_conf = '/etc/rsyslog.conf'
+ $rsyslog_default = '/etc/conf.d/rsyslog'
+ $default_config_file = 'rsyslog_default_gentoo'
+ $run_group = 'root'
+ $log_group = 'adm'
+ $log_style = 'debian'
+ $umask = false
+ $perm_file = '0640'
+ $perm_dir = '0755'
+ $spool_dir = '/var/spool/rsyslog'
+ $service_name = 'rsyslog'
+ $client_conf = 'client'
+ $server_conf = 'server'
+ $ssl = false
+ $modules = [
+ '$ModLoad imuxsock # provides support for local system logging',
+ '$ModLoad imklog # provides kernel logging support (previously done by rklogd)',
+ '#$ModLoad immark # provides --MARK-- message capability',
+ ]
+ $service_hasrestart = true
+ $service_hasstatus = true
+
+ }
+ default: {
+ fail("The ${module_name} module is not supported on ${::osfamily}/${::operatingsystem}.")
+ }
+ }
+ }
+ }
+}
diff --git a/puppet/modules/rsyslog/manifests/server.pp b/puppet/modules/rsyslog/manifests/server.pp
new file mode 100644
index 00000000..13ee56de
--- /dev/null
+++ b/puppet/modules/rsyslog/manifests/server.pp
@@ -0,0 +1,70 @@
+# == Class: rsyslog::server
+#
+# This class configures rsyslog for a server role.
+#
+# === Parameters
+#
+# [*enable_tcp*]
+# [*enable_udp*]
+# [*enable_onefile*]
+# [*server_dir*]
+# [*custom_config*]
+# [*high_precision_timestamps*]
+# [*ssl_ca*]
+# [*ssl_cert*]
+# [*ssl_key*]
+#
+# === Variables
+#
+# === Examples
+#
+# Defaults
+#
+# class { 'rsyslog::server': }
+#
+# Create seperate directory per host
+#
+# class { 'rsyslog::server':
+# custom_config => 'rsyslog/server-hostname.conf.erb'
+# }
+#
+class rsyslog::server (
+ $enable_tcp = true,
+ $enable_udp = true,
+ $enable_onefile = false,
+ $server_dir = '/srv/log/',
+ $custom_config = undef,
+ $port = '514',
+ $high_precision_timestamps = false,
+ $ssl_ca = undef,
+ $ssl_cert = undef,
+ $ssl_key = undef,
+ $rotate = undef
+) inherits rsyslog {
+
+ ### Logrotate policy
+ $logpath = $rotate ? {
+ 'year' => '/%$YEAR%/',
+ 'YEAR' => '/%$YEAR%/',
+ 'month' => '/%$YEAR%/%$MONTH%/',
+ 'MONTH' => '/%$YEAR%/%$MONTH%/',
+ 'day' => '/%$YEAR%/%$MONTH%/%$DAY%/',
+ 'DAY' => '/%$YEAR%/%$MONTH%/%$DAY%/',
+ default => '/',
+ }
+
+ if $custom_config {
+ $real_content = template($custom_config)
+ } else {
+ $real_content = template("${module_name}/server-default.conf.erb")
+ }
+
+ rsyslog::snippet { $rsyslog::server_conf:
+ ensure => present,
+ content => $real_content,
+ }
+
+ if $rsyslog::ssl and (!$enable_tcp or $ssl_ca == undef or $ssl_cert == undef or $ssl_key == undef) {
+ fail('You need to define all the ssl options and enable tcp in order to use SSL.')
+ }
+}
diff --git a/puppet/modules/rsyslog/manifests/service.pp b/puppet/modules/rsyslog/manifests/service.pp
new file mode 100644
index 00000000..4be19999
--- /dev/null
+++ b/puppet/modules/rsyslog/manifests/service.pp
@@ -0,0 +1,21 @@
+# == Class: rsyslog::service
+#
+# This class enforces running of the rsyslog service.
+#
+# === Parameters
+#
+# === Variables
+#
+# === Examples
+#
+# class { 'rsyslog::service': }
+#
+class rsyslog::service {
+ service { $rsyslog::service_name:
+ ensure => running,
+ enable => true,
+ hasstatus => $rsyslog::service_hasstatus,
+ hasrestart => $rsyslog::service_hasrestart,
+ require => Class['rsyslog::config'],
+ }
+}
diff --git a/puppet/modules/rsyslog/manifests/snippet.pp b/puppet/modules/rsyslog/manifests/snippet.pp
new file mode 100644
index 00000000..f6383963
--- /dev/null
+++ b/puppet/modules/rsyslog/manifests/snippet.pp
@@ -0,0 +1,35 @@
+# == Define: rsyslog::snippet
+#
+# This class allows for you to create a rsyslog configuration file with
+# whatever content you pass in.
+#
+# === Parameters
+#
+# [*content*] - The actual content to place in the file.
+# [*ensure*] - How to enforce the file (default: present)
+#
+# === Variables
+#
+# === Examples
+#
+# rsyslog::snippet { 'my-rsyslog-config':
+# content => '<Some rsyslog directive>',
+# }
+#
+define rsyslog::snippet(
+ $content,
+ $ensure = 'present'
+) {
+
+ include rsyslog
+
+ file { "${rsyslog::rsyslog_d}${name}.conf":
+ ensure => $ensure,
+ owner => $rsyslog::run_user,
+ group => $rsyslog::run_group,
+ content => "# This file is managed by Puppet, changes may be overwritten\n${content}\n",
+ require => Class['rsyslog::config'],
+ notify => Class['rsyslog::service'],
+ }
+
+}
diff --git a/puppet/modules/rsyslog/metadata.json b/puppet/modules/rsyslog/metadata.json
new file mode 100644
index 00000000..c9338eef
--- /dev/null
+++ b/puppet/modules/rsyslog/metadata.json
@@ -0,0 +1,62 @@
+{
+ "name": "saz-rsyslog",
+ "version": "3.4.0",
+ "author": "saz",
+ "summary": "Manage rsyslog client and server",
+ "license": "Apache License, Version 2.0",
+ "source": "https://github.com/saz/puppet-rsyslog.git",
+ "project_page": "https://github.com/saz/puppet-rsyslog",
+ "issues_url": "https://github.com/saz/puppet-rsyslog/issues",
+ "operatingsystem_support": [
+ {
+ "operatingsystem": "RedHat"
+ },
+ {
+ "operatingsystem": "Amazon"
+ },
+ {
+ "operatingsystem": "CentOS"
+ },
+ {
+ "operatingsystem": "SuSe"
+ },
+ {
+ "operatingsystem": "SLES"
+ },
+ {
+ "operatingsystem": "OracleLinux"
+ },
+ {
+ "operatingsystem": "Scientific"
+ },
+ {
+ "operatingsystem": "Debian"
+ },
+ {
+ "operatingsystem": "Ubuntu"
+ },
+ {
+ "operatingsystem": "FreeBSD"
+ },
+ {
+ "operatingsystem": "Gentoo"
+ }
+ ],
+ "requirements": [
+ {
+ "name": "pe",
+ "version_requirement": ">= 3.2.0 < 3.4.0"
+ },
+ {
+ "name": "puppet",
+ "version_requirement": "3.x"
+ }
+ ],
+ "description": "Manage rsyslog client and server via Puppet",
+ "types": [
+
+ ],
+ "dependencies": [
+
+ ]
+}
diff --git a/puppet/modules/rsyslog/spec/classes/rsyslog_client_spec.rb b/puppet/modules/rsyslog/spec/classes/rsyslog_client_spec.rb
new file mode 100644
index 00000000..82865db9
--- /dev/null
+++ b/puppet/modules/rsyslog/spec/classes/rsyslog_client_spec.rb
@@ -0,0 +1,146 @@
+require 'spec_helper'
+
+describe 'rsyslog::client', :type => :class do
+
+ context "Rsyslog version >= 8" do
+ let(:default_facts) do
+ {
+ :rsyslog_version => '8.1.2'
+ }
+ end
+
+ context "osfamily = RedHat" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'RedHat',
+ :operatingsystem => 'RedHat',
+ :operatingsystemmajrelease => 6,
+ })
+ end
+
+ context "default usage (osfamily = RedHat)" do
+ let(:title) { 'rsyslog-client-basic' }
+
+ it 'should compile' do
+ should contain_file('/etc/rsyslog.d/client.conf')
+ end
+ end
+ end
+
+ context "osfamily = Debian" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'Debian',
+ })
+ end
+
+ context "default usage (osfamily = Debian)" do
+ let(:title) { 'rsyslog-client-basic' }
+
+ it 'should compile' do
+ should contain_file('/etc/rsyslog.d/client.conf')
+ end
+ end
+ end
+
+ context "osfamily = FreeBSD" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'freebsd',
+ })
+ end
+
+ context "default usage (osfamily = Debian)" do
+ let(:title) { 'rsyslog-client-basic' }
+
+ it 'should compile' do
+ should contain_file('/etc/syslog.d/client.conf')
+ end
+ end
+ end
+ end
+
+ context "Rsyslog version =< 8" do
+ let(:default_facts) do
+ {
+ :rsyslog_version => '7.1.2'
+ }
+ end
+
+ context "osfamily = RedHat" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'RedHat',
+ :operatingsystem => 'RedHat',
+ :operatingsystemmajrelease => 6,
+ })
+ end
+
+ context "default usage (osfamily = RedHat)" do
+ let(:title) { 'rsyslog-client-basic' }
+
+ it 'should compile' do
+ should contain_file('/etc/rsyslog.d/client.conf')
+ end
+ end
+ end
+
+ context "osfamily = Debian" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'Debian',
+ })
+ end
+
+ context "default usage (osfamily = Debian)" do
+ let(:title) { 'rsyslog-client-basic' }
+
+ it 'should compile' do
+ should contain_file('/etc/rsyslog.d/client.conf')
+ end
+ end
+ end
+
+ context "osfamily = FreeBSD" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'freebsd',
+ })
+ end
+
+ context "default usage (osfamily = FreeBSD)" do
+ let(:title) { 'rsyslog-client-basic' }
+
+ it 'should compile' do
+ should contain_file('/etc/syslog.d/client.conf')
+ end
+ end
+ end
+ end
+
+ context "Rsyslog version = nil" do
+ let(:default_facts) do
+ {
+ :rsyslog_version => nil
+ }
+ end
+
+ context "osfamily = RedHat" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'RedHat',
+ :operatingsystem => 'RedHat',
+ :operatingsystemmajrelease => 6,
+ })
+ end
+
+ context "default usage (osfamily = RedHat)" do
+ let(:title) { 'rsyslog-client-basic' }
+
+ it 'should compile' do
+ should contain_file('/etc/rsyslog.d/client.conf')
+ end
+ end
+ end
+ end
+end
diff --git a/puppet/modules/rsyslog/spec/classes/rsyslog_database_spec.rb b/puppet/modules/rsyslog/spec/classes/rsyslog_database_spec.rb
new file mode 100644
index 00000000..0421135a
--- /dev/null
+++ b/puppet/modules/rsyslog/spec/classes/rsyslog_database_spec.rb
@@ -0,0 +1,308 @@
+require 'spec_helper'
+
+describe 'rsyslog::database', :type => :class do
+
+ context "Rsyslog version >= 8" do
+ let(:default_facts) do
+ {
+ :rsyslog_version => '8.1.2'
+ }
+ end
+
+ context "osfamily = RedHat" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'RedHat',
+ :operatingsystem => 'RedHat',
+ :operatingsystemmajrelease => 6,
+ })
+ end
+
+ context "default usage mysql (osfamily = RedHat)" do
+ let(:title) { 'rsyslog-database-mysql' }
+
+ let (:params) {
+ {
+ 'backend' => 'mysql',
+ 'server' => 'localhost',
+ 'database' => 'rsyslog',
+ 'username' => 'us3rname',
+ 'password' => 'passw0rd',
+ }
+ }
+
+ it 'should compile' do
+ should contain_package('rsyslog-mysql')
+ should contain_file('/etc/rsyslog.d/mysql.conf')
+ end
+ end
+
+ context "default usage pgsql (osfamily = RedHat)" do
+ let(:title) { 'rsyslog-database-pgsql' }
+
+ let (:params) {
+ {
+ 'backend' => 'pgsql',
+ 'server' => 'localhost',
+ 'database' => 'rsyslog',
+ 'username' => 'us3rname',
+ 'password' => 'passw0rd',
+ }
+ }
+
+ it 'should compile' do
+ should contain_package('rsyslog-pgsql')
+ should contain_file('/etc/rsyslog.d/pgsql.conf')
+ end
+ end
+ end
+
+
+
+ context "osfamily = Debian" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'Debian',
+ })
+ end
+
+ context "default usage mysql (osfamily = Debian)" do
+ let(:title) { 'rsyslog-database-mysql' }
+
+ let (:params) {
+ {
+ 'backend' => 'mysql',
+ 'server' => 'localhost',
+ 'database' => 'rsyslog',
+ 'username' => 'us3rname',
+ 'password' => 'passw0rd',
+ }
+ }
+
+ it 'should compile' do
+ should contain_package('rsyslog-mysql')
+ should contain_file('/etc/rsyslog.d/mysql.conf')
+ end
+ end
+
+ context "default usage pgsql (osfamily = Debian)" do
+ let(:title) { 'rsyslog-database-pgsql' }
+
+ let (:params) {
+ {
+ 'backend' => 'pgsql',
+ 'server' => 'localhost',
+ 'database' => 'rsyslog',
+ 'username' => 'us3rname',
+ 'password' => 'passw0rd',
+ }
+ }
+
+ it 'should compile' do
+ should contain_package('rsyslog-pgsql')
+ should contain_file('/etc/rsyslog.d/pgsql.conf')
+ end
+ end
+ end
+
+
+
+ context "osfamily = FreeBSD" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'freebsd',
+ })
+ end
+
+ context "default usage mysql (osfamily = FreeBSD)" do
+ let(:title) { 'rsyslog-database-mysql' }
+
+ let (:params) {
+ {
+ 'backend' => 'mysql',
+ 'server' => 'localhost',
+ 'database' => 'rsyslog',
+ 'username' => 'us3rname',
+ 'password' => 'passw0rd',
+ }
+ }
+
+ it 'should compile' do
+ should contain_package('sysutils/rsyslog5-mysql')
+ should contain_file('/etc/syslog.d/mysql.conf')
+ end
+ end
+
+ context "default usage pgsql (osfamily = FreeBSD)" do
+ let(:title) { 'rsyslog-database-pgsql' }
+
+ let (:params) {
+ {
+ 'backend' => 'pgsql',
+ 'server' => 'localhost',
+ 'database' => 'rsyslog',
+ 'username' => 'us3rname',
+ 'password' => 'passw0rd',
+ }
+ }
+
+ it 'should compile' do
+ should contain_package('sysutils/rsyslog5-pgsql')
+ should contain_file('/etc/syslog.d/pgsql.conf')
+ end
+ end
+ end
+ end
+
+ context "Rsyslog version =< 8" do
+ let(:default_facts) do
+ {
+ :rsyslog_version => '7.1.2'
+ }
+ end
+
+ context "osfamily = RedHat" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'RedHat',
+ :operatingsystem => 'RedHat',
+ :operatingsystemmajrelease => 6,
+ })
+ end
+
+ context "default usage mysql (osfamily = RedHat)" do
+ let(:title) { 'rsyslog-database-mysql' }
+
+ let (:params) {
+ {
+ 'backend' => 'mysql',
+ 'server' => 'localhost',
+ 'database' => 'rsyslog',
+ 'username' => 'us3rname',
+ 'password' => 'passw0rd',
+ }
+ }
+
+ it 'should compile' do
+ should contain_package('rsyslog-mysql')
+ should contain_file('/etc/rsyslog.d/mysql.conf')
+ end
+ end
+
+ context "default usage pgsql (osfamily = RedHat)" do
+ let(:title) { 'rsyslog-database-pgsql' }
+
+ let (:params) {
+ {
+ 'backend' => 'pgsql',
+ 'server' => 'localhost',
+ 'database' => 'rsyslog',
+ 'username' => 'us3rname',
+ 'password' => 'passw0rd',
+ }
+ }
+
+ it 'should compile' do
+ should contain_package('rsyslog-pgsql')
+ should contain_file('/etc/rsyslog.d/pgsql.conf')
+ end
+ end
+ end
+
+
+
+ context "osfamily = Debian" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'Debian',
+ })
+ end
+
+ context "default usage mysql (osfamily = Debian)" do
+ let(:title) { 'rsyslog-database-mysql' }
+
+ let (:params) {
+ {
+ 'backend' => 'mysql',
+ 'server' => 'localhost',
+ 'database' => 'rsyslog',
+ 'username' => 'us3rname',
+ 'password' => 'passw0rd',
+ }
+ }
+
+ it 'should compile' do
+ should contain_package('rsyslog-mysql')
+ should contain_file('/etc/rsyslog.d/mysql.conf')
+ end
+ end
+
+ context "default usage pgsql (osfamily = Debian)" do
+ let(:title) { 'rsyslog-database-pgsql' }
+
+ let (:params) {
+ {
+ 'backend' => 'pgsql',
+ 'server' => 'localhost',
+ 'database' => 'rsyslog',
+ 'username' => 'us3rname',
+ 'password' => 'passw0rd',
+ }
+ }
+
+ it 'should compile' do
+ should contain_package('rsyslog-pgsql')
+ should contain_file('/etc/rsyslog.d/pgsql.conf')
+ end
+ end
+ end
+
+
+
+ context "osfamily = FreeBSD" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'freebsd',
+ })
+ end
+
+ context "default usage mysql (osfamily = FreeBSD)" do
+ let(:title) { 'rsyslog-database-mysql' }
+
+ let (:params) {
+ {
+ 'backend' => 'mysql',
+ 'server' => 'localhost',
+ 'database' => 'rsyslog',
+ 'username' => 'us3rname',
+ 'password' => 'passw0rd',
+ }
+ }
+
+ it 'should compile' do
+ should contain_package('sysutils/rsyslog5-mysql')
+ should contain_file('/etc/syslog.d/mysql.conf')
+ end
+ end
+
+ context "default usage pgsql (osfamily = FreeBSD)" do
+ let(:title) { 'rsyslog-database-pgsql' }
+
+ let (:params) {
+ {
+ 'backend' => 'pgsql',
+ 'server' => 'localhost',
+ 'database' => 'rsyslog',
+ 'username' => 'us3rname',
+ 'password' => 'passw0rd',
+ }
+ }
+
+ it 'should compile' do
+ should contain_package('sysutils/rsyslog5-pgsql')
+ should contain_file('/etc/syslog.d/pgsql.conf')
+ end
+ end
+ end
+ end
+end
diff --git a/puppet/modules/rsyslog/spec/classes/rsyslog_server_spec.rb b/puppet/modules/rsyslog/spec/classes/rsyslog_server_spec.rb
new file mode 100644
index 00000000..8f57656b
--- /dev/null
+++ b/puppet/modules/rsyslog/spec/classes/rsyslog_server_spec.rb
@@ -0,0 +1,182 @@
+require 'spec_helper'
+
+describe 'rsyslog::server', :type => :class do
+
+ context "Rsyslog version >= 8" do
+ let(:default_facts) do
+ {
+ :rsyslog_version => '8.1.2'
+ }
+ end
+
+ ['RedHat', 'Debian'].each do |osfamily|
+ context "osfamily = #{osfamily}" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => osfamily,
+ :operatingsystem => osfamily,
+ :operatingsystemmajrelease => 6,
+ })
+ end
+
+ context "default usage (osfamily = #{osfamily})" do
+ let(:title) { 'rsyslog-server-basic' }
+
+ it 'should compile' do
+ should contain_file('/etc/rsyslog.d/server.conf').with_content(/\(\[A-Za-z-\]\*\)--end%\/auth.log/)
+ should contain_file('/etc/rsyslog.d/server.conf').with_content(/\(\[A-Za-z-\]\*\)--end%\/messages/)
+ end
+ end
+
+ context "enable_onefile (osfamily = #{osfamily})" do
+ let(:title) { 'rsyslog-server-onefile' }
+ let(:params) { {'enable_onefile' => 'true'} }
+
+ it 'should compile' do
+ should_not contain_file('/etc/rsyslog.d/server.conf').with_content(/\(\[A-Za-z-\]\*\)--end%\/auth.log/)
+ should contain_file('/etc/rsyslog.d/server.conf').with_content(/\(\[A-Za-z-\]\*\)--end%\/messages/)
+ end
+ end
+
+ context "hostname_template (osfamily = #{osfamily})" do
+ let(:title) { 'rsyslog-server-onefile' }
+ let(:params) { {'custom_config' => 'rsyslog/server-hostname.conf.erb'} }
+
+ it 'should compile' do
+ should contain_file('/etc/rsyslog.d/server.conf').with_content(/%hostname%\/auth.log/)
+ should contain_file('/etc/rsyslog.d/server.conf').with_content(/%hostname%\/messages/)
+ end
+ end
+
+ end
+ end
+
+
+ context "osfamily = FreeBSD" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'freebsd',
+ })
+ end
+
+ context "default usage (osfamily = FreeBSD)" do
+ let(:title) { 'rsyslog-server-basic' }
+
+ it 'should compile' do
+ should contain_file('/etc/syslog.d/server.conf').with_content(/\(\[A-Za-z-\]\*\)--end%\/auth.log/)
+ should contain_file('/etc/syslog.d/server.conf').with_content(/\(\[A-Za-z-\]\*\)--end%\/messages/)
+ end
+ end
+
+ context "enable_onefile (osfamily = FreeBSD)" do
+ let(:title) { 'rsyslog-server-onefile' }
+ let(:params) { {'enable_onefile' => 'true'} }
+
+ it 'should compile' do
+ should_not contain_file('/etc/syslog.d/server.conf').with_content(/\(\[A-Za-z-\]\*\)--end%\/auth.log/)
+ should contain_file('/etc/syslog.d/server.conf').with_content(/\(\[A-Za-z-\]\*\)--end%\/messages/)
+ end
+ end
+
+ context "hostname_template (osfamily = FreeBSD)" do
+ let(:title) { 'rsyslog-server-onefile' }
+ let(:params) { {'custom_config' => 'rsyslog/server-hostname.conf.erb'} }
+
+ it 'should compile' do
+ should contain_file('/etc/syslog.d/server.conf').with_content(/%hostname%\/auth.log/)
+ should contain_file('/etc/syslog.d/server.conf').with_content(/%hostname%\/messages/)
+ end
+ end
+
+ end
+ end
+
+ context "Rsyslog version =< 8" do
+ let(:default_facts) do
+ {
+ :rsyslog_version => '7.1.2'
+ }
+ end
+
+ ['RedHat', 'Debian'].each do |osfamily|
+ context "osfamily = #{osfamily}" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => osfamily,
+ :operatingsystem => osfamily,
+ :operatingsystemmajrelease => 6,
+ })
+ end
+
+ context "default usage (osfamily = #{osfamily})" do
+ let(:title) { 'rsyslog-server-basic' }
+
+ it 'should compile' do
+ should contain_file('/etc/rsyslog.d/server.conf').with_content(/\(\[A-Za-z-\]\*\)--end%\/auth.log/)
+ should contain_file('/etc/rsyslog.d/server.conf').with_content(/\(\[A-Za-z-\]\*\)--end%\/messages/)
+ end
+ end
+
+ context "enable_onefile (osfamily = #{osfamily})" do
+ let(:title) { 'rsyslog-server-onefile' }
+ let(:params) { {'enable_onefile' => 'true'} }
+
+ it 'should compile' do
+ should_not contain_file('/etc/rsyslog.d/server.conf').with_content(/\(\[A-Za-z-\]\*\)--end%\/auth.log/)
+ should contain_file('/etc/rsyslog.d/server.conf').with_content(/\(\[A-Za-z-\]\*\)--end%\/messages/)
+ end
+ end
+
+ context "hostname_template (osfamily = #{osfamily})" do
+ let(:title) { 'rsyslog-server-onefile' }
+ let(:params) { {'custom_config' => 'rsyslog/server-hostname.conf.erb'} }
+
+ it 'should compile' do
+ should contain_file('/etc/rsyslog.d/server.conf').with_content(/%hostname%\/auth.log/)
+ should contain_file('/etc/rsyslog.d/server.conf').with_content(/%hostname%\/messages/)
+ end
+ end
+
+ end
+ end
+
+
+ context "osfamily = FreeBSD" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'freebsd',
+ })
+ end
+
+ context "default usage (osfamily = FreeBSD)" do
+ let(:title) { 'rsyslog-server-basic' }
+
+ it 'should compile' do
+ should contain_file('/etc/syslog.d/server.conf').with_content(/\(\[A-Za-z-\]\*\)--end%\/auth.log/)
+ should contain_file('/etc/syslog.d/server.conf').with_content(/\(\[A-Za-z-\]\*\)--end%\/messages/)
+ end
+ end
+
+ context "enable_onefile (osfamily = FreeBSD)" do
+ let(:title) { 'rsyslog-server-onefile' }
+ let(:params) { {'enable_onefile' => 'true'} }
+
+ it 'should compile' do
+ should_not contain_file('/etc/syslog.d/server.conf').with_content(/\(\[A-Za-z-\]\*\)--end%\/auth.log/)
+ should contain_file('/etc/syslog.d/server.conf').with_content(/\(\[A-Za-z-\]\*\)--end%\/messages/)
+ end
+ end
+
+ context "hostname_template (osfamily = FreeBSD)" do
+ let(:title) { 'rsyslog-server-onefile' }
+ let(:params) { {'custom_config' => 'rsyslog/server-hostname.conf.erb'} }
+
+ it 'should compile' do
+ should contain_file('/etc/syslog.d/server.conf').with_content(/%hostname%\/auth.log/)
+ should contain_file('/etc/syslog.d/server.conf').with_content(/%hostname%\/messages/)
+ end
+ end
+
+ end
+ end
+end # describe 'rsyslog::server'
diff --git a/puppet/modules/rsyslog/spec/classes/rsyslog_spec.rb b/puppet/modules/rsyslog/spec/classes/rsyslog_spec.rb
new file mode 100644
index 00000000..344d7174
--- /dev/null
+++ b/puppet/modules/rsyslog/spec/classes/rsyslog_spec.rb
@@ -0,0 +1,469 @@
+require 'spec_helper'
+
+describe 'rsyslog', :type => :class do
+
+ context "Rsyslog version >= 8" do
+ let(:default_facts) do
+ {
+ :rsyslog_version => '8.1.2'
+ }
+ end
+
+ context "osfamily = RedHat" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'RedHat',
+ :operatingsystem => 'RedHat',
+ :operatingsystemmajrelease => 6,
+ })
+ end
+
+ context "default usage (osfamily = RedHat)" do
+ let(:title) { 'rsyslog-basic' }
+
+ it 'should compile' do
+ should contain_class('rsyslog::install')
+ should contain_class('rsyslog::config')
+ should contain_class('rsyslog::service')
+ end
+ end
+ end
+
+ context "osfamily = Debian" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'Debian',
+ })
+ end
+
+ context "default usage (osfamily = Debian)" do
+ let(:title) { 'rsyslog-basic' }
+
+ it 'should compile' do
+ should contain_class('rsyslog::install')
+ should contain_class('rsyslog::config')
+ should contain_class('rsyslog::service')
+ end
+ end
+ end
+
+ context "osfamily = FreeBSD" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'freebsd',
+ })
+ end
+
+ context "default usage (osfamily = FreeBSD)" do
+ let(:title) { 'rsyslog-basic' }
+
+ it 'should compile' do
+ should contain_class('rsyslog::install')
+ should contain_class('rsyslog::config')
+ should contain_class('rsyslog::service')
+ end
+ end
+ end
+
+ context "osfamily = RedHat" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'RedHat',
+ :operatingsystem => 'RedHat',
+ :operatingsystemmajrelease => 6,
+ })
+ end
+
+ context "default usage (osfamily = RedHat)" do
+ let(:title) { 'rsyslog-basic' }
+
+ it 'should compile' do
+ should contain_file('/etc/rsyslog.conf')
+ should contain_file('/etc/rsyslog.d/')
+ end
+ end
+ end
+
+ context "osfamily = Debian" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'Debian',
+ })
+ end
+
+ context "default usage (osfamily = Debian)" do
+ let(:title) { 'rsyslog-basic' }
+
+ it 'should compile' do
+ should contain_file('/etc/rsyslog.conf')
+ should contain_file('/etc/rsyslog.d/')
+ end
+ end
+ end
+
+ context "osfamily = FreeBSD" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'freebsd',
+ })
+ end
+
+ context "default usage (osfamily = Debian)" do
+ let(:title) { 'rsyslog-basic' }
+
+ it 'should compile' do
+ should contain_file('/etc/syslog.conf')
+ should contain_file('/etc/syslog.d/')
+ end
+ end
+ end
+
+ context "osfamily = RedHat" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'RedHat',
+ :operatingsystem => 'RedHat',
+ :operatingsystemmajrelease => 6,
+ })
+ end
+
+ context "default usage (osfamily = RedHat)" do
+ let(:title) { 'rsyslog-install-basic' }
+
+ it 'should compile' do
+ should contain_package('rsyslog')
+ should contain_package('rsyslog-relp')
+ end
+ end
+ end
+
+ context "osfamily = Debian" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'Debian',
+ })
+ end
+
+ context "default usage (osfamily = Debian)" do
+ let(:title) { 'rsyslog-install-basic' }
+
+ it 'should compile' do
+ should contain_package('rsyslog')
+ should contain_package('rsyslog-relp')
+ end
+ end
+ end
+
+ context "osfamily = FreeBSD" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'freebsd',
+ })
+ end
+
+ context "default usage (osfamily = Debian)" do
+ let(:title) { 'rsyslog-install-basic' }
+
+ it 'should compile' do
+ should contain_package('sysutils/rsyslog5')
+ should contain_package('sysutils/rsyslog5-relp')
+ end
+ end
+ end
+
+ context "osfamily = RedHat" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'RedHat',
+ :operatingsystem => 'RedHat',
+ :operatingsystemmajrelease => 6,
+ })
+ end
+
+ context "default usage (osfamily = RedHat)" do
+ let(:title) { 'rsyslog-service-basic' }
+
+ it 'should compile' do
+ should contain_service('rsyslog')
+ end
+ end
+ end
+
+ context "osfamily = Debian" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'Debian',
+ })
+ end
+
+ context "default usage (osfamily = Debian)" do
+ let(:title) { 'rsyslog-service-basic' }
+
+ it 'should compile' do
+ should contain_service('rsyslog')
+ end
+ end
+ end
+
+ context "osfamily = FreeBSD" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'freebsd',
+ })
+ end
+
+ context "default usage (osfamily = Debian)" do
+ let(:title) { 'rsyslog-service-basic' }
+
+ it 'should compile' do
+ should contain_service('syslogd')
+ end
+ end
+ end
+ end
+
+ context "Rsyslog version =< 8" do
+ let(:default_facts) do
+ {
+ :rsyslog_version => '7.1.2'
+ }
+ end
+
+ context "osfamily = RedHat" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'RedHat',
+ :operatingsystem => 'RedHat',
+ :operatingsystemmajrelease => 6,
+ })
+ end
+
+ context "default usage (osfamily = RedHat)" do
+ let(:title) { 'rsyslog-basic' }
+
+ it 'should compile' do
+ should contain_class('rsyslog::install')
+ should contain_class('rsyslog::config')
+ should contain_class('rsyslog::service')
+ end
+ end
+ end
+
+ context "osfamily = Debian" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'Debian',
+ })
+ end
+
+ context "default usage (osfamily = Debian)" do
+ let(:title) { 'rsyslog-basic' }
+
+ it 'should compile' do
+ should contain_class('rsyslog::install')
+ should contain_class('rsyslog::config')
+ should contain_class('rsyslog::service')
+ end
+ end
+ end
+
+ context "osfamily = FreeBSD" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'freebsd',
+ })
+ end
+
+ context "default usage (osfamily = FreeBSD)" do
+ let(:title) { 'rsyslog-basic' }
+
+ it 'should compile' do
+ should contain_class('rsyslog::install')
+ should contain_class('rsyslog::config')
+ should contain_class('rsyslog::service')
+ end
+ end
+ end
+
+ context "osfamily = RedHat" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'RedHat',
+ :operatingsystem => 'RedHat',
+ :operatingsystemmajrelease => 6,
+ })
+ end
+
+ context "default usage (osfamily = RedHat)" do
+ let(:title) { 'rsyslog-basic' }
+
+ it 'should compile' do
+ should contain_file('/etc/rsyslog.conf')
+ should contain_file('/etc/rsyslog.d/')
+ end
+ end
+ end
+
+ context "osfamily = Debian" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'Debian',
+ })
+ end
+
+ context "default usage (osfamily = Debian)" do
+ let(:title) { 'rsyslog-basic' }
+
+ it 'should compile' do
+ should contain_file('/etc/rsyslog.conf')
+ should contain_file('/etc/rsyslog.d/')
+ end
+ end
+ end
+
+ context "osfamily = FreeBSD" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'freebsd',
+ })
+ end
+
+ context "default usage (osfamily = Debian)" do
+ let(:title) { 'rsyslog-basic' }
+
+ it 'should compile' do
+ should contain_file('/etc/syslog.conf')
+ should contain_file('/etc/syslog.d/')
+ end
+ end
+ end
+
+ context "osfamily = RedHat" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'RedHat',
+ :operatingsystem => 'RedHat',
+ :operatingsystemmajrelease => 6,
+ })
+ end
+
+ context "default usage (osfamily = RedHat)" do
+ let(:title) { 'rsyslog-install-basic' }
+
+ it 'should compile' do
+ should contain_package('rsyslog')
+ should contain_package('rsyslog-relp')
+ end
+ end
+ end
+
+ context "osfamily = Debian" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'Debian',
+ })
+ end
+
+ context "default usage (osfamily = Debian)" do
+ let(:title) { 'rsyslog-install-basic' }
+
+ it 'should compile' do
+ should contain_package('rsyslog')
+ should contain_package('rsyslog-relp')
+ end
+ end
+ end
+
+ context "osfamily = FreeBSD" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'freebsd',
+ })
+ end
+
+ context "default usage (osfamily = Debian)" do
+ let(:title) { 'rsyslog-install-basic' }
+
+ it 'should compile' do
+ should contain_package('sysutils/rsyslog5')
+ should contain_package('sysutils/rsyslog5-relp')
+ end
+ end
+ end
+
+ context "osfamily = RedHat" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'RedHat',
+ :operatingsystem => 'RedHat',
+ :operatingsystemmajrelease => 6,
+ })
+ end
+
+ context "default usage (osfamily = RedHat)" do
+ let(:title) { 'rsyslog-service-basic' }
+
+ it 'should compile' do
+ should contain_service('rsyslog')
+ end
+ end
+ end
+
+ context "osfamily = Debian" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'Debian',
+ })
+ end
+
+ context "default usage (osfamily = Debian)" do
+ let(:title) { 'rsyslog-service-basic' }
+
+ it 'should compile' do
+ should contain_service('rsyslog')
+ end
+ end
+ end
+
+ context "osfamily = FreeBSD" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'freebsd',
+ })
+ end
+
+ context "default usage (osfamily = Debian)" do
+ let(:title) { 'rsyslog-service-basic' }
+
+ it 'should compile' do
+ should contain_service('syslogd')
+ end
+ end
+ end
+ end
+
+ context "Rsyslog version >= 8" do
+ let(:default_facts) do
+ {
+ :rsyslog_version => nil
+ }
+ end
+
+ context "osfamily = RedHat" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'RedHat',
+ :operatingsystem => 'RedHat',
+ :operatingsystemmajrelease => 6,
+ })
+ end
+
+ context "default usage (osfamily = RedHat)" do
+ it 'should compile' do
+ should contain_file('/etc/rsyslog.conf')
+ should contain_file('/etc/rsyslog.d/')
+ end
+ end
+ end
+ end
+end
diff --git a/puppet/modules/rsyslog/spec/defines/rsyslog_imfile_spec.rb b/puppet/modules/rsyslog/spec/defines/rsyslog_imfile_spec.rb
new file mode 100644
index 00000000..1c505eb7
--- /dev/null
+++ b/puppet/modules/rsyslog/spec/defines/rsyslog_imfile_spec.rb
@@ -0,0 +1,169 @@
+require 'spec_helper'
+
+describe 'rsyslog::imfile', :type => :define do
+
+ context "Rsyslog version >= 8" do
+ let(:default_facts) do
+ {
+ :rsyslog_version => '8.1.2'
+ }
+ end
+
+ context "osfamily = RedHat" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'RedHat',
+ :operatingsystem => 'RedHat',
+ :operatingsystemmajrelease => 6,
+ })
+ end
+
+ let (:params) {
+ {
+ 'file_name' => 'mylogfile',
+ 'file_tag' => 'mytag',
+ 'file_facility' => 'myfacility',
+ }
+ }
+
+ context "default usage (osfamily = RedHat)" do
+ let(:title) { 'rsyslog-imfile-basic' }
+
+ it 'should compile' do
+ should contain_file('/etc/rsyslog.d/rsyslog-imfile-basic.conf')
+ end
+ end
+ end
+
+ context "osfamily = Debian" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'Debian',
+ })
+ end
+
+ let (:params) {
+ {
+ 'file_name' => 'mylogfile',
+ 'file_tag' => 'mytag',
+ 'file_facility' => 'myfacility',
+ }
+ }
+
+ context "default usage (osfamily = Debian)" do
+ let(:title) { 'rsyslog-imfile-basic' }
+
+ it 'should compile' do
+ should contain_file('/etc/rsyslog.d/rsyslog-imfile-basic.conf')
+ end
+ end
+ end
+
+ context "osfamily = FreeBSD" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'freebsd',
+ })
+ end
+
+ let (:params) {
+ {
+ 'file_name' => 'mylogfile',
+ 'file_tag' => 'mytag',
+ 'file_facility' => 'myfacility',
+ }
+ }
+
+ context "default usage (osfamily = Debian)" do
+ let(:title) { 'rsyslog-imfile-basic' }
+
+ it 'should compile' do
+ should contain_file('/etc/syslog.d/rsyslog-imfile-basic.conf')
+ end
+ end
+ end
+ end
+
+ context "Rsyslog version =< 8" do
+ let(:default_facts) do
+ {
+ :rsyslog_version => '7.1.2'
+ }
+ end
+
+ context "osfamily = RedHat" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'RedHat',
+ :operatingsystem => 'RedHat',
+ :operatingsystemmajrelease => 6,
+ })
+ end
+
+ let (:params) {
+ {
+ 'file_name' => 'mylogfile',
+ 'file_tag' => 'mytag',
+ 'file_facility' => 'myfacility',
+ }
+ }
+
+ context "default usage (osfamily = RedHat)" do
+ let(:title) { 'rsyslog-imfile-basic' }
+
+ it 'should compile' do
+ should contain_file('/etc/rsyslog.d/rsyslog-imfile-basic.conf')
+ end
+ end
+ end
+
+ context "osfamily = Debian" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'Debian',
+ })
+ end
+
+ let (:params) {
+ {
+ 'file_name' => 'mylogfile',
+ 'file_tag' => 'mytag',
+ 'file_facility' => 'myfacility',
+ }
+ }
+
+ context "default usage (osfamily = Debian)" do
+ let(:title) { 'rsyslog-imfile-basic' }
+
+ it 'should compile' do
+ should contain_file('/etc/rsyslog.d/rsyslog-imfile-basic.conf')
+ end
+ end
+ end
+
+ context "osfamily = FreeBSD" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'freebsd',
+ })
+ end
+
+ let (:params) {
+ {
+ 'file_name' => 'mylogfile',
+ 'file_tag' => 'mytag',
+ 'file_facility' => 'myfacility',
+ }
+ }
+
+ context "default usage (osfamily = Debian)" do
+ let(:title) { 'rsyslog-imfile-basic' }
+
+ it 'should compile' do
+ should contain_file('/etc/syslog.d/rsyslog-imfile-basic.conf')
+ end
+ end
+ end
+ end
+
+end
diff --git a/puppet/modules/rsyslog/spec/defines/rsyslog_snippet_spec.rb b/puppet/modules/rsyslog/spec/defines/rsyslog_snippet_spec.rb
new file mode 100644
index 00000000..6cc68839
--- /dev/null
+++ b/puppet/modules/rsyslog/spec/defines/rsyslog_snippet_spec.rb
@@ -0,0 +1,157 @@
+require 'spec_helper'
+
+describe 'rsyslog::snippet', :type => :define do
+
+ context "Rsyslog version >= 8" do
+ let(:default_facts) do
+ {
+ :rsyslog_version => '8.1.2'
+ }
+ end
+
+ context "osfamily = RedHat" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'RedHat',
+ :operatingsystem => 'Redhat',
+ :operatingsystemmajrelease => 6,
+ })
+ end
+
+ let (:params) {
+ {
+ 'content' => 'Random Content',
+ }
+ }
+
+ context "default usage (osfamily = RedHat)" do
+ let(:title) { 'rsyslog-snippet-basic' }
+
+ it 'should compile' do
+ should contain_file('/etc/rsyslog.d/rsyslog-snippet-basic.conf').with_content("# This file is managed by Puppet, changes may be overwritten\nRandom Content\n")
+ end
+ end
+ end
+
+ context "osfamily = Debian" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'Debian',
+ })
+ end
+
+ let (:params) {
+ {
+ 'content' => 'Random Content',
+ }
+ }
+
+ context "default usage (osfamily = Debian)" do
+ let(:title) { 'rsyslog-snippet-basic' }
+
+ it 'should compile' do
+ should contain_file('/etc/rsyslog.d/rsyslog-snippet-basic.conf').with_content("# This file is managed by Puppet, changes may be overwritten\nRandom Content\n")
+ end
+ end
+ end
+
+ context "osfamily = FreeBSD" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'freebsd',
+ })
+ end
+
+ let (:params) {
+ {
+ 'content' => 'Random Content',
+ }
+ }
+
+ context "default usage (osfamily = Debian)" do
+ let(:title) { 'rsyslog-snippet-basic' }
+
+ it 'should compile' do
+ should contain_file('/etc/syslog.d/rsyslog-snippet-basic.conf').with_content("# This file is managed by Puppet, changes may be overwritten\nRandom Content\n")
+ end
+ end
+ end
+ end
+
+ context "Rsyslog version =< 8" do
+ let(:default_facts) do
+ {
+ :rsyslog_version => '7.1.2'
+ }
+ end
+
+ context "osfamily = RedHat" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'RedHat',
+ :operatingsystem => 'Redhat',
+ :operatingsystemmajrelease => 6,
+ })
+ end
+
+ let (:params) {
+ {
+ 'content' => 'Random Content',
+ }
+ }
+
+ context "default usage (osfamily = RedHat)" do
+ let(:title) { 'rsyslog-snippet-basic' }
+
+ it 'should compile' do
+ should contain_file('/etc/rsyslog.d/rsyslog-snippet-basic.conf').with_content("# This file is managed by Puppet, changes may be overwritten\nRandom Content\n")
+ end
+ end
+ end
+
+ context "osfamily = Debian" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'Debian',
+ })
+ end
+
+ let (:params) {
+ {
+ 'content' => 'Random Content',
+ }
+ }
+
+ context "default usage (osfamily = Debian)" do
+ let(:title) { 'rsyslog-snippet-basic' }
+
+ it 'should compile' do
+ should contain_file('/etc/rsyslog.d/rsyslog-snippet-basic.conf').with_content("# This file is managed by Puppet, changes may be overwritten\nRandom Content\n")
+ end
+ end
+ end
+
+ context "osfamily = FreeBSD" do
+ let :facts do
+ default_facts.merge!({
+ :osfamily => 'freebsd',
+ })
+ end
+
+ let (:params) {
+ {
+ 'content' => 'Random Content',
+ }
+ }
+
+ context "default usage (osfamily = Debian)" do
+ let(:title) { 'rsyslog-snippet-basic' }
+
+ it 'should compile' do
+ should contain_file('/etc/syslog.d/rsyslog-snippet-basic.conf').with_content("# This file is managed by Puppet, changes may be overwritten\nRandom Content\n")
+ end
+ end
+ end
+ end
+
+end
diff --git a/puppet/modules/rsyslog/spec/spec.opts b/puppet/modules/rsyslog/spec/spec.opts
new file mode 100644
index 00000000..91cd6427
--- /dev/null
+++ b/puppet/modules/rsyslog/spec/spec.opts
@@ -0,0 +1,6 @@
+--format
+s
+--colour
+--loadby
+mtime
+--backtrace
diff --git a/puppet/modules/rsyslog/spec/spec_helper.rb b/puppet/modules/rsyslog/spec/spec_helper.rb
new file mode 100644
index 00000000..12bb0b7f
--- /dev/null
+++ b/puppet/modules/rsyslog/spec/spec_helper.rb
@@ -0,0 +1,28 @@
+require 'puppetlabs_spec_helper/module_spec_helper'
+
+RSpec.configure do |c|
+ c.mock_with :rspec do |mock|
+ mock.syntax = [:expect, :should]
+ end
+ c.include PuppetlabsSpec::Files
+
+ c.before :each do
+ # Ensure that we don't accidentally cache facts and environment
+ # between test cases.
+ Facter::Util::Loader.any_instance.stubs(:load_all)
+ Facter.clear
+ Facter.clear_messages
+
+ # Store any environment variables away to be restored later
+ @old_env = {}
+ ENV.each_key {|k| @old_env[k] = ENV[k]}
+
+ if Gem::Version.new(`puppet --version`) >= Gem::Version.new('3.5')
+ Puppet.settings[:strict_variables]=true
+ end
+ end
+
+ c.after :each do
+ PuppetlabsSpec::Files.cleanup
+ end
+end
diff --git a/puppet/modules/rsyslog/templates/client.conf.erb b/puppet/modules/rsyslog/templates/client.conf.erb
new file mode 100644
index 00000000..c05ae797
--- /dev/null
+++ b/puppet/modules/rsyslog/templates/client.conf.erb
@@ -0,0 +1,180 @@
+
+# An "In-Memory Queue" is created for remote logging.
+$WorkDirectory <%= scope.lookupvar('rsyslog::spool_dir') -%> # where to place spool files
+$ActionQueueFileName queue # unique name prefix for spool files
+$ActionQueueMaxDiskSpace <%= scope.lookupvar('rsyslog::client::spool_size') -%> # spool space limit (use as much as possible)
+$ActionQueueSaveOnShutdown on # save messages to disk on shutdown
+$ActionQueueType LinkedList # run asynchronously
+$ActionResumeRetryCount -1 # infinety retries if host is down
+<% if scope.lookupvar('rsyslog::client::log_templates') and ! scope.lookupvar('rsyslog::client::log_templates').empty?-%>
+
+# Define custom logging templates
+<% scope.lookupvar('rsyslog::client::log_templates').flatten.compact.each do |log_template| -%>
+$template <%= log_template['name'] %>,"<%= log_template['template'] %>"
+<% end -%>
+<% end -%>
+<% if scope.lookupvar('rsyslog::client::actionfiletemplate') -%>
+
+# Using specified format for default logging format:
+$ActionFileDefaultTemplate <%= scope.lookupvar('rsyslog::client::actionfiletemplate') %>
+<% else -%>
+
+#Using default format for default logging format:
+$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat
+<% end -%>
+<% if scope.lookupvar('rsyslog::client::ssl') -%>
+
+# Setup SSL connection.
+# CA/Cert
+$DefaultNetStreamDriverCAFile <%= scope.lookupvar('rsyslog::client::ssl_ca') %>
+
+# Connection settings.
+$DefaultNetstreamDriver gtls
+$ActionSendStreamDriverMode 1
+$ActionSendStreamDriverAuthMode anon
+<% end -%>
+<% if scope.lookupvar('rsyslog::client::remote_servers') -%>
+
+<% scope.lookupvar('rsyslog::client::remote_servers').flatten.compact.each do |server| -%>
+<% if server['pattern'] and server['pattern'] != ''-%>
+<% pattern = server['pattern'] -%>
+<% else -%>
+<% pattern = '*.*' -%>
+<% end -%>
+<% if server['protocol'] == 'TCP' or server['protocol'] == 'tcp'-%>
+<% protocol = '@@' -%>
+<% protocol_type = 'TCP' -%>
+<% else -%>
+<% protocol = '@' -%>
+<% protocol_type = 'UDP' -%>
+<% end -%>
+<% if server['host'] and server['host'] != ''-%>
+<% host = server['host'] -%>
+<% else -%>
+<% host = 'localhost' -%>
+<% end -%>
+<% if server['port'] and server['port'] != ''-%>
+<% port = server['port'] -%>
+<% else -%>
+<% port = '514' -%>
+<% end -%>
+<% if server['format'] -%>
+<% format = ";#{server['format']}" -%>
+<% format_type = server['format'] -%>
+<% else -%>
+<% format = '' -%>
+<% format_type = 'the default' -%>
+<% end -%>
+# Sending logs that match <%= pattern %> to <%= host %> via <%= protocol_type %> on <%= port %> using <%=format_type %> format.
+<%= pattern %> <%= protocol %><%= host %>:<%= port %><%= format %>
+<% end -%>
+<% elsif scope.lookupvar('rsyslog::client::log_remote') -%>
+
+# Log to remote syslog server using <%= scope.lookupvar('rsyslog::client::remote_type') %>
+<% if scope.lookupvar('rsyslog::client::remote_type') == 'tcp' -%>
+*.* @@<%= scope.lookupvar('rsyslog::client::server') -%>:<%= scope.lookupvar('rsyslog::client::port') -%>;<%= scope.lookupvar('remote_forward_format') -%>
+<% else -%>
+*.* @<%= scope.lookupvar('rsyslog::client::server') -%>:<%= scope.lookupvar('rsyslog::client::port') -%>;<%= scope.lookupvar('remote_forward_format') -%>
+<% end -%>
+<% end -%>
+<% if scope.lookupvar('rsyslog::client::log_auth_local') or scope.lookupvar('rsyslog::client::log_local') -%>
+
+# Logging locally.
+
+<% if scope.lookupvar('rsyslog::log_style') == 'debian' -%>
+# Log auth messages locally
+auth,authpriv.* /var/log/auth.log
+<% elsif scope.lookupvar('rsyslog::log_style') == 'redhat' -%>
+# Log auth messages locally
+auth,authpriv.* /var/log/secure
+<% end -%>
+<% end -%>
+<% if scope.lookupvar('rsyslog::client::log_local') -%>
+<% if scope.lookupvar('rsyslog::log_style') == 'debian' -%>
+# First some standard log files. Log by facility.
+#
+*.*;auth,authpriv.none -/var/log/syslog
+cron.* /var/log/cron.log
+daemon.* -/var/log/daemon.log
+kern.* -/var/log/kern.log
+#lpr.* -/var/log/lpr.log
+mail.* -/var/log/mail.log
+user.* -/var/log/user.log
+
+#
+# Logging for the mail system. Split it up so that
+# it is easy to write scripts to parse these files.
+#
+mail.info -/var/log/mail.info
+mail.warn -/var/log/mail.warn
+mail.err /var/log/mail.err
+
+#
+# Logging for INN news system.
+#
+news.crit /var/log/news/news.crit
+news.err /var/log/news/news.err
+news.notice -/var/log/news/news.notice
+
+#
+# Some "catch-all" log files.
+#
+*.=debug;\
+ auth,authpriv.none;\
+ news.none;mail.none -/var/log/debug
+*.=info;*.=notice;*.=warn;\
+ auth,authpriv.none;\
+ cron,daemon.none;\
+ mail,news.none -/var/log/messages
+
+#
+# I like to have messages displayed on the console, but only on a virtual
+# console I usually leave idle.
+#
+#daemon,mail.*;\
+# news.=crit;news.=err;news.=notice;\
+# *.=debug;*.=info;\
+# *.=notice;*.=warn /dev/tty8
+
+# The named pipe /dev/xconsole is for the `xconsole' utility. To use it,
+# you must invoke `xconsole' with the `-file' option:
+#
+# $ xconsole -file /dev/xconsole [...]
+#
+# NOTE: adjust the list below, or you'll go crazy if you have a reasonably
+# busy site..
+#
+daemon.*;mail.*;\
+ news.err;\
+ *.=debug;*.=info;\
+ *.=notice;*.=warn |/dev/xconsole
+<% elsif scope.lookupvar('rsyslog::log_style') == 'redhat' -%>
+# Log all kernel messages to the console.
+# Logging much else clutters up the screen.
+#kern.* /dev/console
+
+# Log anything (except mail) of level info or higher.
+# Don't log private authentication messages!
+*.info;mail.none;authpriv.none;cron.none /var/log/messages
+
+# Log all the mail messages in one place.
+mail.* -/var/log/maillog
+
+
+# Log cron stuff
+cron.* /var/log/cron
+
+# Everybody gets emergency messages
+<% if @rsyslog_version and @rsyslog_version.split('.')[0].to_i >= 8 -%>
+*.emerg :omusrmsg:*
+<% else -%>
+*.emerg *
+<% end -%>
+
+# Save news errors of level crit and higher in a special file.
+uucp,news.crit -/var/log/spooler
+
+# Save boot messages also to boot.log
+local7.* -/var/log/boot.log
+<% end -%>
+<% end -%>
diff --git a/puppet/modules/rsyslog/templates/database.conf.erb b/puppet/modules/rsyslog/templates/database.conf.erb
new file mode 100644
index 00000000..3934d6cf
--- /dev/null
+++ b/puppet/modules/rsyslog/templates/database.conf.erb
@@ -0,0 +1,6 @@
+# File is managed by Puppet
+
+## Configuration file for rsyslog-<%= @backend %>
+
+$ModLoad <%= @db_module %>
+*.* :<%= @db_module -%>:<%= @server -%>,<%= @database -%>,<%= @username -%>,<%= @password %>
diff --git a/puppet/modules/rsyslog/templates/imfile.erb b/puppet/modules/rsyslog/templates/imfile.erb
new file mode 100644
index 00000000..4a11c728
--- /dev/null
+++ b/puppet/modules/rsyslog/templates/imfile.erb
@@ -0,0 +1,15 @@
+<% if @extra_modules.empty?() or !@extra_modules.include?('imfile') -%>
+$ModLoad imfile
+<% end -%>
+
+
+$InputFileName <%= @file_name %>
+$InputFileTag <%= @file_tag %>
+$InputFileStateFile state-<%= @name %>
+$InputFileSeverity <%= @file_severity %>
+$InputFileFacility <%= @file_facility %>
+$InputFilePollInterval <%= @polling_interval %>
+$InputFilePersistStateInterval <%= @persist_state_interval %>
+<% if @run_file_monitor == true -%>
+$InputRunFileMonitor
+<% end -%>
diff --git a/puppet/modules/rsyslog/templates/modload.erb b/puppet/modules/rsyslog/templates/modload.erb
new file mode 100644
index 00000000..a14a612a
--- /dev/null
+++ b/puppet/modules/rsyslog/templates/modload.erb
@@ -0,0 +1,3 @@
+<% @extra_modules.each do |mod| -%>
+$ModLoad <%= mod %>
+<% end -%>
diff --git a/puppet/modules/rsyslog/templates/rsyslog.conf.erb b/puppet/modules/rsyslog/templates/rsyslog.conf.erb
new file mode 100644
index 00000000..406aa49c
--- /dev/null
+++ b/puppet/modules/rsyslog/templates/rsyslog.conf.erb
@@ -0,0 +1,49 @@
+# file is managed by puppet
+
+#################
+#### MODULES ####
+#################
+
+<% scope.lookupvar('rsyslog::modules').each do |module_row| -%>
+<%= module_row %>
+<% end -%>
+
+###########################
+#### GLOBAL DIRECTIVES ####
+###########################
+#
+# Set max message size for sending and receiving
+#
+$MaxMessageSize <%= scope.lookupvar('rsyslog::max_message_size') %>
+
+#
+# Set the default permissions for all log files.
+#
+<% if scope.lookupvar('rsyslog::preserve_fqdn') -%>
+$PreserveFQDN on
+<% end -%>
+$FileOwner <%= scope.lookupvar('rsyslog::log_user') %>
+$FileGroup <%= scope.lookupvar('rsyslog::log_group') %>
+$FileCreateMode <%= scope.lookupvar('rsyslog::perm_file') %>
+$DirOwner <%= scope.lookupvar('rsyslog::log_user') %>
+$DirGroup <%= scope.lookupvar('rsyslog::log_group') %>
+$DirCreateMode <%= scope.lookupvar('rsyslog::perm_dir') %>
+$PrivDropToUser <%= scope.lookupvar('rsyslog::run_user') %>
+$PrivDropToGroup <%= scope.lookupvar('rsyslog::run_group') %>
+<% if scope.lookupvar('rsyslog::umask') -%>
+$Umask <%= scope.lookupvar('rsyslog::umask') %>
+<% end -%>
+
+#
+# Include all config files in <%= scope.lookupvar('rsyslog::rsyslog_d') %>
+#
+$IncludeConfig <%= scope.lookupvar('rsyslog::rsyslog_d') -%>*.conf
+
+#
+# Emergencies are sent to everybody logged in.
+#
+<% if @rsyslog_version and @rsyslog_version.split('.')[0].to_i >= 8 -%>
+*.emerg :omusrmsg:*
+<% else -%>
+*.emerg *
+<% end -%>
diff --git a/puppet/modules/rsyslog/templates/rsyslog_default.erb b/puppet/modules/rsyslog/templates/rsyslog_default.erb
new file mode 100644
index 00000000..a49eb59e
--- /dev/null
+++ b/puppet/modules/rsyslog/templates/rsyslog_default.erb
@@ -0,0 +1,9 @@
+# File is managed by puppet
+
+<% if @rsyslog_version and @rsyslog_version.split('.')[0].to_i < 7 -%>
+# Debian, Ubuntu
+RSYSLOGD_OPTIONS="-c4"
+<% end -%>
+
+# CentOS, RedHat, Fedora
+SYSLOGD_OPTIONS="${RSYSLOGD_OPTIONS}"
diff --git a/puppet/modules/rsyslog/templates/rsyslog_default_gentoo.erb b/puppet/modules/rsyslog/templates/rsyslog_default_gentoo.erb
new file mode 100644
index 00000000..f5de7b58
--- /dev/null
+++ b/puppet/modules/rsyslog/templates/rsyslog_default_gentoo.erb
@@ -0,0 +1,16 @@
+# Copyright 1999-2012 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+# $Header: /var/cvsroot/gentoo-x86/app-admin/rsyslog/files/7-stable/rsyslog.confd,v 1.1 2012/11/20 13:03:36 ultrabug Exp $
+
+# Configuration file
+CONFIGFILE="/etc/rsyslog.conf"
+
+# PID file
+PIDFILE="/var/run/rsyslogd.pid"
+
+# Options to rsyslogd
+# See rsyslogd(8) for more details
+# Notes:
+# * Do not specify another PIDFILE but use the variable above to change the location
+# * Do not specify another CONFIGFILE but use the variable above to change the location
+RSYSLOG_OPTS=""
diff --git a/puppet/modules/rsyslog/templates/rsyslog_default_rhel7.erb b/puppet/modules/rsyslog/templates/rsyslog_default_rhel7.erb
new file mode 100644
index 00000000..c3b95c7f
--- /dev/null
+++ b/puppet/modules/rsyslog/templates/rsyslog_default_rhel7.erb
@@ -0,0 +1,2 @@
+# File is managed by puppet
+SYSLOGD_OPTIONS=""
diff --git a/puppet/modules/rsyslog/templates/server-default.conf.erb b/puppet/modules/rsyslog/templates/server-default.conf.erb
new file mode 100644
index 00000000..0c7f67fe
--- /dev/null
+++ b/puppet/modules/rsyslog/templates/server-default.conf.erb
@@ -0,0 +1,42 @@
+# File is managed by puppet
+
+<% # Common header across all templates -%>
+<%= scope.function_template(['rsyslog/server/_default-header.conf.erb']) %>
+
+# Log files are stored in directories matching the short hostname, excluding numbers
+# i.e. web01 web02 and web03 will all log to a the web directory
+<% if scope.lookupvar('rsyslog::server::enable_onefile') == false -%>
+
+# Templates
+$Template dynAuthLog,"<%= scope.lookupvar('rsyslog::server::server_dir') -%>%source:R,ERE,1,DFLT:([A-Za-z-]*)--end%<%= scope.lookupvar('rsyslog::server::logpath') -%>auth.log"
+$Template dynSyslog,"<%= scope.lookupvar('rsyslog::server::server_dir') -%>%source:R,ERE,1,DFLT:([A-Za-z-]*)--end%<%= scope.lookupvar('rsyslog::server::logpath') -%>syslog"
+$Template dynCronLog,"<%= scope.lookupvar('rsyslog::server::server_dir') -%>%source:R,ERE,1,DFLT:([A-Za-z-]*)--end%<%= scope.lookupvar('rsyslog::server::logpath') -%>cron.log"
+$Template dynDaemonLog,"<%= scope.lookupvar('rsyslog::server::server_dir') -%>%source:R,ERE,1,DFLT:([A-Za-z-]*)--end%<%= scope.lookupvar('rsyslog::server::logpath') -%>daemon.log"
+$Template dynKernLog,"<%= scope.lookupvar('rsyslog::server::server_dir') -%>%source:R,ERE,1,DFLT:([A-Za-z-]*)--end%<%= scope.lookupvar('rsyslog::server::logpath') -%>kern.log"
+$Template dynUserLog,"<%= scope.lookupvar('rsyslog::server::server_dir') -%>%source:R,ERE,1,DFLT:([A-Za-z-]*)--end%<%= scope.lookupvar('rsyslog::server::logpath') -%>user.log"
+$Template dynMailLog,"<%= scope.lookupvar('rsyslog::server::server_dir') -%>%source:R,ERE,1,DFLT:([A-Za-z-]*)--end%<%= scope.lookupvar('rsyslog::server::logpath') -%>mail.log"
+$Template dynDebug,"<%= scope.lookupvar('rsyslog::server::server_dir') -%>%source:R,ERE,1,DFLT:([A-Za-z-]*)--end%<%= scope.lookupvar('rsyslog::server::logpath') -%>debug"
+$Template dynMessages,"<%= scope.lookupvar('rsyslog::server::server_dir') -%>%source:R,ERE,1,DFLT:([A-Za-z-]*)--end%<%= scope.lookupvar('rsyslog::server::logpath') -%>messages"
+
+# Rules
+auth,authpriv.* ?dynAuthLog
+*.*;auth,authpriv.none,mail.none,cron.none -?dynSyslog
+cron.* ?dynCronLog
+daemon.* -?dynDaemonLog
+kern.* -?dynKernLog
+mail.* -?dynMailLog
+user.* -?dynUserLog
+*.=info;*.=notice;*.=warn;\
+ auth.none,authpriv.none;\
+ cron.none,daemon.none;\
+ mail.none,news.none -?dynMessages
+<% else -%>
+# Template
+$Template dynAllMessages,"<%= scope.lookupvar('rsyslog::server::server_dir') -%>%source:R,ERE,1,DFLT:([A-Za-z-]*)--end%<%= scope.lookupvar('rsyslog::server::logpath') -%>messages"
+
+# Rules
+*.* -?dynAllMessages
+<% end -%>
+
+<% # Common footer across all templates -%>
+<%= scope.function_template(['rsyslog/server/_default-footer.conf.erb']) %>
diff --git a/puppet/modules/rsyslog/templates/server-hostname.conf.erb b/puppet/modules/rsyslog/templates/server-hostname.conf.erb
new file mode 100644
index 00000000..67158d95
--- /dev/null
+++ b/puppet/modules/rsyslog/templates/server-hostname.conf.erb
@@ -0,0 +1,41 @@
+# File is managed by puppet
+
+<% # Common header across all templates -%>
+<%= scope.function_template(['rsyslog/server/_default-header.conf.erb']) %>
+
+# Log files are stored in directories matching the hostname
+<% if scope.lookupvar('rsyslog::server::enable_onefile') == false -%>
+
+# Templates
+$Template dynAuthLog,"<%= scope.lookupvar('rsyslog::server::server_dir') -%>%hostname%<%= scope.lookupvar('rsyslog::server::logpath') -%>auth.log"
+$Template dynSyslog,"<%= scope.lookupvar('rsyslog::server::server_dir') -%>%hostname%<%= scope.lookupvar('rsyslog::server::logpath') -%>syslog"
+$Template dynCronLog,"<%= scope.lookupvar('rsyslog::server::server_dir') -%>%hostname%<%= scope.lookupvar('rsyslog::server::logpath') -%>cron.log"
+$Template dynDaemonLog,"<%= scope.lookupvar('rsyslog::server::server_dir') -%>%hostname%<%= scope.lookupvar('rsyslog::server::logpath') -%>daemon.log"
+$Template dynKernLog,"<%= scope.lookupvar('rsyslog::server::server_dir') -%>%hostname%<%= scope.lookupvar('rsyslog::server::logpath') -%>kern.log"
+$Template dynUserLog,"<%= scope.lookupvar('rsyslog::server::server_dir') -%>%hostname%<%= scope.lookupvar('rsyslog::server::logpath') -%>user.log"
+$Template dynMailLog,"<%= scope.lookupvar('rsyslog::server::server_dir') -%>%hostname%<%= scope.lookupvar('rsyslog::server::logpath') -%>mail.log"
+$Template dynDebug,"<%= scope.lookupvar('rsyslog::server::server_dir') -%>%hostname%<%= scope.lookupvar('rsyslog::server::logpath') -%>debug"
+$Template dynMessages,"<%= scope.lookupvar('rsyslog::server::server_dir') -%>%hostname%<%= scope.lookupvar('rsyslog::server::logpath') -%>messages"
+
+# Rules
+auth,authpriv.* ?dynAuthLog
+*.*;auth,authpriv.none,mail.none,cron.none -?dynSyslog
+cron.* ?dynCronLog
+daemon.* -?dynDaemonLog
+kern.* -?dynKernLog
+mail.* -?dynMailLog
+user.* -?dynUserLog
+*.=info;*.=notice;*.=warn;\
+ auth.none,authpriv.none;\
+ cron.none,daemon.none;\
+ mail.none,news.none -?dynMessages
+<% else -%>
+# Template
+$Template dynAllMessages,"<%= scope.lookupvar('rsyslog::server::server_dir') -%>%hostname%<%= scope.lookupvar('rsyslog::server::logpath') -%>messages"
+
+# Rules
+*.* -?dynAllMessages
+<% end -%>
+
+<% # Common footer across all templates -%>
+<%= scope.function_template(['rsyslog/server/_default-footer.conf.erb']) %>
diff --git a/puppet/modules/rsyslog/templates/server/_default-footer.conf.erb b/puppet/modules/rsyslog/templates/server/_default-footer.conf.erb
new file mode 100644
index 00000000..d8bd00ad
--- /dev/null
+++ b/puppet/modules/rsyslog/templates/server/_default-footer.conf.erb
@@ -0,0 +1,13 @@
+
+# Switch back to default ruleset
+$RuleSet RSYSLOG_DefaultRuleset
+
+<% if scope.lookupvar('rsyslog::server::enable_udp') -%>
+$InputUDPServerBindRuleset remote
+$UDPServerRun <%= scope.lookupvar('rsyslog::server::port') %>
+<% end -%>
+
+<% if scope.lookupvar('rsyslog::server::enable_tcp') -%>
+$InputTCPServerBindRuleset remote
+$InputTCPServerRun <%= scope.lookupvar('rsyslog::server::port') %>
+<% end -%>
diff --git a/puppet/modules/rsyslog/templates/server/_default-header.conf.erb b/puppet/modules/rsyslog/templates/server/_default-header.conf.erb
new file mode 100644
index 00000000..4bffa858
--- /dev/null
+++ b/puppet/modules/rsyslog/templates/server/_default-header.conf.erb
@@ -0,0 +1,36 @@
+<% if scope.lookupvar('rsyslog::server::enable_udp') -%>
+# Load UDP module
+$ModLoad imudp
+<% end -%>
+
+<% if scope.lookupvar('rsyslog::server::enable_tcp') -%>
+# Load TCP module
+$ModLoad imtcp
+<% end -%>
+
+#
+<% if scope.lookupvar('rsyslog::server::high_precision_timestamps') == false -%>
+# Use traditional timestamp format.
+#
+$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat
+<% else -%>
+# Use high precision timestamp format.
+#
+$ActionFileDefaultTemplate RSYSLOG_FileFormat
+<% end -%>
+
+<% if scope.lookupvar('rsyslog::server::ssl') -%>
+# Server side SSL.
+$DefaultNetstreamDriver gtls
+
+# Cert files.
+$DefaultNetstreamDriverCAFile <%= scope.lookupvar('rsyslog::server::ssl_ca') %>
+$DefaultNetstreamDriverCertFile <%= scope.lookupvar('rsyslog::server::ssl_cert') %>
+$DefaultNetstreamDriverKeyFile <%= scope.lookupvar('rsyslog::server::ssl_key') %>
+
+$InputTCPServerStreamDriverMode 1
+$InputTCPServerStreamDriverAuthMode anon
+<% end -%>
+
+# Switch to remote ruleset
+$RuleSet remote
diff --git a/puppet/modules/rsyslog/tests/database.pp b/puppet/modules/rsyslog/tests/database.pp
new file mode 100644
index 00000000..269be696
--- /dev/null
+++ b/puppet/modules/rsyslog/tests/database.pp
@@ -0,0 +1,9 @@
+include rsyslog
+
+class { 'rsyslog::database':
+ backend => 'mysql',
+ server => 'localhost',
+ database => 'Syslog',
+ username => 'rsyslog',
+ password => 'secret',
+}
diff --git a/puppet/modules/rsyslog/tests/init.pp b/puppet/modules/rsyslog/tests/init.pp
new file mode 100644
index 00000000..7fc50c8b
--- /dev/null
+++ b/puppet/modules/rsyslog/tests/init.pp
@@ -0,0 +1 @@
+include rsyslog
diff --git a/puppet/modules/rsyslog/tests/log_templates.pp b/puppet/modules/rsyslog/tests/log_templates.pp
new file mode 100644
index 00000000..a6bf75b7
--- /dev/null
+++ b/puppet/modules/rsyslog/tests/log_templates.pp
@@ -0,0 +1,9 @@
+class { 'rsyslog::client':
+ log_templates => [
+ {
+ name => 'RFC3164fmt',
+ template => '<%PRI%>%TIMESTAMP% %HOSTNAME% %syslogtag%%msg%',
+ },
+ ],
+ actionfiletemplate => 'RFC3164fmt',
+}
diff --git a/puppet/modules/rsyslog/tests/multiple_hosts.pp b/puppet/modules/rsyslog/tests/multiple_hosts.pp
new file mode 100644
index 00000000..9e5a60ed
--- /dev/null
+++ b/puppet/modules/rsyslog/tests/multiple_hosts.pp
@@ -0,0 +1,17 @@
+class { 'rsyslog::client':
+ remote_servers => [
+ {
+ host => 'logs.example.org',
+ },
+ {
+ port => '55514',
+ },
+ {
+ host => 'logs.somewhere.com',
+ port => '555',
+ pattern => '*.log',
+ protocol => 'tcp',
+ format => 'RFC3164fmt',
+ },
+ ]
+}
diff --git a/puppet/modules/ruby/manifests/devel.pp b/puppet/modules/ruby/manifests/devel.pp
new file mode 100644
index 00000000..7068a74a
--- /dev/null
+++ b/puppet/modules/ruby/manifests/devel.pp
@@ -0,0 +1,5 @@
+# install ruby header files and rake
+class ruby::devel {
+ include ruby
+ ensure_packages($ruby::ruby_dev)
+}
diff --git a/puppet/modules/ruby/manifests/init.pp b/puppet/modules/ruby/manifests/init.pp
new file mode 100644
index 00000000..8d8ae48e
--- /dev/null
+++ b/puppet/modules/ruby/manifests/init.pp
@@ -0,0 +1,72 @@
+# Class: ruby
+#
+# This class installs Ruby
+#
+# Parameters:
+#
+# version: (default installed)
+# Set the version of Ruby to install
+#
+# Sample Usage:
+#
+# For a standard install using the latest ruby, simply do:
+#
+# class { 'ruby': }
+#
+# On Debian this is equivilant to
+# $ apt-get install ruby
+#
+# To install a specific version of ruby, simply do:
+#
+# class { 'ruby':
+# ruby_version => '1.8.7',
+# }
+#
+# Supported versions: 1.8, 1.8.7, 1.9, 1.9.1, 1.9.3
+#
+# To install the development files, you can do:
+#
+# class { 'ruby': install_dev => true }
+
+class ruby (
+ $ruby_version = '',
+ $version = 'installed',
+ $install_dev = false
+)
+{
+
+ case $::operatingsystem {
+ 'redhat', 'suse': {
+ $ruby_package='ruby'
+ $ruby_dev='ruby-devel'
+ }
+ 'debian', 'ubuntu': {
+ case $ruby_version {
+ '1.8', '1.8.7': {
+ $ruby_package = 'ruby1.8'
+ $ruby_dev = [ 'ruby1.8-dev', 'rake' ]
+ }
+ '1.9.1': {
+ $ruby_package = 'ruby1.9.1'
+ $ruby_dev = [ 'ruby1.9.1-dev', 'rake' ]
+ }
+ '1.9', '1.9.3': {
+ $ruby_package = 'ruby1.9.3'
+ $ruby_dev = [ 'ruby-dev', 'rake' ]
+ }
+ default: {
+ $ruby_package = 'ruby'
+ $ruby_dev = [ 'ruby-dev', 'rake' ]
+ }
+ }
+ }
+ }
+
+ package{ $ruby_package:
+ ensure => $version,
+ }
+
+ if $install_dev {
+ ensure_packages($ruby_dev)
+ }
+}
diff --git a/puppet/modules/ruby/manifests/mysql.pp b/puppet/modules/ruby/manifests/mysql.pp
new file mode 100644
index 00000000..2e894789
--- /dev/null
+++ b/puppet/modules/ruby/manifests/mysql.pp
@@ -0,0 +1,7 @@
+class ruby::mysql {
+ include ruby
+ package{'ruby-mysql':
+ ensure => present,
+ require => Package['ruby'],
+ }
+}
diff --git a/puppet/modules/ruby/manifests/postgres.pp b/puppet/modules/ruby/manifests/postgres.pp
new file mode 100644
index 00000000..ec0e253a
--- /dev/null
+++ b/puppet/modules/ruby/manifests/postgres.pp
@@ -0,0 +1,6 @@
+class ruby::postgres {
+ include ruby
+ package{'ruby-postgres':
+ ensure => installed,
+ }
+}
diff --git a/puppet/modules/ruby/manifests/shadow.pp b/puppet/modules/ruby/manifests/shadow.pp
new file mode 100644
index 00000000..43f1aeab
--- /dev/null
+++ b/puppet/modules/ruby/manifests/shadow.pp
@@ -0,0 +1,6 @@
+class ruby::shadow {
+ case $::operatingsystem {
+ debian,ubuntu: { include ruby::shadow::debian }
+ default: { include ruby::shadow::base }
+ }
+}
diff --git a/puppet/modules/ruby/manifests/shadow/base.pp b/puppet/modules/ruby/manifests/shadow/base.pp
new file mode 100644
index 00000000..af8c5c92
--- /dev/null
+++ b/puppet/modules/ruby/manifests/shadow/base.pp
@@ -0,0 +1,6 @@
+class ruby::shadow::base {
+ require ::ruby
+ package{'ruby-shadow':
+ ensure => installed,
+ }
+}
diff --git a/puppet/modules/ruby/manifests/shadow/debian.pp b/puppet/modules/ruby/manifests/shadow/debian.pp
new file mode 100644
index 00000000..8182b9b1
--- /dev/null
+++ b/puppet/modules/ruby/manifests/shadow/debian.pp
@@ -0,0 +1,8 @@
+class ruby::shadow::debian inherits ruby::shadow::base {
+ Package['ruby-shadow']{
+ name => $::lsbdistcodename ? {
+ 'wheezy' => 'libshadow-ruby1.8',
+ default => 'ruby-shadow',
+ }
+ }
+}
diff --git a/puppet/modules/rubygems/files/gemrc b/puppet/modules/rubygems/files/gemrc
new file mode 100644
index 00000000..040f20ba
--- /dev/null
+++ b/puppet/modules/rubygems/files/gemrc
@@ -0,0 +1,3 @@
+---
+:sources:
+- https://rubygems.org/
diff --git a/puppet/modules/rubygems/manifests/activerecord.pp b/puppet/modules/rubygems/manifests/activerecord.pp
new file mode 100644
index 00000000..131222af
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/activerecord.pp
@@ -0,0 +1,7 @@
+class rubygems::activerecord {
+ require rubygems
+ package{'activerecord':
+ ensure => present,
+ provider => gem,
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/activesupport.pp b/puppet/modules/rubygems/manifests/activesupport.pp
new file mode 100644
index 00000000..ae5aee70
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/activesupport.pp
@@ -0,0 +1,7 @@
+class rubygems::activesupport {
+ require rubygems
+ package{'activesupport':
+ ensure => present,
+ provider => gem,
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/backports.pp b/puppet/modules/rubygems/manifests/backports.pp
new file mode 100644
index 00000000..4290e340
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/backports.pp
@@ -0,0 +1,7 @@
+class rubygems::backports {
+ require rubygems::devel
+ package{'backports':
+ ensure => present,
+ provider => gem,
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/bcrypt.pp b/puppet/modules/rubygems/manifests/bcrypt.pp
new file mode 100644
index 00000000..4c646477
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/bcrypt.pp
@@ -0,0 +1,14 @@
+class rubygems::bcrypt {
+ if ($::osfamily == 'RedHat') and
+ versioncmp($::operatingsystemrelease,'6') > 0 {
+ package{'rubygem-bcrypt':
+ ensure => present,
+ }
+ } else {
+ require rubygems
+ package{'bcrypt-ruby':
+ ensure => present,
+ provider => gem,
+ }
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/brokengem.pp b/puppet/modules/rubygems/manifests/brokengem.pp
new file mode 100644
index 00000000..b3284d97
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/brokengem.pp
@@ -0,0 +1,14 @@
+define rubygems::brokengem($source,$ensure) {
+ exec { "get-gem-$name":
+ command => "/usr/bin/wget --output-document=/tmp/$name.gem $source",
+ creates => "/tmp/$name.gem",
+ before => Package[$name]
+ }
+ package{$name:
+ ensure => $ensure,
+ provider => gem,
+ source => "/tmp/$name.gem"
+ }
+}
+
+# $Id$
diff --git a/puppet/modules/rubygems/manifests/camping.pp b/puppet/modules/rubygems/manifests/camping.pp
new file mode 100644
index 00000000..f79fca13
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/camping.pp
@@ -0,0 +1,7 @@
+class rubygems::camping {
+ require rubygems::rack
+ package{'camping':
+ ensure => present,
+ provider => gem,
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/captcha/v_0_1_2.pp b/puppet/modules/rubygems/manifests/captcha/v_0_1_2.pp
new file mode 100644
index 00000000..2a4e7123
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/captcha/v_0_1_2.pp
@@ -0,0 +1,5 @@
+class rubygems::captcha::v_0_1_2 {
+ rubygems::gem{ 'captcha-0.1.2':
+ requiresgcc => true,
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/chronic_duration.pp b/puppet/modules/rubygems/manifests/chronic_duration.pp
new file mode 100644
index 00000000..c789eb51
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/chronic_duration.pp
@@ -0,0 +1,5 @@
+class rubygems::chronic_duration {
+ rubygems::gem{'chronic_duration':
+ ensure => present,
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/devel.pp b/puppet/modules/rubygems/manifests/devel.pp
new file mode 100644
index 00000000..2f69f892
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/devel.pp
@@ -0,0 +1,6 @@
+class rubygems::devel {
+ include ::rubygems
+ include ruby::devel
+ include gcc
+}
+
diff --git a/puppet/modules/rubygems/manifests/fastercsv.pp b/puppet/modules/rubygems/manifests/fastercsv.pp
new file mode 100644
index 00000000..95ae0212
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/fastercsv.pp
@@ -0,0 +1,6 @@
+class rubygems::fastercsv {
+ rubygems::gem{'fastercsv':
+ ensure => present,
+ source => 'http://rubyforge.org/frs/download.php/43190/fastercsv-1.4.0.gem',
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/gd/v_0_7_4.pp b/puppet/modules/rubygems/manifests/gd/v_0_7_4.pp
new file mode 100644
index 00000000..9027ecb5
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/gd/v_0_7_4.pp
@@ -0,0 +1,5 @@
+class rubygems::gd::v_0_7_4 {
+ rubygems::gem{ 'ruby-gd-0.7.4':
+ buildflags => '--with-freetype',
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/gem.pp b/puppet/modules/rubygems/manifests/gem.pp
new file mode 100644
index 00000000..14b67850
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/gem.pp
@@ -0,0 +1,108 @@
+# Installs gems that are slightly broken
+# As a name it expects the name of the gem.
+# If you want to want to install a certain version
+# you have to append the version to the gem name:
+#
+# install a version of mime-types:
+# rubygems::gem{'mime-types': }
+#
+# install version 0.0.4 of ruby-net-ldap:
+# rubygems::gem{'ruby-net-ldap-0.0.4': }
+#
+# uninstall polygot gem (until no such gem is anymore installed):
+# rubygems::gem{'polygot': ensure => absent }
+#
+# uninstall ruby-net-ldap version 0.0.3
+# rubygems::gem{'ruby-net-ldap-0.0.3': ensure => absent }
+#
+# You can also set your own buildlfags, which will then install
+# the gem in question by the gem command.
+#
+# You can also enforce to use the gem command to manage the gem
+# by setting provider to `exec`.
+#
+define rubygems::gem(
+ $ensure = 'present',
+ $source = 'absent',
+ $provider = 'default',
+ $buildflags = 'absent',
+ $requiresgcc = false
+) {
+ require ::rubygems
+ if $requiresgcc or ($buildflags != 'absent') {
+ require ::gcc
+ }
+
+ if $name =~ /\-(\d|\.)+$/ {
+ $real_name = regsubst($name,'^(.*)-(\d|\.)+$','\1')
+ $gem_version = regsubst($name,'^(.*)-(\d+(\d|\.)+)$','\2')
+ } else {
+ $real_name = $name
+ }
+
+ if $source != 'absent' {
+ if $ensure != 'absent' {
+ require rubygems::gem::cachedir
+ exec{"get-gem-$name":
+ command => "/usr/bin/wget -O ${rubygems::gem::cachedir::dir}/$name.gem $source",
+ creates => "${rubygems::gem::cachedir::dir}/$name.gem",
+ }
+ } else {
+ file{"${rubygems::gem::cachedir::dir}/$name.gem":
+ ensure => 'absent';
+ }
+ }
+ }
+
+ if ($buildflags != 'absent') or ($provider == 'exec') {
+ if $gem_version {
+ $gem_version_str = "-v ${gem_version}"
+ $gem_version_check_str = $gem_version
+ } else {
+ $gem_version_check_str = '.*'
+ }
+
+ if $ensure == 'present' {
+ $gem_cmd = 'install'
+ } else {
+ $gem_cmd = 'uninstall -x'
+ }
+
+ if $buildflags != 'absent' {
+ $buildflags_str = "-- --build-flags ${buildflags}"
+ } else {
+ $buildflags_str = ''
+ }
+
+ exec{"manage_gem_${name}":
+ command => "gem ${gem_cmd} ${real_name} ${gem_version_str} ${buildflags_str}",
+ }
+
+ $gem_cmd_check_str = "gem list | egrep -q '^${real_name} \\(${gem_version_check_str}\\)\$'"
+ if $ensure == 'present' {
+ Exec["manage_gem_${name}"]{
+ unless => $gem_cmd_check_str
+ }
+ } else {
+ Exec["manage_gem_${name}"]{
+ onlyif => $gem_cmd_check_str
+ }
+ }
+ } else {
+ package{"$real_name":
+ ensure => $ensure ? {
+ 'absent' => $ensure,
+ default => $gem_version ? {
+ undef => $ensure,
+ default => $gem_version
+ }
+ },
+ provider => gem,
+ }
+ if $source != 'absent' {
+ Package["$name"]{
+ source => "${rubygems::gem::cachedir::dir}/$name.gem"
+ }
+ }
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/gem/cachedir.pp b/puppet/modules/rubygems/manifests/gem/cachedir.pp
new file mode 100644
index 00000000..3e371e42
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/gem/cachedir.pp
@@ -0,0 +1,4 @@
+class rubygems::gem::cachedir {
+ $dir = '/var/lib/puppet/modules/rubygems_cache'
+ modules_dir{'rubygems_cache': }
+}
diff --git a/puppet/modules/rubygems/manifests/gpgme.pp b/puppet/modules/rubygems/manifests/gpgme.pp
new file mode 100644
index 00000000..e9b04a9a
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/gpgme.pp
@@ -0,0 +1,35 @@
+class rubygems::gpgme{
+ case $::operatingsystem {
+ debian,ubuntu: {
+ case $::lsbdistcodename {
+ 'lenny','squeeze': {
+ # install gpgme as gem, as the squeeze deb-package is too old
+ # for i.e. gpg module
+ $provider = 'gem'
+ $packagename = 'ruby-gpgme'
+ }
+ default: {
+ # don't need to install gpgme as gem, debian package works
+ # fine with the gpg module
+ $provider = 'apt'
+ $packagename = 'libgpgme-ruby'
+ }
+ }
+ }
+ default: {
+ $provider = 'gem'
+ $packagename = 'ruby-gpgme'
+ }
+ }
+
+ if $provider == 'gem' {
+ require rubygems::devel
+ require gpg::gpgme::devel
+ }
+
+ package{'ruby-gpgme':
+ ensure => present,
+ provider => $provider,
+ name => $packagename
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/hiera.pp b/puppet/modules/rubygems/manifests/hiera.pp
new file mode 100644
index 00000000..4c766a15
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/hiera.pp
@@ -0,0 +1,7 @@
+class rubygems::hiera{
+ require ::rubygems
+ package{'hiera':
+ ensure => installed,
+ provider => gem,
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/hiera_puppet.pp b/puppet/modules/rubygems/manifests/hiera_puppet.pp
new file mode 100644
index 00000000..319e7d0e
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/hiera_puppet.pp
@@ -0,0 +1,7 @@
+class rubygems::hiera_puppet {
+ require rubygems::hiera
+ package{'hiera-puppet':
+ ensure => installed,
+ provider => gem,
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/highline.pp b/puppet/modules/rubygems/manifests/highline.pp
new file mode 100644
index 00000000..e9da09a5
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/highline.pp
@@ -0,0 +1,14 @@
+class rubygems::highline {
+ require rubygems
+ package{'rubygem-highline':
+ ensure => present,
+ }
+
+ case $::operatingsystem {
+ debian,ubuntu: {
+ Package['rubygem-highline']{
+ name => 'ruby-highline'
+ }
+ }
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/init.pp b/puppet/modules/rubygems/manifests/init.pp
new file mode 100644
index 00000000..bca40b9e
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/init.pp
@@ -0,0 +1,31 @@
+#
+# rubygems module
+# original by luke kanies
+# http://github.com/lak
+#
+# Copyright 2008, Puzzle ITC GmbH
+# Marcel Härry haerry+puppet(at)puzzle.ch
+# Simon Josi josi+puppet(at)puzzle.ch
+#
+# This program is free software; you can redistribute
+# it and/or modify it under the terms of the GNU
+# General Public License version 3 as published by
+# the Free Software Foundation.
+#
+
+# manage rubygems basics
+class rubygems {
+ # from debian 8 on this is not anymore needed as it's part of the ruby pkg
+ if ($::operatingsystem != 'Debian') or (versioncmp($::operatingsystemrelease,'8') < 0) {
+ package{'rubygems':
+ ensure => installed,
+ }
+ }
+ file { '/etc/gemrc':
+ source => [ 'puppet:///modules/site_rubygems/gemrc',
+ 'puppet:///modules/rubygems/gemrc' ],
+ mode => '0644',
+ owner => 'root',
+ group => 'root',
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/ip.pp b/puppet/modules/rubygems/manifests/ip.pp
new file mode 100644
index 00000000..190d869d
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/ip.pp
@@ -0,0 +1,7 @@
+class rubygems::ip {
+ require rubygems
+ package{'ip':
+ ensure => present,
+ provider => gem,
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/json/v_1_4_6.pp b/puppet/modules/rubygems/manifests/json/v_1_4_6.pp
new file mode 100644
index 00000000..d0901ba3
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/json/v_1_4_6.pp
@@ -0,0 +1,3 @@
+class rubygems::json::v_1_4_6 {
+ rubygems::gem{ 'json-1.4.6': }
+}
diff --git a/puppet/modules/rubygems/manifests/lockfile.pp b/puppet/modules/rubygems/manifests/lockfile.pp
new file mode 100644
index 00000000..f4ed6b0f
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/lockfile.pp
@@ -0,0 +1,7 @@
+class rubygems::lockfile {
+ require rubygems
+ package{'lockfile':
+ ensure => present,
+ provider => gem,
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/mail.pp b/puppet/modules/rubygems/manifests/mail.pp
new file mode 100644
index 00000000..b8b50bbe
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/mail.pp
@@ -0,0 +1,19 @@
+# manage the mail rubygem
+class rubygems::mail {
+ if ($::osfamily == 'RedHat') and
+ versioncmp($::operatingsystemrelease,'6') > 0 {
+ package{'rubygem-mail':
+ ensure => present,
+ }
+ } else {
+ require rubygems::devel
+ package{'mail':
+ ensure => present,
+ provider => gem,
+ }
+
+ if $::rubyversion == '1.8.6' {
+ require rubygems::tlsmail
+ }
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/maildir.pp b/puppet/modules/rubygems/manifests/maildir.pp
new file mode 100644
index 00000000..8773f37a
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/maildir.pp
@@ -0,0 +1,15 @@
+# manage maildir rubygem
+class rubygems::maildir {
+ if ($::osfamily == 'RedHat') and
+ versioncmp($::operatingsystemrelease,'6') > 0 {
+ package{'rubygem-maildir':
+ ensure => present,
+ }
+ } else {
+ require rubygems::devel
+ package{'maildir':
+ ensure => present,
+ provider => gem,
+ }
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/markaby.pp b/puppet/modules/rubygems/manifests/markaby.pp
new file mode 100644
index 00000000..817969e3
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/markaby.pp
@@ -0,0 +1,7 @@
+class rubygems::markaby {
+ require rubygems
+ package{'markaby':
+ ensure => present,
+ provider => gem,
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/moneta.pp b/puppet/modules/rubygems/manifests/moneta.pp
new file mode 100644
index 00000000..ea9bb5a6
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/moneta.pp
@@ -0,0 +1,7 @@
+class rubygems::moneta {
+ require rubygems
+ package{'moneta':
+ ensure => present,
+ provider => gem,
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/mysql.pp b/puppet/modules/rubygems/manifests/mysql.pp
new file mode 100644
index 00000000..cc0bbbf6
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/mysql.pp
@@ -0,0 +1,5 @@
+class rubygems::mysql {
+ require ::mysql::devel
+ require gcc
+ rubygems::gem{'mysql':}
+}
diff --git a/puppet/modules/rubygems/manifests/net_ldap/v_0_0_4.pp b/puppet/modules/rubygems/manifests/net_ldap/v_0_0_4.pp
new file mode 100644
index 00000000..88e1e7b4
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/net_ldap/v_0_0_4.pp
@@ -0,0 +1,3 @@
+class rubygems::net_ldap::v_0_0_4 {
+ rubygems::gem{ 'ruby-net-ldap-0.0.4': }
+}
diff --git a/puppet/modules/rubygems/manifests/ntlm/v_0_1_1.pp b/puppet/modules/rubygems/manifests/ntlm/v_0_1_1.pp
new file mode 100644
index 00000000..fd6eade3
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/ntlm/v_0_1_1.pp
@@ -0,0 +1,3 @@
+class rubygems::ntlm::v_0_1_1 {
+ rubygems::gem{ 'rubyntlm-0.1.1': }
+}
diff --git a/puppet/modules/rubygems/manifests/open4.pp b/puppet/modules/rubygems/manifests/open4.pp
new file mode 100644
index 00000000..1e3fbb78
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/open4.pp
@@ -0,0 +1,7 @@
+class rubygems::open4 {
+ require rubygems
+ package{'open4':
+ ensure => present,
+ provider => gem,
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/pbkdf2.pp b/puppet/modules/rubygems/manifests/pbkdf2.pp
new file mode 100644
index 00000000..b2cf1136
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/pbkdf2.pp
@@ -0,0 +1,8 @@
+class rubygems::pbkdf2{
+ require ::rubygems
+ package{'pbkdf2':
+ ensure => installed,
+ provider => gem,
+ }
+}
+
diff --git a/puppet/modules/rubygems/manifests/postgres.pp b/puppet/modules/rubygems/manifests/postgres.pp
new file mode 100644
index 00000000..8720f4ef
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/postgres.pp
@@ -0,0 +1,11 @@
+class rubygems::postgres {
+ if $::osfamily == 'RedHat' and
+ versioncmp($::operatingsystemrelease,'5') > 0 {
+ package{'rubygem-pg':
+ ensure => installed,
+ }
+ } else {
+ require postgres::devel
+ rubygems::gem{'ruby-pg':}
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/rack.pp b/puppet/modules/rubygems/manifests/rack.pp
new file mode 100644
index 00000000..953ab22b
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/rack.pp
@@ -0,0 +1,7 @@
+class rubygems::rack {
+ require rubygems
+ package{'rack':
+ ensure => present,
+ provider => gem,
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/sinatra.pp b/puppet/modules/rubygems/manifests/sinatra.pp
new file mode 100644
index 00000000..327f829f
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/sinatra.pp
@@ -0,0 +1,7 @@
+class rubygems::sinatra {
+ require rubygems
+ package{'sinatra':
+ ensure => present,
+ provider => gem,
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/sqlite.pp b/puppet/modules/rubygems/manifests/sqlite.pp
new file mode 100644
index 00000000..6b670152
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/sqlite.pp
@@ -0,0 +1,6 @@
+class rubygems::sqlite {
+ require rubygems::devel
+ package{'rubygem-sqlite3-ruby':
+ ensure => present,
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/systemu.pp b/puppet/modules/rubygems/manifests/systemu.pp
new file mode 100644
index 00000000..62a599cf
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/systemu.pp
@@ -0,0 +1,7 @@
+class rubygems::systemu {
+ require rubygems
+ package{'systemu':
+ ensure => present,
+ provider => gem,
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/thin.pp b/puppet/modules/rubygems/manifests/thin.pp
new file mode 100644
index 00000000..b2499d81
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/thin.pp
@@ -0,0 +1,7 @@
+class rubygems::thin {
+ require rubygems
+ package{'thin':
+ ensure => present,
+ provider => gem,
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/tlsmail.pp b/puppet/modules/rubygems/manifests/tlsmail.pp
new file mode 100644
index 00000000..71aa6158
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/tlsmail.pp
@@ -0,0 +1,7 @@
+class rubygems::tlsmail {
+ require rubygems::devel
+ package{'tlsmail':
+ ensure => present,
+ provider => gem,
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/tmail.pp b/puppet/modules/rubygems/manifests/tmail.pp
new file mode 100644
index 00000000..dd7117d9
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/tmail.pp
@@ -0,0 +1,7 @@
+class rubygems::tmail {
+ require rubygems::devel
+ package{'tmail':
+ ensure => present,
+ provider => gem,
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/xmlsimple.pp b/puppet/modules/rubygems/manifests/xmlsimple.pp
new file mode 100644
index 00000000..914156b0
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/xmlsimple.pp
@@ -0,0 +1,20 @@
+# xml simple lib
+class rubygems::xmlsimple {
+ package{'rubygem-xml-simple':
+ ensure => present,
+ }
+ case $::operatingsystem {
+ debian,ubuntu: {
+ Package['rubygem-xml-simple']{
+ name => 'libxml-simple-ruby'
+ }
+ }
+ }
+ if $::operatingsystem == 'CentOS' and versioncmp($::operatingsystemrelease, '6') > 0 {
+ # not yet packaged
+ Package['rubygem-xml-simple']{
+ name => 'xml-simple',
+ provider => gem,
+ }
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/xmpp4r.pp b/puppet/modules/rubygems/manifests/xmpp4r.pp
new file mode 100644
index 00000000..068d5825
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/xmpp4r.pp
@@ -0,0 +1,7 @@
+class rubygems::xmpp4r {
+ require ::rubygems
+ package{'xmpp4r':
+ ensure => present,
+ provider => gem,
+ }
+}
diff --git a/puppet/modules/rubygems/manifests/ya2yaml.pp b/puppet/modules/rubygems/manifests/ya2yaml.pp
new file mode 100644
index 00000000..7df362dc
--- /dev/null
+++ b/puppet/modules/rubygems/manifests/ya2yaml.pp
@@ -0,0 +1,7 @@
+class rubygems::ya2yaml {
+ require rubygems
+ package{'ya2yaml':
+ ensure => present,
+ provider => gem,
+ }
+}
diff --git a/puppet/modules/shorewall/LICENSE b/puppet/modules/shorewall/LICENSE
new file mode 100644
index 00000000..94a9ed02
--- /dev/null
+++ b/puppet/modules/shorewall/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/puppet/modules/shorewall/README b/puppet/modules/shorewall/README
new file mode 100644
index 00000000..3a84b3bd
--- /dev/null
+++ b/puppet/modules/shorewall/README
@@ -0,0 +1,219 @@
+modules/shorewall/manifests/init.pp - manage firewalling with shorewall 3.x
+
+Puppet Module for Shorewall
+---------------------------
+This module manages the configuration of Shorewall (http://www.shorewall.net/)
+
+Requirements
+------------
+
+This module requires the augeas module, you can find that here:
+https://labs.riseup.net/code/projects/shared-augeas
+
+Copyright
+---------
+
+Copyright (C) 2007 David Schmitt <david@schmitt.edv-bus.at>
+adapted by immerda project group - admin+puppet(at)immerda.ch
+adapted by Puzzle ITC - haerry+puppet(at)puzzle.ch
+Copyright (c) 2009 Riseup Networks - micah(shift+2)riseup.net
+Copyright (c) 2010 intrigeri - intrigeri(at)boum.org
+See LICENSE for the full license granted to you.
+
+Based on the work of ADNET Ghislain <gadnet@aqueos.com> from AQUEOS
+at https://reductivelabs.com/trac/puppet/wiki/AqueosShorewall
+
+Merged from:
+- git://git.puppet.immerda.ch/module-shorewall.git
+- git://labs.riseup.net/module_shorewall
+
+Todo
+----
+- check if shorewall compiles without errors, otherwise fail !
+
+Configuration
+-------------
+
+If you need to install a specific version of shorewall other than
+the default one that would be installed by 'ensure => present', then
+you can set the following variable and that specific version will be
+installed instead:
+
+ $shorewall_ensure_version = "4.0.15-1"
+
+The main shorewall.conf is not managed by this module, rather the default one
+that your operatingsystem provides is used, and any modifications you wish to do
+to it should be configured with augeas, for example, to set IP_FORWARDING=Yes in
+shorewall.conf, simply do this:
+
+ augeas { 'enable_ip_forwarding':
+ changes => 'set /files/etc/shorewall/shorewall.conf/IP_FORWARDING Yes',
+ lens => 'Shellvars.lns',
+ incl => '/etc/shorewall/shorewall.conf',
+ notify => Service[shorewall];
+ }
+
+NOTE: this requires the augeas ruby bindings newer than 0.7.3.
+
+If you need to, you can provide an entire shorewall.conf by passing its
+source to the main class:
+
+class{'shorewall':
+ conf_source => "puppet:///modules/site_shorewall/${::fqdn}/shorewall.conf.${::operatingsystem}",
+}
+
+NOTE: if you distribute a file, you cannot also use augeas, puppet and augeas
+will fight forever. Secondly, you will *need* to make sure that if you are shipping your own
+shorewall.conf that you have the following value set in your shorewall.conf otherwise this
+module will not work:
+
+ CONFIG_PATH="/etc/shorewall/puppet:/etc/shorewall:/usr/share/shorewall"
+
+Documentation
+-------------
+
+see also: http://reductivelabs.com/trac/puppet/wiki/Recipes/AqueosShorewall
+
+Torify
+------
+
+The shorewall::rules::torify define can be used to force some outgoing
+TCP traffic through the Tor transparent proxy. The corresponding
+non-TCP traffic is rejected accordingly.
+
+Beware! This define only is part of a torified setup. DNS requests and
+IPv6, amongst others, might leak network activity you would prefer not
+to. You really need to read proper documentation about these matters
+before using this feature e.g.:
+
+ https://www.torproject.org/download/download.html.en#warning
+
+The Tor transparent proxy location defaults to 127.0.0.1:9040 and can
+be configured by setting the $tor_transparent_proxy_host and
+$tor_transparent_proxy_port variables before including the main
+shorewall class.
+
+Example usage follows.
+
+Torify any outgoing TCP traffic originating from user bob or alice and
+aimed at 6.6.6.6 or 7.7.7.7:
+
+ shorewall::rules::torify {
+ 'torify-some-bits':
+ users => [ 'bob', 'alice' ],
+ destinations => [ '6.6.6.6', '7.7.7.7' ];
+ }
+
+Torify any outgoing TCP traffic to 8.8.8.8:
+
+ shorewall::rules::torify {
+ 'torify-to-this-host':
+ destinations => [ '8.8.8.8' ];
+ }
+
+When no destination nor user is provided any outgoing TCP traffic (see
+restrictions bellow) is torified. In that case the user running the
+Tor client ($tor_user) is whitelisted; this variable defaults to
+"debian-tor" on Debian systems and to "tor" on others. if this does
+not suit your configuration you need to set the $tor_user variable
+before including the main shorewall class.
+
+When no destination is provided traffic directed to RFC1918 addresses
+is by default allowed and (obviously) not torified. This behaviour can
+be changed by setting the allow_rfc1918 parameter to false.
+
+Torify any outgoing TCP traffic but connections to RFC1918 addresses:
+
+ shorewall::rules::torify {
+ 'torify-everything-but-lan':
+ }
+
+Torify any outgoing TCP traffic:
+
+ shorewall::rules::torify {
+ 'torify-everything:
+ allow_rfc1918 => false;
+ }
+
+In some cases (e.g. when providing no specific destination nor user
+and denying access to RFC1918 addresses) UDP DNS requests may be
+rejected. This is intentional: it does not make sense leaking -via DNS
+requests- network activity that would otherwise be torified. In that
+case you probably want to read proper documentation about such
+matters, enable the Tor DNS resolver and redirect DNS requests through
+it.
+
+Example
+-------
+
+Example from node.pp:
+
+node xy {
+ class{'config::site_shorewall':
+ startup => "0" # create shorewall ruleset but don't startup
+ }
+ shorewall::rule {
+ 'incoming-ssh': source => 'all', destination => '$FW', action => 'SSH(ACCEPT)', order => 200;
+ 'incoming-puppetmaster': source => 'all', destination => '$FW', action => 'Puppetmaster(ACCEPT)', order => 300;
+ 'incoming-imap': source => 'all', destination => '$FW', action => 'IMAP(ACCEPT)', order => 300;
+ 'incoming-smtp': source => 'all', destination => '$FW', action => 'SMTP(ACCEPT)', order => 300;
+ }
+}
+
+
+class config::site_shorewall($startup = '1') {
+ class{'shorewall':
+ startup => $startup
+ }
+
+ # If you want logging:
+ #shorewall::params {
+ # 'LOG': value => 'debug';
+ #}
+
+ shorewall::zone {'net':
+ type => 'ipv4';
+ }
+
+ shorewall::rule_section { 'NEW':
+ order => 100;
+ }
+
+ shorewall::interface { 'eth0':
+ zone => 'net',
+ rfc1918 => true,
+ options => 'tcpflags,blacklist,nosmurfs';
+ }
+
+ shorewall::policy {
+ 'fw-to-fw':
+ sourcezone => '$FW',
+ destinationzone => '$FW',
+ policy => 'ACCEPT',
+ order => 100;
+ 'fw-to-net':
+ sourcezone => '$FW',
+ destinationzone => 'net',
+ policy => 'ACCEPT',
+ shloglevel => '$LOG',
+ order => 110;
+ 'net-to-fw':
+ sourcezone => 'net',
+ destinationzone => '$FW',
+ policy => 'DROP',
+ shloglevel => '$LOG',
+ order => 120;
+ }
+
+
+ # default Rules : ICMP
+ shorewall::rule {
+ 'allicmp-to-host':
+ source => 'all',
+ destination => '$FW',
+ order => 200,
+ action => 'AllowICMPs/(ACCEPT)';
+ }
+}
+
+
diff --git a/puppet/modules/shorewall/files/boilerplate/blacklist.footer b/puppet/modules/shorewall/files/boilerplate/blacklist.footer
new file mode 100644
index 00000000..5e12d1da
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/blacklist.footer
@@ -0,0 +1 @@
+#LAST LINE -- ADD YOUR ENTRIES BEFORE THIS ONE -- DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/blacklist.header b/puppet/modules/shorewall/files/boilerplate/blacklist.header
new file mode 100644
index 00000000..2392e176
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/blacklist.header
@@ -0,0 +1,10 @@
+#
+# Shorewall version 3.4 - Blacklist File
+#
+# For information about entries in this file, type "man shorewall-blacklist"
+#
+# Please see http://shorewall.net/blacklisting_support.htm for additional
+# information.
+#
+###############################################################################
+#ADDRESS/SUBNET PROTOCOL PORT
diff --git a/puppet/modules/shorewall/files/boilerplate/clear.footer b/puppet/modules/shorewall/files/boilerplate/clear.footer
new file mode 100644
index 00000000..662ac1cc
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/clear.footer
@@ -0,0 +1 @@
+#LAST LINE - ADD YOUR ENTRIES ABOVE THIS ONE - DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/clear.header b/puppet/modules/shorewall/files/boilerplate/clear.header
new file mode 100644
index 00000000..6a39b0b6
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/clear.header
@@ -0,0 +1,13 @@
+#
+# Shorewall version 4 - Clear
+#
+# /etc/shorewall/stop
+#
+# Add commands below that you want to be executed at the beginning of a
+# "shorewall stop" command.
+#
+# See http://shorewall.net/shorewall_extension_scripts.htm for additional
+# information.
+#
+###############################################################################
+#LAST LINE - ADD YOUR ENTRIES ABOVE THIS ONE - DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/continue.footer b/puppet/modules/shorewall/files/boilerplate/continue.footer
new file mode 100644
index 00000000..662ac1cc
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/continue.footer
@@ -0,0 +1 @@
+#LAST LINE - ADD YOUR ENTRIES ABOVE THIS ONE - DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/continue.header b/puppet/modules/shorewall/files/boilerplate/continue.header
new file mode 100644
index 00000000..d2ee48a5
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/continue.header
@@ -0,0 +1,14 @@
+#
+# Shorewall version 4 - Continue File
+#
+# /etc/shorewall/continue
+#
+# Add commands below that you want to be executed after shorewall has
+# cleared any existing Netfilter rules and has enabled existing
+# connections.
+#
+# For additional information, see
+# http://shorewall.net/shorewall_extension_scripts.htm
+#
+###############################################################################
+
diff --git a/puppet/modules/shorewall/files/boilerplate/hosts.footer b/puppet/modules/shorewall/files/boilerplate/hosts.footer
new file mode 100644
index 00000000..dc2fef52
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/hosts.footer
@@ -0,0 +1 @@
+#LAST LINE -- ADD YOUR ENTRIES BEFORE THIS LINE -- DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/hosts.header b/puppet/modules/shorewall/files/boilerplate/hosts.header
new file mode 100644
index 00000000..e39d6145
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/hosts.header
@@ -0,0 +1,9 @@
+#
+# Shorewall version 3.4 - Hosts file
+#
+# For information about entries in this file, type "man shorewall-hosts"
+#
+# For additional information, see http://shorewall.net/Documentation.htm#Hosts
+#
+###############################################################################
+#ZONE HOST(S) OPTIONS
diff --git a/puppet/modules/shorewall/files/boilerplate/init.footer b/puppet/modules/shorewall/files/boilerplate/init.footer
new file mode 100644
index 00000000..662ac1cc
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/init.footer
@@ -0,0 +1 @@
+#LAST LINE - ADD YOUR ENTRIES ABOVE THIS ONE - DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/init.header b/puppet/modules/shorewall/files/boilerplate/init.header
new file mode 100644
index 00000000..cbb0393e
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/init.header
@@ -0,0 +1,13 @@
+#
+# Shorewall version 4 - Init File
+#
+# /etc/shorewall/init
+#
+# Add commands below that you want to be executed at the beginning of
+# a "shorewall start" or "shorewall restart" command.
+#
+# For additional information, see
+# http://shorewall.net/shorewall_extension_scripts.htm
+#
+###############################################################################
+
diff --git a/puppet/modules/shorewall/files/boilerplate/initdone.footer b/puppet/modules/shorewall/files/boilerplate/initdone.footer
new file mode 100644
index 00000000..662ac1cc
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/initdone.footer
@@ -0,0 +1 @@
+#LAST LINE - ADD YOUR ENTRIES ABOVE THIS ONE - DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/initdone.header b/puppet/modules/shorewall/files/boilerplate/initdone.header
new file mode 100644
index 00000000..9252a3bc
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/initdone.header
@@ -0,0 +1,14 @@
+#
+# Shorewall version 4 - Initdone File
+#
+# /etc/shorewall/initdone
+#
+# Add commands below that you want to be executed during
+# "shorewall start" or "shorewall restart" commands at the point where
+# Shorewall has not yet added any perminent rules to the builtin chains.
+#
+# For additional information, see
+# http://shorewall.net/shorewall_extension_scripts.htm
+#
+###############################################################################
+
diff --git a/puppet/modules/shorewall/files/boilerplate/interfaces.footer b/puppet/modules/shorewall/files/boilerplate/interfaces.footer
new file mode 100644
index 00000000..5e12d1da
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/interfaces.footer
@@ -0,0 +1 @@
+#LAST LINE -- ADD YOUR ENTRIES BEFORE THIS ONE -- DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/interfaces.header b/puppet/modules/shorewall/files/boilerplate/interfaces.header
new file mode 100644
index 00000000..2027523e
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/interfaces.header
@@ -0,0 +1,10 @@
+#
+# Shorewall version 3.4 - Interfaces File
+#
+# For information about entries in this file, type "man shorewall-interfaces"
+#
+# For additional information, see
+# http://shorewall.net/Documentation.htm#Interfaces
+#
+###############################################################################
+#ZONE INTERFACE BROADCAST OPTIONS
diff --git a/puppet/modules/shorewall/files/boilerplate/maclog.footer b/puppet/modules/shorewall/files/boilerplate/maclog.footer
new file mode 100644
index 00000000..5e12d1da
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/maclog.footer
@@ -0,0 +1 @@
+#LAST LINE -- ADD YOUR ENTRIES BEFORE THIS ONE -- DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/maclog.header b/puppet/modules/shorewall/files/boilerplate/maclog.header
new file mode 100644
index 00000000..b0c382ab
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/maclog.header
@@ -0,0 +1,14 @@
+#
+# Shorewall version 4 - Maclog File (Added in Shorewall version 3.2.5)
+#
+# /etc/shorewall/start
+#
+# Add commands below that you want executed while mac filtering rules are
+# being created. These will be executed once for each interface having
+# 'maclist' speciied and it is invoked just before the logging rule is
+# added to the current chain (the name of that chain will be in $CHAIN)
+#
+# See http://shorewall.net/shorewall_extension_scripts.htm for additional
+# information.
+#
+###############################################################################
diff --git a/puppet/modules/shorewall/files/boilerplate/mangle.footer b/puppet/modules/shorewall/files/boilerplate/mangle.footer
new file mode 100644
index 00000000..6bebc05c
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/mangle.footer
@@ -0,0 +1 @@
+#LAST LINE -- ADD YOUR ENTRIES ABOVE THIS LINE -- DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/mangle.header b/puppet/modules/shorewall/files/boilerplate/mangle.header
new file mode 100644
index 00000000..7a7b12ab
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/mangle.header
@@ -0,0 +1,7 @@
+#
+# Shorewall - Mangle File
+#
+# For additional information, see http://shorewall.net/manpages/shorewall-mangle.html
+#
+#######################################################################################
+#ACTION SOURCE DESTINATION PROTO DSTPORT SRCPORT USER TEST LENGTH TOS CONNBYTES HELPER HEADERS
diff --git a/puppet/modules/shorewall/files/boilerplate/masq.footer b/puppet/modules/shorewall/files/boilerplate/masq.footer
new file mode 100644
index 00000000..6bebc05c
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/masq.footer
@@ -0,0 +1 @@
+#LAST LINE -- ADD YOUR ENTRIES ABOVE THIS LINE -- DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/masq.header b/puppet/modules/shorewall/files/boilerplate/masq.header
new file mode 100644
index 00000000..f8233210
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/masq.header
@@ -0,0 +1,9 @@
+#
+# Shorewall version 3.4 - Masq file
+#
+# For information about entries in this file, type "man shorewall-masq"
+#
+# For additional information, see http://shorewall.net/Documentation.htm#Masq
+#
+###############################################################################
+#INTERFACE SOURCE ADDRESS PROTO PORT(S) IPSEC MARK
diff --git a/puppet/modules/shorewall/files/boilerplate/nat.footer b/puppet/modules/shorewall/files/boilerplate/nat.footer
new file mode 100644
index 00000000..6bebc05c
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/nat.footer
@@ -0,0 +1 @@
+#LAST LINE -- ADD YOUR ENTRIES ABOVE THIS LINE -- DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/nat.header b/puppet/modules/shorewall/files/boilerplate/nat.header
new file mode 100644
index 00000000..c2e0d922
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/nat.header
@@ -0,0 +1,9 @@
+#
+# Shorewall version 3.4 - Nat File
+#
+# For information about entries in this file, type "man shorewall-nat"
+#
+# For additional information, see http://shorewall.net/NAT.htm
+#
+###############################################################################
+#EXTERNAL INTERFACE INTERNAL ALL LOCAL
diff --git a/puppet/modules/shorewall/files/boilerplate/params.footer b/puppet/modules/shorewall/files/boilerplate/params.footer
new file mode 100644
index 00000000..662ac1cc
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/params.footer
@@ -0,0 +1 @@
+#LAST LINE - ADD YOUR ENTRIES ABOVE THIS ONE - DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/params.header b/puppet/modules/shorewall/files/boilerplate/params.header
new file mode 100644
index 00000000..b258b0de
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/params.header
@@ -0,0 +1,26 @@
+#
+# Shorewall version 3.4 - Params File
+#
+# /etc/shorewall/params
+#
+# Assign any variables that you need here.
+#
+# It is suggested that variable names begin with an upper case letter
+# to distinguish them from variables used internally within the
+# Shorewall programs
+#
+# Example:
+#
+# NET_IF=eth0
+# NET_BCAST=130.252.100.255
+# NET_OPTIONS=routefilter,norfc1918
+#
+# Example (/etc/shorewall/interfaces record):
+#
+# net $NET_IF $NET_BCAST $NET_OPTIONS
+#
+# The result will be the same as if the record had been written
+#
+# net eth0 130.252.100.255 routefilter,norfc1918
+#
+###############################################################################
diff --git a/puppet/modules/shorewall/files/boilerplate/policy.footer b/puppet/modules/shorewall/files/boilerplate/policy.footer
new file mode 100644
index 00000000..16c86d0e
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/policy.footer
@@ -0,0 +1 @@
+#LAST LINE -- DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/policy.header b/puppet/modules/shorewall/files/boilerplate/policy.header
new file mode 100644
index 00000000..a0c5d5d2
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/policy.header
@@ -0,0 +1,9 @@
+#
+# Shorewall version 3.4 - Policy File
+#
+# For information about entries in this file, type "man shorewall-policy"
+#
+# See http://shorewall.net/Documentation.htm#Policy for additional information.
+#
+###############################################################################
+#SOURCE DEST POLICY LOG LIMIT:BURST
diff --git a/puppet/modules/shorewall/files/boilerplate/providers.footer b/puppet/modules/shorewall/files/boilerplate/providers.footer
new file mode 100644
index 00000000..5e12d1da
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/providers.footer
@@ -0,0 +1 @@
+#LAST LINE -- ADD YOUR ENTRIES BEFORE THIS ONE -- DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/providers.header b/puppet/modules/shorewall/files/boilerplate/providers.header
new file mode 100644
index 00000000..b4a5990f
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/providers.header
@@ -0,0 +1,9 @@
+#
+# Shorewall version 4 - Providers File
+#
+# For information about entries in this file, type "man shorewall-providers"
+#
+# For additional information, see http://shorewall.net/MultiISP.html
+#
+############################################################################################
+#NAME NUMBER MARK DUPLICATE INTERFACE GATEWAY OPTIONS COPY
diff --git a/puppet/modules/shorewall/files/boilerplate/proxyarp.footer b/puppet/modules/shorewall/files/boilerplate/proxyarp.footer
new file mode 100644
index 00000000..5e12d1da
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/proxyarp.footer
@@ -0,0 +1 @@
+#LAST LINE -- ADD YOUR ENTRIES BEFORE THIS ONE -- DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/proxyarp.header b/puppet/modules/shorewall/files/boilerplate/proxyarp.header
new file mode 100644
index 00000000..1e168532
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/proxyarp.header
@@ -0,0 +1,9 @@
+#
+# Shorewall version 3.4 - Proxyarp File
+#
+# For information about entries in this file, type "man shorewall-proxyarp"
+#
+# See http://shorewall.net/ProxyARP.htm for additional information.
+#
+###############################################################################
+#ADDRESS INTERFACE EXTERNAL HAVEROUTE PERSISTENT
diff --git a/puppet/modules/shorewall/files/boilerplate/rfc1918.footer b/puppet/modules/shorewall/files/boilerplate/rfc1918.footer
new file mode 100644
index 00000000..e07fdb15
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/rfc1918.footer
@@ -0,0 +1,5 @@
+# The real subnets from RFC1918
+172.16.0.0/12 logdrop # RFC 1918
+192.168.0.0/16 logdrop # RFC 1918
+10.0.0.0/8 logdrop # RFC 1918
+#LAST LINE -- ADD YOUR ENTRIES BEFORE THIS ONE -- DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/rfc1918.header b/puppet/modules/shorewall/files/boilerplate/rfc1918.header
new file mode 100644
index 00000000..8d6a4162
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/rfc1918.header
@@ -0,0 +1,5 @@
+#
+# Shorewall version 3.4 - Rfc1918 File
+#
+###############################################################################
+#SUBNETS TARGET
diff --git a/puppet/modules/shorewall/files/boilerplate/routestopped.footer b/puppet/modules/shorewall/files/boilerplate/routestopped.footer
new file mode 100644
index 00000000..5e12d1da
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/routestopped.footer
@@ -0,0 +1 @@
+#LAST LINE -- ADD YOUR ENTRIES BEFORE THIS ONE -- DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/routestopped.header b/puppet/modules/shorewall/files/boilerplate/routestopped.header
new file mode 100644
index 00000000..5408aace
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/routestopped.header
@@ -0,0 +1,11 @@
+#
+# Shorewall version 3.4 - Routestopped File
+#
+# For information about entries in this file, type "man shorewall-routestopped"
+#
+# See http://shorewall.net/Documentation.htm#Routestopped and
+# http://shorewall.net/starting_and_stopping_shorewall.htm for additional
+# information.
+#
+###############################################################################
+#INTERFACE HOST(S) OPTIONS
diff --git a/puppet/modules/shorewall/files/boilerplate/rtrules.footer b/puppet/modules/shorewall/files/boilerplate/rtrules.footer
new file mode 100644
index 00000000..5e12d1da
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/rtrules.footer
@@ -0,0 +1 @@
+#LAST LINE -- ADD YOUR ENTRIES BEFORE THIS ONE -- DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/rtrules.header b/puppet/modules/shorewall/files/boilerplate/rtrules.header
new file mode 100644
index 00000000..fd9b2f48
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/rtrules.header
@@ -0,0 +1,8 @@
+#
+# Shorewall version 4 - route rules File
+#
+# For information about entries in this file, type "man shorewall-rtrules"
+#
+# For additional information, see http://www.shorewall.net/MultiISP.html
+####################################################################################
+# SOURCE DEST PROVIDER PRIORITY MASK
diff --git a/puppet/modules/shorewall/files/boilerplate/rules.footer b/puppet/modules/shorewall/files/boilerplate/rules.footer
new file mode 100644
index 00000000..5e12d1da
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/rules.footer
@@ -0,0 +1 @@
+#LAST LINE -- ADD YOUR ENTRIES BEFORE THIS ONE -- DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/rules.header b/puppet/modules/shorewall/files/boilerplate/rules.header
new file mode 100644
index 00000000..764358ac
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/rules.header
@@ -0,0 +1,10 @@
+#
+# Shorewall version 3.4 - Rules File
+#
+# For information on the settings in this file, type "man shorewall-rules"
+#
+# See http://shorewall.net/Documentation.htm#Rules for additional information.
+#
+#############################################################################################################
+#ACTION SOURCE DEST PROTO DEST SOURCE ORIGINAL RATE USER/ MARK
+# PORT PORT(S) DEST LIMIT GROUP
diff --git a/puppet/modules/shorewall/files/boilerplate/start.footer b/puppet/modules/shorewall/files/boilerplate/start.footer
new file mode 100644
index 00000000..5e12d1da
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/start.footer
@@ -0,0 +1 @@
+#LAST LINE -- ADD YOUR ENTRIES BEFORE THIS ONE -- DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/start.header b/puppet/modules/shorewall/files/boilerplate/start.header
new file mode 100644
index 00000000..689dff19
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/start.header
@@ -0,0 +1,12 @@
+#
+# Shorewall version 4 - Start File
+#
+# /etc/shorewall/start
+#
+# Add commands below that you want to be executed after shorewall has
+# been started or restarted.
+#
+# See http://shorewall.net/shorewall_extension_scripts.htm for additional
+# information.
+#
+###############################################################################
diff --git a/puppet/modules/shorewall/files/boilerplate/started.footer b/puppet/modules/shorewall/files/boilerplate/started.footer
new file mode 100644
index 00000000..5e12d1da
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/started.footer
@@ -0,0 +1 @@
+#LAST LINE -- ADD YOUR ENTRIES BEFORE THIS ONE -- DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/started.header b/puppet/modules/shorewall/files/boilerplate/started.header
new file mode 100644
index 00000000..b7704dba
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/started.header
@@ -0,0 +1,20 @@
+#
+# Shorewall version 4 - Started File
+#
+# /etc/shorewall/started
+#
+# Add commands below that you want to be executed after shorewall has
+# been completely started or restarted. The difference between this
+# extension script and /etc/shorewall/start is that this one is invoked
+# after delayed loading of the blacklist (DELAYBLACKLISTLOAD=Yes) and
+# after the 'shorewall' chain has been created (thus signaling that the
+# firewall is completely up).
+#
+# This script should not change the firewall configuration directly but
+# may do so indirectly by running /sbin/shorewall with the 'nolock'
+# option.
+#
+# See http://shorewall.net/shorewall_extension_scripts.htm for additional
+# information.
+#
+###############################################################################
diff --git a/puppet/modules/shorewall/files/boilerplate/stop.footer b/puppet/modules/shorewall/files/boilerplate/stop.footer
new file mode 100644
index 00000000..5e12d1da
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/stop.footer
@@ -0,0 +1 @@
+#LAST LINE -- ADD YOUR ENTRIES BEFORE THIS ONE -- DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/stop.header b/puppet/modules/shorewall/files/boilerplate/stop.header
new file mode 100644
index 00000000..0088abe1
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/stop.header
@@ -0,0 +1,13 @@
+#
+# Shorewall version 4 - Stop File
+#
+# /etc/shorewall/stop
+#
+# Add commands below that you want to be executed at the beginning of a
+# "shorewall stop" command.
+#
+# See http://shorewall.net/shorewall_extension_scripts.htm for additional
+# information.
+#
+###############################################################################
+#LAST LINE - ADD YOUR ENTRIES ABOVE THIS ONE - DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/stopped.footer b/puppet/modules/shorewall/files/boilerplate/stopped.footer
new file mode 100644
index 00000000..5e12d1da
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/stopped.footer
@@ -0,0 +1 @@
+#LAST LINE -- ADD YOUR ENTRIES BEFORE THIS ONE -- DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/stopped.header b/puppet/modules/shorewall/files/boilerplate/stopped.header
new file mode 100644
index 00000000..438e5e05
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/stopped.header
@@ -0,0 +1,13 @@
+#
+# Shorewall version 4 - Stopped File
+#
+# /etc/shorewall/stopped
+#
+# Add commands below that you want to be executed at the completion of a
+# "shorewall stop" command.
+#
+# See http://shorewall.net/shorewall_extension_scripts.htm for additional
+# information.
+#
+###############################################################################
+#LAST LINE - ADD YOUR ENTRIES ABOVE THIS ONE - DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/tcclasses.footer b/puppet/modules/shorewall/files/boilerplate/tcclasses.footer
new file mode 100644
index 00000000..5e12d1da
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/tcclasses.footer
@@ -0,0 +1 @@
+#LAST LINE -- ADD YOUR ENTRIES BEFORE THIS ONE -- DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/tcclasses.header b/puppet/modules/shorewall/files/boilerplate/tcclasses.header
new file mode 100644
index 00000000..025415ba
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/tcclasses.header
@@ -0,0 +1,9 @@
+#
+# Shorewall version 4 - Tcclasses File
+#
+# For information about entries in this file, type "man shorewall-tcclasses"
+#
+# See http://shorewall.net/traffic_shaping.htm for additional information.
+#
+###############################################################################
+#INTERFACE:CLASS MARK RATE CEIL PRIORITY OPTIONS
diff --git a/puppet/modules/shorewall/files/boilerplate/tcdevices.footer b/puppet/modules/shorewall/files/boilerplate/tcdevices.footer
new file mode 100644
index 00000000..5e12d1da
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/tcdevices.footer
@@ -0,0 +1 @@
+#LAST LINE -- ADD YOUR ENTRIES BEFORE THIS ONE -- DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/tcdevices.header b/puppet/modules/shorewall/files/boilerplate/tcdevices.header
new file mode 100644
index 00000000..fe7c3d1f
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/tcdevices.header
@@ -0,0 +1,10 @@
+#
+# Shorewall version 4 - Tcdevices File
+#
+# For information about entries in this file, type "man shorewall-tcdevices"
+#
+# See http://shorewall.net/traffic_shaping.htm for additional information.
+#
+###############################################################################
+#NUMBER: IN-BANDWITH OUT-BANDWIDTH OPTIONS REDIRECTED
+#INTERFACE INTERFACES
diff --git a/puppet/modules/shorewall/files/boilerplate/tcrules.footer b/puppet/modules/shorewall/files/boilerplate/tcrules.footer
new file mode 100644
index 00000000..5e12d1da
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/tcrules.footer
@@ -0,0 +1 @@
+#LAST LINE -- ADD YOUR ENTRIES BEFORE THIS ONE -- DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/tcrules.header b/puppet/modules/shorewall/files/boilerplate/tcrules.header
new file mode 100644
index 00000000..e0e7adcf
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/tcrules.header
@@ -0,0 +1,15 @@
+#
+# Shorewall version 4 - Tcrules File
+#
+# For information about entries in this file, type "man shorewall-tcrules"
+#
+# See http://shorewall.net/traffic_shaping.htm for additional information.
+# For usage in selecting among multiple ISPs, see
+# http://shorewall.net/MultiISP.html
+#
+# See http://shorewall.net/PacketMarking.html for a detailed description of
+# the Netfilter/Shorewall packet marking mechanism.
+######################################################################################################################
+#MARK SOURCE DEST PROTO DEST SOURCE USER TEST LENGTH TOS CONNBYTES HELPER
+# PORT(S) PORT(S)
+
diff --git a/puppet/modules/shorewall/files/boilerplate/tunnel.footer b/puppet/modules/shorewall/files/boilerplate/tunnel.footer
new file mode 100644
index 00000000..5e12d1da
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/tunnel.footer
@@ -0,0 +1 @@
+#LAST LINE -- ADD YOUR ENTRIES BEFORE THIS ONE -- DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/tunnel.header b/puppet/modules/shorewall/files/boilerplate/tunnel.header
new file mode 100644
index 00000000..638fd568
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/tunnel.header
@@ -0,0 +1,11 @@
+#
+# Shorewall version 4 - Tunnels File
+#
+# For information about entries in this file, type "man shorewall-tunnels"
+#
+# The manpage is also online at
+# http://www.shorewall.net/manpages/shorewall-tunnels.html
+#
+###############################################################################
+#TYPE ZONE GATEWAY GATEWAY
+# ZONE
diff --git a/puppet/modules/shorewall/files/boilerplate/zones.footer b/puppet/modules/shorewall/files/boilerplate/zones.footer
new file mode 100644
index 00000000..662ac1cc
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/zones.footer
@@ -0,0 +1 @@
+#LAST LINE - ADD YOUR ENTRIES ABOVE THIS ONE - DO NOT REMOVE
diff --git a/puppet/modules/shorewall/files/boilerplate/zones.header b/puppet/modules/shorewall/files/boilerplate/zones.header
new file mode 100644
index 00000000..8b82c2e5
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/zones.header
@@ -0,0 +1,11 @@
+#
+# Shorewall version 3.4 - Zones File
+#
+# For information about this file, type "man shorewall-zones"
+#
+# For more information, see http://www.shorewall.net/Documentation.htm#Zones
+#
+###############################################################################
+#ZONE TYPE OPTIONS IN OUT
+# OPTIONS OPTIONS
+fw firewall
diff --git a/puppet/modules/shorewall/files/empty/.ignore b/puppet/modules/shorewall/files/empty/.ignore
new file mode 100644
index 00000000..89cb1fe9
--- /dev/null
+++ b/puppet/modules/shorewall/files/empty/.ignore
@@ -0,0 +1 @@
+# file needed for git - don't remove it
diff --git a/puppet/modules/shorewall/manifests/base.pp b/puppet/modules/shorewall/manifests/base.pp
new file mode 100644
index 00000000..7959f018
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/base.pp
@@ -0,0 +1,48 @@
+# base things for shorewall
+class shorewall::base {
+
+ package { 'shorewall':
+ ensure => $shorewall::ensure_version,
+ }
+
+ # This file has to be managed in place, so shorewall can find it
+ file {
+ '/etc/shorewall/shorewall.conf':
+ require => Package[shorewall],
+ notify => Service[shorewall],
+ owner => root,
+ group => 0,
+ mode => '0644';
+ '/etc/shorewall/puppet':
+ ensure => directory,
+ require => Package[shorewall],
+ owner => root,
+ group => 0,
+ mode => '0644';
+ }
+
+ if $shorewall::conf_source {
+ File['/etc/shorewall/shorewall.conf']{
+ source => $shorewall::conf_source,
+ }
+ } else {
+
+ Class['augeas'] -> Class['shorewall::base']
+
+ augeas { 'shorewall_module_config_path':
+ changes => 'set /files/etc/shorewall/shorewall.conf/CONFIG_PATH \'"/etc/shorewall/puppet:/etc/shorewall:/usr/share/shorewall"\'',
+ lens => 'Shellvars.lns',
+ incl => '/etc/shorewall/shorewall.conf',
+ notify => Service['shorewall'],
+ require => Package['shorewall'];
+ }
+ }
+
+ service{'shorewall':
+ ensure => running,
+ enable => true,
+ hasstatus => true,
+ hasrestart => true,
+ require => Package['shorewall'],
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/blacklist.pp b/puppet/modules/shorewall/manifests/blacklist.pp
new file mode 100644
index 00000000..afbe2165
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/blacklist.pp
@@ -0,0 +1,9 @@
+define shorewall::blacklist(
+ $proto = '-',
+ $port = '-',
+ $order='100'
+){
+ shorewall::entry{"blacklist-${order}-${name}":
+ line => "${name} ${proto} ${port}",
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/centos.pp b/puppet/modules/shorewall/manifests/centos.pp
new file mode 100644
index 00000000..f671bc9f
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/centos.pp
@@ -0,0 +1,13 @@
+# things needed on centos
+class shorewall::centos inherits shorewall::base {
+ if $::lsbmajdistrelease > 5 {
+ augeas{'enable_shorewall':
+ context => '/files/etc/sysconfig/shorewall',
+ changes => 'set startup 1',
+ lens => 'Shellvars.lns',
+ incl => '/etc/sysconfig/shorewall',
+ require => Package['shorewall'],
+ notify => Service['shorewall'],
+ }
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/debian.pp b/puppet/modules/shorewall/manifests/debian.pp
new file mode 100644
index 00000000..c7ed6077
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/debian.pp
@@ -0,0 +1,11 @@
+class shorewall::debian inherits shorewall::base {
+ file{'/etc/default/shorewall':
+ content => template("shorewall/debian_default.erb"),
+ require => Package['shorewall'],
+ notify => Service['shorewall'],
+ owner => root, group => 0, mode => 0644;
+ }
+ Service['shorewall']{
+ status => '/sbin/shorewall status'
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/entry.pp b/puppet/modules/shorewall/manifests/entry.pp
new file mode 100644
index 00000000..c8fffc72
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/entry.pp
@@ -0,0 +1,12 @@
+define shorewall::entry(
+ $ensure = present,
+ $line
+){
+ $parts = split($name,'-')
+ concat::fragment{$name:
+ ensure => $ensure,
+ content => "${line}\n",
+ order => $parts[1],
+ target => "/etc/shorewall/puppet/${parts[0]}",
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/extension_script.pp b/puppet/modules/shorewall/manifests/extension_script.pp
new file mode 100644
index 00000000..569fcbf8
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/extension_script.pp
@@ -0,0 +1,14 @@
+# See http://shorewall.net/shorewall_extension_scripts.htm
+define shorewall::extension_script($script = '') {
+ case $name {
+ 'init', 'initdone', 'start', 'started', 'stop', 'stopped', 'clear', 'refresh', 'continue', 'maclog': {
+ file { "/etc/shorewall/puppet/${name}":
+ content => "${script}\n",
+ notify => Service[shorewall];
+ }
+ }
+ '', default: {
+ err("${name}: unknown shorewall extension script")
+ }
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/gentoo.pp b/puppet/modules/shorewall/manifests/gentoo.pp
new file mode 100644
index 00000000..7b307a4e
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/gentoo.pp
@@ -0,0 +1,5 @@
+class shorewall::gentoo inherits shorewall::base {
+ Package[shorewall]{
+ category => 'net-firewall',
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/host.pp b/puppet/modules/shorewall/manifests/host.pp
new file mode 100644
index 00000000..f4002232
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/host.pp
@@ -0,0 +1,10 @@
+define shorewall::host(
+ $zone,
+ $options = 'tcpflags,blacklist,norfc1918',
+ $order='100'
+){
+ shorewall::entry{"hosts-${order}-${name}":
+ line => "${zone} ${name} ${options}"
+ }
+}
+
diff --git a/puppet/modules/shorewall/manifests/init.pp b/puppet/modules/shorewall/manifests/init.pp
new file mode 100644
index 00000000..a5675646
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/init.pp
@@ -0,0 +1,75 @@
+# Manage shorewall on your system
+class shorewall(
+ $startup = '1',
+ $conf_source = false,
+ $ensure_version = 'present',
+ $tor_transparent_proxy_host = '127.0.0.1',
+ $tor_transparent_proxy_port = '9040',
+ $tor_user = $::operatingsystem ? {
+ 'Debian' => 'debian-tor',
+ default => 'tor'
+ }
+) {
+
+ case $::operatingsystem {
+ gentoo: { include shorewall::gentoo }
+ debian: {
+ include shorewall::debian
+ $dist_tor_user = 'debian-tor'
+ }
+ centos: { include shorewall::centos }
+ ubuntu: {
+ case $::lsbdistcodename {
+ karmic: { include shorewall::ubuntu::karmic }
+ default: { include shorewall::debian }
+ }
+ }
+ default: {
+ notice "unknown operatingsystem: ${::operatingsystem}"
+ include shorewall::base
+ }
+ }
+
+ shorewall::managed_file{
+ [
+ # See http://www.shorewall.net/3.0/Documentation.htm#Zones
+ 'zones',
+ # See http://www.shorewall.net/3.0/Documentation.htm#Interfaces
+ 'interfaces',
+ # See http://www.shorewall.net/3.0/Documentation.htm#Hosts
+ 'hosts',
+ # See http://www.shorewall.net/3.0/Documentation.htm#Policy
+ 'policy',
+ # See http://www.shorewall.net/3.0/Documentation.htm#Rules
+ 'rules',
+ # See http://www.shorewall.net/3.0/Documentation.htm#Masq
+ 'masq',
+ # See http://www.shorewall.net/3.0/Documentation.htm#ProxyArp
+ 'proxyarp',
+ # See http://www.shorewall.net/3.0/Documentation.htm#NAT
+ 'nat',
+ # See http://www.shorewall.net/3.0/Documentation.htm#Blacklist
+ 'blacklist',
+ # See http://www.shorewall.net/3.0/Documentation.htm#rfc1918
+ 'rfc1918',
+ # See http://www.shorewall.net/3.0/Documentation.htm#Routestopped
+ 'routestopped',
+ # See http://www.shorewall.net/3.0/Documentation.htm#Variables
+ 'params',
+ # See http://www.shorewall.net/3.0/traffic_shaping.htm
+ 'tcdevices',
+ # See http://www.shorewall.net/3.0/traffic_shaping.htm
+ 'tcrules',
+ # See http://www.shorewall.net/3.0/traffic_shaping.htm
+ 'tcclasses',
+ # http://www.shorewall.net/manpages/shorewall-providers.html
+ 'providers',
+ # See http://www.shorewall.net/manpages/shorewall-tunnels.html
+ 'tunnel',
+ # See http://www.shorewall.net/MultiISP.html
+ 'rtrules',
+ # See http://www.shorewall.net/manpages/shorewall-mangle.html
+ 'mangle',
+ ]:;
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/interface.pp b/puppet/modules/shorewall/manifests/interface.pp
new file mode 100644
index 00000000..403ee749
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/interface.pp
@@ -0,0 +1,29 @@
+define shorewall::interface(
+ $zone,
+ $broadcast = 'detect',
+ $options = 'tcpflags,blacklist,routefilter,nosmurfs,logmartians',
+ $add_options = '',
+ $rfc1918 = false,
+ $dhcp = false,
+ $order = 100
+){
+ $added_opts = $add_options ? {
+ '' => '',
+ default => ",${add_options}",
+ }
+
+ $dhcp_opt = $dhcp ? {
+ false => '',
+ default => ',dhcp',
+ }
+
+ $rfc1918_opt = $rfc1918 ? {
+ false => ',norfc1918',
+ default => '',
+ }
+
+ shorewall::entry { "interfaces-${order}-${name}":
+ line => "${zone} ${name} ${broadcast} ${options}${dhcp_opt}${rfc1918_opt}${added_opts}",
+ }
+}
+
diff --git a/puppet/modules/shorewall/manifests/managed_file.pp b/puppet/modules/shorewall/manifests/managed_file.pp
new file mode 100644
index 00000000..d564daa7
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/managed_file.pp
@@ -0,0 +1,17 @@
+define shorewall::managed_file () {
+ concat{ "/etc/shorewall/puppet/${name}":
+ notify => Service['shorewall'],
+ require => File['/etc/shorewall/puppet'],
+ owner => root, group => 0, mode => 0600;
+ }
+ concat::fragment {
+ "${name}-header":
+ source => "puppet:///modules/shorewall/boilerplate/${name}.header",
+ target => "/etc/shorewall/puppet/${name}",
+ order => '000';
+ "${name}-footer":
+ source => "puppet:///modules/shorewall/boilerplate/${name}.footer",
+ target => "/etc/shorewall/puppet/${name}",
+ order => '999';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/mangle.pp b/puppet/modules/shorewall/manifests/mangle.pp
new file mode 100644
index 00000000..e3fd1b3b
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/mangle.pp
@@ -0,0 +1,19 @@
+define shorewall::mangle(
+ $source,
+ $destination,
+ $proto = '-',
+ $destinationport = '-',
+ $sourceport = '-',
+ $user = '-',
+ $test = '-',
+ $length = '-',
+ $tos = '-',
+ $connbytes = '-',
+ $helper = '-',
+ $headers = '-',
+ $order = '100'
+){
+ shorewall::entry{"mangle-${order}-${name}":
+ line => "${name} ${source} ${destination} ${proto} ${destinationport} ${sourceport} ${user} ${test} ${length} ${tos} ${connbytes} ${helper} ${headers}"
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/masq.pp b/puppet/modules/shorewall/manifests/masq.pp
new file mode 100644
index 00000000..fb097e5e
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/masq.pp
@@ -0,0 +1,17 @@
+# mark is new in 3.4.4
+# source (= subnet) = Set of hosts that you wish to masquerade.
+# address = If you specify an address here, SNAT will be used and this will be the source address.
+define shorewall::masq(
+ $interface,
+ $source, $address = '-',
+ $proto = '-',
+ $port = '-',
+ $ipsec = '-',
+ $mark = '',
+ $order='100'
+){
+ shorewall::entry{"masq-${order}-${name}":
+ line => "# ${name}\n${interface} ${source} ${address} ${proto} ${port} ${ipsec} ${mark}"
+ }
+}
+
diff --git a/puppet/modules/shorewall/manifests/nat.pp b/puppet/modules/shorewall/manifests/nat.pp
new file mode 100644
index 00000000..e29b7849
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/nat.pp
@@ -0,0 +1,11 @@
+define shorewall::nat(
+ $interface,
+ $internal,
+ $all = 'no',
+ $local = 'yes',
+ $order='100'
+){
+ shorewall::entry{"nat-${order}-${name}":
+ line => "${name} ${interface} ${internal} ${all} ${local}"
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/params.pp b/puppet/modules/shorewall/manifests/params.pp
new file mode 100644
index 00000000..3bc56630
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/params.pp
@@ -0,0 +1,5 @@
+define shorewall::params($value, $order='100'){
+ shorewall::entry{"params-${order}-${name}":
+ line => "${name}=${value}",
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/policy.pp b/puppet/modules/shorewall/manifests/policy.pp
new file mode 100644
index 00000000..efee05b5
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/policy.pp
@@ -0,0 +1,12 @@
+define shorewall::policy(
+ $sourcezone,
+ $destinationzone,
+ $policy, $shloglevel = '-',
+ $limitburst = '-',
+ $order
+){
+ shorewall::entry{"policy-${order}-${name}":
+ line => "# ${name}\n${sourcezone} ${destinationzone} ${policy} ${shloglevel} ${limitburst}",
+ }
+}
+
diff --git a/puppet/modules/shorewall/manifests/providers.pp b/puppet/modules/shorewall/manifests/providers.pp
new file mode 100644
index 00000000..a1f8726a
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/providers.pp
@@ -0,0 +1,16 @@
+# manage providers
+define shorewall::providers(
+ $provider = $name,
+ $number = '',
+ $mark = '',
+ $duplicate = 'main',
+ $interface = '',
+ $gateway = '',
+ $options = '',
+ $copy = '',
+ $order = '100'
+){
+ shorewall::entry{"providers-${order}-${name}":
+ line => "# ${name}\n${provider} ${number} ${mark} ${duplicate} ${interface} ${gateway} ${options} ${copy}"
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/proxyarp.pp b/puppet/modules/shorewall/manifests/proxyarp.pp
new file mode 100644
index 00000000..1af554fb
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/proxyarp.pp
@@ -0,0 +1,11 @@
+define shorewall::proxyarp(
+ $interface,
+ $external,
+ $haveroute = yes,
+ $persistent = no,
+ $order='100'
+ ){
+ shorewall::entry{"proxyarp-${order}-${name}":
+ line => "# ${name}\n${name} ${interface} ${external} ${haveroute} ${persistent}"
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rfc1918.pp b/puppet/modules/shorewall/manifests/rfc1918.pp
new file mode 100644
index 00000000..31dce5dc
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rfc1918.pp
@@ -0,0 +1,8 @@
+define shorewall::rfc1918(
+ $action = 'logdrop',
+ $order='100'
+){
+ shorewall::entry{"rfc1918-${order}-${name}":
+ line => "${name} ${action}"
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/routestopped.pp b/puppet/modules/shorewall/manifests/routestopped.pp
new file mode 100644
index 00000000..aca57b51
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/routestopped.pp
@@ -0,0 +1,14 @@
+define shorewall::routestopped(
+ $interface = $name,
+ $host = '-',
+ $options = '',
+ $order='100'
+){
+ $real_interface = $interface ? {
+ '' => $name,
+ default => $interface,
+ }
+ shorewall::entry{"routestopped-${order}-${name}":
+ line => "${real_interface} ${host} ${options}",
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rtrules.pp b/puppet/modules/shorewall/manifests/rtrules.pp
new file mode 100644
index 00000000..3810f26d
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rtrules.pp
@@ -0,0 +1,11 @@
+define shorewall::rtrules(
+ $source = '-',
+ $destination = '-',
+ $provider,
+ $priority = '10000',
+ $mark,
+){
+ shorewall::entry { "rtrules-${mark}-${name}":
+ line => "# ${name}\n${source} ${destination} ${provider} ${priority} ${mark}",
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rule.pp b/puppet/modules/shorewall/manifests/rule.pp
new file mode 100644
index 00000000..2fe91e27
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rule.pp
@@ -0,0 +1,20 @@
+# mark is new in 3.4.4
+define shorewall::rule(
+ $ensure = present,
+ $action,
+ $source,
+ $destination,
+ $proto = '-',
+ $destinationport = '-',
+ $sourceport = '-',
+ $originaldest = '-',
+ $ratelimit = '-',
+ $user = '-',
+ $mark = '',
+ $order
+){
+ shorewall::entry{"rules-${order}-${name}":
+ ensure => $ensure,
+ line => "# ${name}\n${action} ${source} ${destination} ${proto} ${destinationport} ${sourceport} ${originaldest} ${ratelimit} ${user} ${mark}",
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rule_section.pp b/puppet/modules/shorewall/manifests/rule_section.pp
new file mode 100644
index 00000000..82984ca2
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rule_section.pp
@@ -0,0 +1,7 @@
+define shorewall::rule_section(
+ $order
+){
+ shorewall::entry{"rules-${order}-${name}":
+ line => "SECTION ${name}",
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/cobbler.pp b/puppet/modules/shorewall/manifests/rules/cobbler.pp
new file mode 100644
index 00000000..e04e4925
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/cobbler.pp
@@ -0,0 +1,19 @@
+class shorewall::rules::cobbler {
+ shorewall::rule{'net-me-syslog-xmlrpc-tcp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '25150:25151',
+ order => 240,
+ action => 'ACCEPT';
+ }
+ shorewall::rule{'net-me-syslog-xmlrpc-udp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'udp',
+ destinationport => '25150:25151',
+ order => 240,
+ action => 'ACCEPT';
+ }
+ include shorewall::rules::rsync
+}
diff --git a/puppet/modules/shorewall/manifests/rules/dns.pp b/puppet/modules/shorewall/manifests/rules/dns.pp
new file mode 100644
index 00000000..99311cae
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/dns.pp
@@ -0,0 +1,18 @@
+class shorewall::rules::dns {
+ shorewall::rule {
+ 'net-me-tcp_dns':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '53',
+ order => 240,
+ action => 'ACCEPT';
+ 'net-me-udp_dns':
+ source => 'net',
+ destination => '$FW',
+ proto => 'udp',
+ destinationport => '53',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/dns/disable.pp b/puppet/modules/shorewall/manifests/rules/dns/disable.pp
new file mode 100644
index 00000000..36541da4
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/dns/disable.pp
@@ -0,0 +1,5 @@
+class shorewall::rules::dns::disable inherits shorewall::rules::dns {
+ Shorewall::Rule['net-me-tcp_dns', 'net-me-udp_dns']{
+ action => 'DROP',
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/ekeyd.pp b/puppet/modules/shorewall/manifests/rules/ekeyd.pp
new file mode 100644
index 00000000..dbff02fe
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/ekeyd.pp
@@ -0,0 +1,10 @@
+class shorewall::rules::ekeyd {
+ shorewall::rule { 'net-me-tcp_ekeyd':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '8888',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/ftp.pp b/puppet/modules/shorewall/manifests/rules/ftp.pp
new file mode 100644
index 00000000..6d34c78f
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/ftp.pp
@@ -0,0 +1,10 @@
+class shorewall::rules::ftp {
+ shorewall::rule { 'net-me-ftp-tcp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '21',
+ order => 240,
+ action => 'FTP/ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/gitdaemon.pp b/puppet/modules/shorewall/manifests/rules/gitdaemon.pp
new file mode 100644
index 00000000..21372f63
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/gitdaemon.pp
@@ -0,0 +1,10 @@
+class shorewall::rules::gitdaemon {
+ shorewall::rule {'net-me-tcp_gitdaemon':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '9418',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/gitdaemon/absent.pp b/puppet/modules/shorewall/manifests/rules/gitdaemon/absent.pp
new file mode 100644
index 00000000..ade6fba0
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/gitdaemon/absent.pp
@@ -0,0 +1,5 @@
+class shorewall::rules::gitdaemon::absent inherits shorewall::rules::gitdaemon {
+ Shorewall::Rule['net-me-tcp_gitdaemon']{
+ ensure => absent,
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/http.pp b/puppet/modules/shorewall/manifests/rules/http.pp
new file mode 100644
index 00000000..e6a9bdef
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/http.pp
@@ -0,0 +1,10 @@
+class shorewall::rules::http {
+ shorewall::rule { 'net-me-http-tcp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '80',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/http/disable.pp b/puppet/modules/shorewall/manifests/rules/http/disable.pp
new file mode 100644
index 00000000..5d9170ca
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/http/disable.pp
@@ -0,0 +1,5 @@
+class shorewall::rules::http::disable inherits shorewall::rules::http {
+ Shorewall::Rule['net-me-http-tcp']{
+ action => 'DROP',
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/https.pp b/puppet/modules/shorewall/manifests/rules/https.pp
new file mode 100644
index 00000000..cc49d100
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/https.pp
@@ -0,0 +1,10 @@
+class shorewall::rules::https {
+ shorewall::rule { 'net-me-https-tcp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '443',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/identd.pp b/puppet/modules/shorewall/manifests/rules/identd.pp
new file mode 100644
index 00000000..719e581c
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/identd.pp
@@ -0,0 +1,10 @@
+class shorewall::rules::identd {
+ shorewall::rule { 'net-me-identd-tcp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '113',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/imap.pp b/puppet/modules/shorewall/manifests/rules/imap.pp
new file mode 100644
index 00000000..7fbe1818
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/imap.pp
@@ -0,0 +1,11 @@
+class shorewall::rules::imap {
+ shorewall::rule {
+ 'net-me-tcp_imap_s':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '143,993',
+ order => 260,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/ipsec.pp b/puppet/modules/shorewall/manifests/rules/ipsec.pp
new file mode 100644
index 00000000..82adff09
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/ipsec.pp
@@ -0,0 +1,32 @@
+class shorewall::rules::ipsec(
+ $source = 'net'
+) {
+ shorewall::rule {
+ 'net-me-ipsec-udp':
+ source => $shorewall::rules::ipsec::source,
+ destination => '$FW',
+ proto => 'udp',
+ destinationport => '500',
+ order => 240,
+ action => 'ACCEPT';
+ 'me-net-ipsec-udp':
+ source => '$FW',
+ destination => $shorewall::rules::ipsec::source,
+ proto => 'udp',
+ destinationport => '500',
+ order => 240,
+ action => 'ACCEPT';
+ 'net-me-ipsec':
+ source => $shorewall::rules::ipsec::source,
+ destination => '$FW',
+ proto => 'esp',
+ order => 240,
+ action => 'ACCEPT';
+ 'me-net-ipsec':
+ source => '$FW',
+ destination => $shorewall::rules::ipsec::source,
+ proto => 'esp',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/ipsec_nat.pp b/puppet/modules/shorewall/manifests/rules/ipsec_nat.pp
new file mode 100644
index 00000000..6c0d5072
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/ipsec_nat.pp
@@ -0,0 +1,18 @@
+class shorewall::rules::ipsec_nat {
+ shorewall::rule {
+ 'net-me-ipsec-nat-udp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'udp',
+ destinationport => '4500',
+ order => 240,
+ action => 'ACCEPT';
+ 'me-net-ipsec-nat-udp':
+ source => '$FW',
+ destination => 'net',
+ proto => 'udp',
+ destinationport => '4500',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/jabberserver.pp b/puppet/modules/shorewall/manifests/rules/jabberserver.pp
new file mode 100644
index 00000000..3b38b294
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/jabberserver.pp
@@ -0,0 +1,19 @@
+class shorewall::rules::jabberserver {
+ shorewall::rule {
+ 'net-me-tcp_jabber':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '5222,5223,5269',
+ order => 240,
+ action => 'ACCEPT';
+ 'me-net-tcp_jabber_s2s':
+ source => '$FW',
+ destination => 'net',
+ proto => 'tcp',
+ destinationport => '5260,5269,5270,5271,5272',
+ order => 240,
+ action => 'ACCEPT';
+ }
+
+}
diff --git a/puppet/modules/shorewall/manifests/rules/jetty.pp b/puppet/modules/shorewall/manifests/rules/jetty.pp
new file mode 100644
index 00000000..4080e7e6
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/jetty.pp
@@ -0,0 +1,12 @@
+class shorewall::rules::jetty {
+ # open jetty port
+ shorewall::rule {
+ 'net-me-jetty-tcp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '8080',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/jetty/http.pp b/puppet/modules/shorewall/manifests/rules/jetty/http.pp
new file mode 100644
index 00000000..4c0652be
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/jetty/http.pp
@@ -0,0 +1,9 @@
+class shorewall::rules::jetty::http {
+ # dnat
+ shorewall::rule {
+ 'dnat-http-to-jetty':
+ destination => "net:${::ipaddress}:8080",
+ destinationport => '80',
+ source => 'net', proto => 'tcp', order => 140, action => 'DNAT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/jetty/ssl.pp b/puppet/modules/shorewall/manifests/rules/jetty/ssl.pp
new file mode 100644
index 00000000..f7517493
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/jetty/ssl.pp
@@ -0,0 +1,11 @@
+class shorewall::rules::jetty::ssl {
+ shorewall::rule {
+ 'net-me-jettyssl-tcp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '8443',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/keyserver.pp b/puppet/modules/shorewall/manifests/rules/keyserver.pp
new file mode 100644
index 00000000..2ade9c1e
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/keyserver.pp
@@ -0,0 +1,11 @@
+class shorewall::rules::keyserver {
+ shorewall::rule {
+ 'net-me-tcp_keyserver':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '11371,11372',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/libvirt/host.pp b/puppet/modules/shorewall/manifests/rules/libvirt/host.pp
new file mode 100644
index 00000000..c2268659
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/libvirt/host.pp
@@ -0,0 +1,79 @@
+class shorewall::rules::libvirt::host (
+ $vmz = 'vmz',
+ $masq_iface = 'eth0',
+ $debproxy_port = 8000,
+ $accept_dhcp = true,
+ $vmz_iface = 'virbr0',
+ ) {
+
+ define shorewall::rule::accept::from_vmz (
+ $proto = '-',
+ $destinationport = '-',
+ $action = 'ACCEPT'
+ ) {
+ shorewall::rule { $name:
+ source => $shorewall::rules::libvirt::host::vmz,
+ destination => '$FW',
+ order => 300,
+ proto => $proto,
+ destinationport => $destinationport,
+ action => $action;
+ }
+ }
+
+ shorewall::policy {
+ 'fw-to-vmz':
+ sourcezone => '$FW',
+ destinationzone => $vmz,
+ policy => 'ACCEPT',
+ order => 110;
+ 'vmz-to-net':
+ sourcezone => $vmz,
+ destinationzone => 'net',
+ policy => 'ACCEPT',
+ order => 200;
+ 'vmz-to-all':
+ sourcezone => $vmz,
+ destinationzone => 'all',
+ policy => 'DROP',
+ shloglevel => 'info',
+ order => 800;
+ }
+
+ shorewall::rule::accept::from_vmz {
+ 'accept_dns_from_vmz':
+ action => 'DNS(ACCEPT)';
+ 'accept_tftp_from_vmz':
+ action => 'TFTP(ACCEPT)';
+ 'accept_puppet_from_vmz':
+ proto => 'tcp',
+ destinationport => '8140',
+ action => 'ACCEPT';
+ }
+
+ if $accept_dhcp {
+ shorewall::mangle { 'CHECKSUM:T':
+ source => '-',
+ destination => $vmz_iface,
+ proto => 'udp',
+ destinationport => '68';
+ }
+ }
+
+ if $debproxy_port {
+ shorewall::rule::accept::from_vmz { 'accept_debproxy_from_vmz':
+ proto => 'tcp',
+ destinationport => $debproxy_port,
+ action => 'ACCEPT';
+ }
+ }
+
+ if $masq_iface {
+ shorewall::masq {
+ "masq-${masq_iface}":
+ interface => $masq_iface,
+ source => '10.0.0.0/8,169.254.0.0/16,172.16.0.0/12,192.168.0.0/16';
+ }
+ }
+
+}
diff --git a/puppet/modules/shorewall/manifests/rules/managesieve.pp b/puppet/modules/shorewall/manifests/rules/managesieve.pp
new file mode 100644
index 00000000..63fafcb6
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/managesieve.pp
@@ -0,0 +1,11 @@
+class shorewall::rules::managesieve {
+ shorewall::rule {
+ 'net-me-tcp_managesieve':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '2000',
+ order => 260,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/mdns.pp b/puppet/modules/shorewall/manifests/rules/mdns.pp
new file mode 100644
index 00000000..76b1fd90
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/mdns.pp
@@ -0,0 +1,8 @@
+class shorewall::rules::mdns {
+ shorewall::rule { 'net-me-mdns':
+ source => 'net',
+ destination => '$FW',
+ order => 240,
+ action => 'mDNS(ACCEPT)';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/munin.pp b/puppet/modules/shorewall/manifests/rules/munin.pp
new file mode 100644
index 00000000..a20a4e0a
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/munin.pp
@@ -0,0 +1,16 @@
+class shorewall::rules::munin(
+ $munin_port = '4949',
+ $munin_collector = ['127.0.0.1'],
+ $collector_source = 'net'
+){
+ shorewall::params { 'MUNINPORT': value => $munin_port }
+ shorewall::params { 'MUNINCOLLECTOR': value => join(any2array($munin_collector),',') }
+ shorewall::rule{'net-me-munin-tcp':
+ source => "${collector_source}:\$MUNINCOLLECTOR",
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '$MUNINPORT',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/mysql.pp b/puppet/modules/shorewall/manifests/rules/mysql.pp
new file mode 100644
index 00000000..0da68a19
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/mysql.pp
@@ -0,0 +1,11 @@
+class shorewall::rules::mysql {
+ shorewall::rule {
+ 'net-me-tcp_mysql':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '3306',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/nfsd.pp b/puppet/modules/shorewall/manifests/rules/nfsd.pp
new file mode 100644
index 00000000..bd509cf2
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/nfsd.pp
@@ -0,0 +1,115 @@
+class shorewall::rules::nfsd {
+ shorewall::rule { 'net-me-portmap-tcp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '111',
+ order => 240,
+ action => 'ACCEPT';
+ }
+ shorewall::rule { 'net-me-portmap-udp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'udp',
+ destinationport => '111',
+ order => 240,
+ action => 'ACCEPT';
+ }
+ shorewall::rule { 'net-me-rpc.statd-tcp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '662',
+ order => 240,
+ action => 'ACCEPT';
+ }
+ shorewall::rule { 'net-me-rpc.statd-udp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'udp',
+ destinationport => '662',
+ order => 240,
+ action => 'ACCEPT';
+ }
+ shorewall::rule { 'me-net-rpc.statd-tcp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '2020',
+ order => 240,
+ action => 'ACCEPT';
+ }
+ shorewall::rule { 'me-net-rpc.statd-udp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'udp',
+ destinationport => '2020',
+ order => 240,
+ action => 'ACCEPT';
+ }
+ shorewall::rule { 'net-me-rpc.lockd-tcp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '32803',
+ order => 240,
+ action => 'ACCEPT';
+ }
+ shorewall::rule { 'net-me-rpc.lockd-udp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'udp',
+ destinationport => '32769',
+ order => 240,
+ action => 'ACCEPT';
+ }
+ shorewall::rule { 'net-me-rpc.mountd-tcp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '892',
+ order => 240,
+ action => 'ACCEPT';
+ }
+ shorewall::rule { 'net-me-rpc.mountd-udp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'udp',
+ destinationport => '892',
+ order => 240,
+ action => 'ACCEPT';
+ }
+ shorewall::rule { 'net-me-rpc.rquotad-tcp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '875',
+ order => 240,
+ action => 'ACCEPT';
+ }
+ shorewall::rule { 'net-me-rpc.rquoata-udp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'udp',
+ destinationport => '875',
+ order => 240,
+ action => 'ACCEPT';
+ }
+ shorewall::rule { 'net-me-rpc.nfsd-tcp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '2049',
+ order => 240,
+ action => 'ACCEPT';
+ }
+ shorewall::rule { 'net-me-rpc.nfsd-udp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'udp',
+ destinationport => '2049',
+ order => 240,
+ action => 'ACCEPT';
+ }
+
+}
diff --git a/puppet/modules/shorewall/manifests/rules/ntp/client.pp b/puppet/modules/shorewall/manifests/rules/ntp/client.pp
new file mode 100644
index 00000000..e0db8d45
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/ntp/client.pp
@@ -0,0 +1,11 @@
+class shorewall::rules::ntp::client {
+ # open ntp udp port to fetch time
+ shorewall::rule {'me-net-udp_ntp':
+ source => '$FW',
+ destination => 'net',
+ proto => 'udp',
+ destinationport => '123',
+ order => 251,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/ntp/server.pp b/puppet/modules/shorewall/manifests/rules/ntp/server.pp
new file mode 100644
index 00000000..ed0968db
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/ntp/server.pp
@@ -0,0 +1,10 @@
+class shorewall::rules::ntp::server {
+ shorewall::rule {'net-me-udp_ntp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'udp',
+ destinationport => '123',
+ order => 241,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/openfire.pp b/puppet/modules/shorewall/manifests/rules/openfire.pp
new file mode 100644
index 00000000..0e6d1d80
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/openfire.pp
@@ -0,0 +1,12 @@
+class shorewall::rules::openfire {
+ include shorewall::rules::jaberserver
+
+ shorewall::rule { 'me-all-openfire-tcp':
+ source => '$FW',
+ destination => 'all',
+ proto => 'tcp',
+ destinationport => '7070,7443,7777',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/out/ekeyd.pp b/puppet/modules/shorewall/manifests/rules/out/ekeyd.pp
new file mode 100644
index 00000000..8acdaad5
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/out/ekeyd.pp
@@ -0,0 +1,10 @@
+define shorewall::rules::out::ekeyd($host) {
+ shorewall::rule { "me-${name}-tcp_ekeyd":
+ source => '$FW',
+ destination => "${name}:${host}",
+ proto => 'tcp',
+ destinationport => '8888',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/out/git.pp b/puppet/modules/shorewall/manifests/rules/out/git.pp
new file mode 100644
index 00000000..cb88da85
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/out/git.pp
@@ -0,0 +1,10 @@
+class shorewall::rules::out::git {
+ shorewall::rule{'me-net-git-tcp':
+ source => '$FW',
+ destination => 'net',
+ proto => 'tcp',
+ destinationport => '9418',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/out/ibackup.pp b/puppet/modules/shorewall/manifests/rules/out/ibackup.pp
new file mode 100644
index 00000000..856bcdb9
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/out/ibackup.pp
@@ -0,0 +1,12 @@
+class shorewall::rules::out::ibackup(
+ $backup_host
+){
+ shorewall::rule { 'me-net-tcp_backupssh':
+ source => '$FW',
+ destination => "net:${backup_host}",
+ proto => 'tcp',
+ destinationport => 'ssh',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/out/imap.pp b/puppet/modules/shorewall/manifests/rules/out/imap.pp
new file mode 100644
index 00000000..f1313d2c
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/out/imap.pp
@@ -0,0 +1,11 @@
+class shorewall::rules::out::imap {
+ shorewall::rule {
+ 'me-net-tcp_imap_s':
+ source => '$FW',
+ destination => 'net',
+ proto => 'tcp',
+ destinationport => '143,993',
+ order => 260,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/out/irc.pp b/puppet/modules/shorewall/manifests/rules/out/irc.pp
new file mode 100644
index 00000000..9c8590ab
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/out/irc.pp
@@ -0,0 +1,10 @@
+class shorewall::rules::out::irc {
+ shorewall::rule{'me-net-irc-tcp':
+ source => '$FW',
+ destination => 'net',
+ proto => 'tcp',
+ destinationport => '6667',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/out/ircs.pp b/puppet/modules/shorewall/manifests/rules/out/ircs.pp
new file mode 100644
index 00000000..a71585d8
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/out/ircs.pp
@@ -0,0 +1,10 @@
+class shorewall::rules::out::ircs {
+ shorewall::rule{'me-net-ircs-tcp':
+ source => '$FW',
+ destination => 'net',
+ proto => 'tcp',
+ destinationport => '6669',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/out/keyserver.pp b/puppet/modules/shorewall/manifests/rules/out/keyserver.pp
new file mode 100644
index 00000000..aa7147e0
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/out/keyserver.pp
@@ -0,0 +1,11 @@
+class shorewall::rules::out::keyserver {
+ shorewall::rule {
+ 'me-net-tcp_keyserver':
+ source => '$FW',
+ destination => 'net',
+ proto => 'tcp',
+ destinationport => '11371,11372',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/out/managesieve.pp b/puppet/modules/shorewall/manifests/rules/out/managesieve.pp
new file mode 100644
index 00000000..b0e1c3da
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/out/managesieve.pp
@@ -0,0 +1,11 @@
+class shorewall::rules::out::managesieve {
+ shorewall::rule {
+ 'me-net-tcp_managesieve':
+ source => '$FW',
+ destination => 'net',
+ proto => 'tcp',
+ destinationport => '2000',
+ order => 260,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/out/munin.pp b/puppet/modules/shorewall/manifests/rules/out/munin.pp
new file mode 100644
index 00000000..004a3d5b
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/out/munin.pp
@@ -0,0 +1,10 @@
+class shorewall::rules::out::munin {
+ shorewall::rule { 'me-net-rcp_muninhost':
+ source => '$FW',
+ destination => 'net',
+ proto => 'tcp',
+ destinationport => '4949',
+ order => 340,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/out/mysql.pp b/puppet/modules/shorewall/manifests/rules/out/mysql.pp
new file mode 100644
index 00000000..1334ba6a
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/out/mysql.pp
@@ -0,0 +1,11 @@
+class shorewall::rules::out::mysql {
+ shorewall::rule {
+ 'me-net-tcp_mysql':
+ source => '$FW',
+ destination => 'net',
+ proto => 'tcp',
+ destinationport => '3306',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/out/pop3.pp b/puppet/modules/shorewall/manifests/rules/out/pop3.pp
new file mode 100644
index 00000000..ebd4828f
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/out/pop3.pp
@@ -0,0 +1,11 @@
+class shorewall::rules::out::pop3 {
+ shorewall::rule {
+ 'me-net-tcp_pop3_s':
+ source => '$FW',
+ destination => 'net',
+ proto => 'tcp',
+ destinationport => 'pop3,pop3s',
+ order => 260,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/out/postgres.pp b/puppet/modules/shorewall/manifests/rules/out/postgres.pp
new file mode 100644
index 00000000..a62d75d7
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/out/postgres.pp
@@ -0,0 +1,11 @@
+class shorewall::rules::out::postgres {
+ shorewall::rule {
+ 'me-net-tcp_postgres':
+ source => '$FW',
+ destination => 'net',
+ proto => 'tcp',
+ destinationport => '5432',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/out/puppet.pp b/puppet/modules/shorewall/manifests/rules/out/puppet.pp
new file mode 100644
index 00000000..cbe8cce7
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/out/puppet.pp
@@ -0,0 +1,20 @@
+class shorewall::rules::out::puppet(
+ $puppetserver = "puppet.${::domain}",
+ $puppetserver_port = 8140,
+ $puppetserver_signport = 8141
+) {
+ class{'shorewall::rules::puppet':
+ puppetserver => $puppetserver,
+ puppetserver_port => $puppetserver_port,
+ puppetserver_signport => $puppetserver_signport,
+ }
+ # we want to connect to the puppet server
+ shorewall::rule { 'me-net-puppet_tcp':
+ source => '$FW',
+ destination => 'net:$PUPPETSERVER',
+ proto => 'tcp',
+ destinationport => '$PUPPETSERVER_PORT,$PUPPETSERVER_SIGN_PORT',
+ order => 340,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/out/silc.pp b/puppet/modules/shorewall/manifests/rules/out/silc.pp
new file mode 100644
index 00000000..830df9c3
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/out/silc.pp
@@ -0,0 +1,19 @@
+class shorewall::rules::out::silc {
+ shorewall::rule{
+ 'me-net-silc-tcp':
+ source => '$FW',
+ destination => 'net',
+ proto => 'tcp',
+ destinationport => '706',
+ order => 240,
+ action => 'ACCEPT';
+ 'me-net-silc-udp':
+ source => '$FW',
+ destination => 'net',
+ proto => 'udp',
+ destinationport => '706',
+ order => 240,
+ action => 'ACCEPT';
+
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/out/smtp.pp b/puppet/modules/shorewall/manifests/rules/out/smtp.pp
new file mode 100644
index 00000000..2cc77cc3
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/out/smtp.pp
@@ -0,0 +1,11 @@
+class shorewall::rules::out::smtp {
+ shorewall::rule {
+ 'me-net-tcp_smtp':
+ source => '$FW',
+ destination => 'net',
+ proto => 'tcp',
+ destinationport => 'smtp',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/out/ssh.pp b/puppet/modules/shorewall/manifests/rules/out/ssh.pp
new file mode 100644
index 00000000..c18e299b
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/out/ssh.pp
@@ -0,0 +1,10 @@
+class shorewall::rules::out::ssh {
+ shorewall::rule { 'me-net-tcp_ssh':
+ source => '$FW',
+ destination => 'net',
+ proto => 'tcp',
+ destinationport => 'ssh',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/out/ssh/disable.pp b/puppet/modules/shorewall/manifests/rules/out/ssh/disable.pp
new file mode 100644
index 00000000..223bf73b
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/out/ssh/disable.pp
@@ -0,0 +1,5 @@
+class shorewall::rules::out::ssh::disable inherits shorewall::rules::out::ssh {
+ Shorewall::Rule['me-net-tcp_ssh']{
+ action => 'DROP',
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/out/ssh/remove.pp b/puppet/modules/shorewall/manifests/rules/out/ssh/remove.pp
new file mode 100644
index 00000000..bc0acf37
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/out/ssh/remove.pp
@@ -0,0 +1,5 @@
+class shorewall::rules::out::ssh::remove inherits shorewall::rules::out::ssh {
+ Shorewall::Rule['me-net-tcp_ssh']{
+ ensure => absent,
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/out/whois.pp b/puppet/modules/shorewall/manifests/rules/out/whois.pp
new file mode 100644
index 00000000..d003d5c1
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/out/whois.pp
@@ -0,0 +1,11 @@
+class shorewall::rules::out::whois {
+ # open whois tcp port
+ shorewall::rule {'me-net-tcp_whois':
+ source => '$FW',
+ destination => 'net',
+ proto => 'tcp',
+ destinationport => '43',
+ order => 251,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/out/xmpp.pp b/puppet/modules/shorewall/manifests/rules/out/xmpp.pp
new file mode 100644
index 00000000..a1b4577c
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/out/xmpp.pp
@@ -0,0 +1,10 @@
+class shorewall::rules::out::xmpp {
+ shorewall::rule{'me-net-xmpp-tcp':
+ source => '$FW',
+ destination => 'net',
+ proto => 'tcp',
+ destinationport => '5222',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/pop3.pp b/puppet/modules/shorewall/manifests/rules/pop3.pp
new file mode 100644
index 00000000..25878568
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/pop3.pp
@@ -0,0 +1,11 @@
+class shorewall::rules::pop3 {
+ shorewall::rule {
+ 'net-me-tcp_pop3_s':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => 'pop3,pop3s',
+ order => 260,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/postgres.pp b/puppet/modules/shorewall/manifests/rules/postgres.pp
new file mode 100644
index 00000000..1a22027e
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/postgres.pp
@@ -0,0 +1,10 @@
+class shorewall::rules::postgres {
+ shorewall::rule { 'net-me-tcp_postgres':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '5432',
+ order => 250,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/puppet.pp b/puppet/modules/shorewall/manifests/rules/puppet.pp
new file mode 100644
index 00000000..84e7d813
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/puppet.pp
@@ -0,0 +1,11 @@
+class shorewall::rules::puppet(
+ $puppetserver = "puppet.${::domain}",
+ $puppetserver_port = 8140,
+ $puppetserver_signport = 8141
+){
+ shorewall::params{
+ 'PUPPETSERVER': value => $puppetserver;
+ 'PUPPETSERVER_PORT': value => $puppetserver_port;
+ 'PUPPETSERVER_SIGN_PORT': value => $puppetserver_signport;
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/puppet/master.pp b/puppet/modules/shorewall/manifests/rules/puppet/master.pp
new file mode 100644
index 00000000..925979c3
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/puppet/master.pp
@@ -0,0 +1,10 @@
+class shorewall::rules::puppet::master {
+ shorewall::rule { 'net-me-tcp_puppet-main':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '$PUPPETSERVER_PORT,$PUPPETSERVER_SIGN_PORT',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/rsync.pp b/puppet/modules/shorewall/manifests/rules/rsync.pp
new file mode 100644
index 00000000..144624db
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/rsync.pp
@@ -0,0 +1,10 @@
+class shorewall::rules::rsync {
+ shorewall::rule{'me-net-rsync-tcp':
+ source => '$FW',
+ destination => 'net',
+ proto => 'tcp',
+ destinationport => '873',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/silcd.pp b/puppet/modules/shorewall/manifests/rules/silcd.pp
new file mode 100644
index 00000000..91ee4a59
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/silcd.pp
@@ -0,0 +1,19 @@
+class shorewall::rules::silcd {
+ shorewall::rule{
+ 'net-me-silcd-tcp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '706',
+ order => 240,
+ action => 'ACCEPT';
+ 'net-me-silcd-udp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'udp',
+ destinationport => '706',
+ order => 240,
+ action => 'ACCEPT';
+
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/smtp.pp b/puppet/modules/shorewall/manifests/rules/smtp.pp
new file mode 100644
index 00000000..b0389012
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/smtp.pp
@@ -0,0 +1,10 @@
+class shorewall::rules::smtp {
+ shorewall::rule { 'net-me-smtp-tcp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '25',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/smtp/disable.pp b/puppet/modules/shorewall/manifests/rules/smtp/disable.pp
new file mode 100644
index 00000000..cee85b08
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/smtp/disable.pp
@@ -0,0 +1,5 @@
+class shorewall::rules::smtp::disable inherits shorewall::rules::smtp {
+ Shorewall::Rule['net-me-smtp-tcp']{
+ action => 'DROP'
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/smtp_submission.pp b/puppet/modules/shorewall/manifests/rules/smtp_submission.pp
new file mode 100644
index 00000000..dff90f35
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/smtp_submission.pp
@@ -0,0 +1,10 @@
+class shorewall::rules::smtp_submission {
+ shorewall::rule { 'net-me-smtp_submission-tcp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '587',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/smtp_submission/disable.pp b/puppet/modules/shorewall/manifests/rules/smtp_submission/disable.pp
new file mode 100644
index 00000000..9724fe79
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/smtp_submission/disable.pp
@@ -0,0 +1,5 @@
+class shorewall::rules::smtp_submission::disable inherits shorewall::rules::smtp_submission {
+ Shorewall::Rule['net-me-smtp_submission-tcp']{
+ action => 'DROP'
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/smtps.pp b/puppet/modules/shorewall/manifests/rules/smtps.pp
new file mode 100644
index 00000000..48183f74
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/smtps.pp
@@ -0,0 +1,10 @@
+class shorewall::rules::smtps {
+ shorewall::rule {'net-me-smtps-tcp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '465',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/smtps/disable.pp b/puppet/modules/shorewall/manifests/rules/smtps/disable.pp
new file mode 100644
index 00000000..24bd21fb
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/smtps/disable.pp
@@ -0,0 +1,5 @@
+class shorewall::rules::smtps::disable inherits shorewall::rules::smtps {
+ Shorewall::Rule['net-me-smtps-tcp']{
+ action => 'DROP',
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/sobby/instance.pp b/puppet/modules/shorewall/manifests/rules/sobby/instance.pp
new file mode 100644
index 00000000..7151976b
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/sobby/instance.pp
@@ -0,0 +1,11 @@
+define shorewall::rules::sobby::instance( $port ){
+ shorewall::rule {
+ "net-me-tcp_sobby_${name}":
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => $port,
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/ssh.pp b/puppet/modules/shorewall/manifests/rules/ssh.pp
new file mode 100644
index 00000000..3a1b5309
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/ssh.pp
@@ -0,0 +1,13 @@
+class shorewall::rules::ssh(
+ $ports,
+ $source = 'net'
+) {
+ shorewall::rule { 'net-me-tcp_ssh':
+ source => $shorewall::rules::ssh::source,
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => join($shorewall::rules::ssh::ports,','),
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/syslog.pp b/puppet/modules/shorewall/manifests/rules/syslog.pp
new file mode 100644
index 00000000..de802e25
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/syslog.pp
@@ -0,0 +1,12 @@
+class shorewall::rules::syslog {
+ shorewall::rule { 'net-me-syslog-udp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'udp',
+ destinationport => '514',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
+
+
diff --git a/puppet/modules/shorewall/manifests/rules/tftp.pp b/puppet/modules/shorewall/manifests/rules/tftp.pp
new file mode 100644
index 00000000..78877293
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/tftp.pp
@@ -0,0 +1,18 @@
+class shorewall::rules::tftp {
+ shorewall::rule { 'net-me-tftp-tcp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '69',
+ order => 240,
+ action => 'ACCEPT';
+ }
+ shorewall::rule { 'net-me-tftp-udp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'udp',
+ destinationport => '69',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/tinc.pp b/puppet/modules/shorewall/manifests/rules/tinc.pp
new file mode 100644
index 00000000..79cf92e4
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/tinc.pp
@@ -0,0 +1,34 @@
+class shorewall::rules::tinc {
+ shorewall::rule { 'net-me-tinc-tcp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '655',
+ order => 240,
+ action => 'ACCEPT';
+ }
+ shorewall::rule { 'me-net-tinc-tcp':
+ source => '$FW',
+ destination => 'net',
+ proto => 'tcp',
+ destinationport => '655',
+ order => 240,
+ action => 'ACCEPT';
+ }
+ shorewall::rule { 'net-me-tinc-udp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'udp',
+ destinationport => '655',
+ order => 240,
+ action => 'ACCEPT';
+ }
+ shorewall::rule { 'me-net-tinc-udp':
+ source => '$FW',
+ destination => 'net',
+ proto => 'udp',
+ destinationport => '655',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/tomcat.pp b/puppet/modules/shorewall/manifests/rules/tomcat.pp
new file mode 100644
index 00000000..3c6f9df0
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/tomcat.pp
@@ -0,0 +1,12 @@
+class shorewall::rules::tomcat {
+ # open tomcat port
+ shorewall::rule {
+ 'net-me-tomcat-tcp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '8080',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/torify.pp b/puppet/modules/shorewall/manifests/rules/torify.pp
new file mode 100644
index 00000000..f6e62d81
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/torify.pp
@@ -0,0 +1,29 @@
+# shorewall::rules::torify
+#
+# Note: shorewall::rules::torify cannot be used several times with the
+# same user listed in the $users array. This restriction applies to
+# using this define multiple times without providing a $users
+# parameter.
+#
+# Parameters:
+#
+# - users: every element of this array must be valid in shorewall
+# rules user/group column.
+# - destinations: every element of this array must be valid in
+# shorewall rules original destination column.
+
+define shorewall::rules::torify(
+ $users = ['-'],
+ $destinations = ['-'],
+ $allow_rfc1918 = true
+){
+
+ $originaldest = join($destinations,',')
+
+ shorewall::rules::torify::user {
+ $users:
+ originaldest => $originaldest,
+ allow_rfc1918 => $allow_rfc1918;
+ }
+
+}
diff --git a/puppet/modules/shorewall/manifests/rules/torify/allow_tor_transparent_proxy.pp b/puppet/modules/shorewall/manifests/rules/torify/allow_tor_transparent_proxy.pp
new file mode 100644
index 00000000..3c18db69
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/torify/allow_tor_transparent_proxy.pp
@@ -0,0 +1,21 @@
+class shorewall::rules::torify::allow_tor_transparent_proxy {
+
+ $rule = "allow-tor-transparent-proxy"
+
+ if !defined(Shorewall::Rule["$rule"]) {
+ # A weirdness in shorewall forces us to explicitly allow traffic to
+ # net:$tor_transparent_proxy_host:$tor_transparent_proxy_port even
+ # if $FW->$FW traffic is allowed. This anyway avoids us special-casing
+ # the remote Tor transparent proxy situation.
+ shorewall::rule {
+ "$rule":
+ source => '$FW',
+ destination => "net:${shorewall::tor_transparent_proxy_host}",
+ proto => 'tcp',
+ destinationport => $shorewall::tor_transparent_proxy_port,
+ order => 100,
+ action => 'ACCEPT';
+ }
+ }
+
+}
diff --git a/puppet/modules/shorewall/manifests/rules/torify/allow_tor_user.pp b/puppet/modules/shorewall/manifests/rules/torify/allow_tor_user.pp
new file mode 100644
index 00000000..f44c1f01
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/torify/allow_tor_user.pp
@@ -0,0 +1,15 @@
+class shorewall::rules::torify::allow_tor_user {
+
+ $whitelist_rule = "allow-from-tor-user"
+ if !defined(Shorewall::Rule["$whitelist_rule"]) {
+ shorewall::rule {
+ "$whitelist_rule":
+ source => '$FW',
+ destination => 'all',
+ user => $shorewall::tor_user,
+ order => 101,
+ action => 'ACCEPT';
+ }
+ }
+
+}
diff --git a/puppet/modules/shorewall/manifests/rules/torify/redirect_tcp_to_tor.pp b/puppet/modules/shorewall/manifests/rules/torify/redirect_tcp_to_tor.pp
new file mode 100644
index 00000000..2bee6584
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/torify/redirect_tcp_to_tor.pp
@@ -0,0 +1,40 @@
+define shorewall::rules::torify::redirect_tcp_to_tor(
+ $user = '-',
+ $originaldest = '-'
+){
+
+ # hash the destination as it may contain slashes
+ $originaldest_sha1 = sha1($originaldest)
+ $rule = "redirect-to-tor-user=${user}-to=${originaldest_sha1}"
+
+ if !defined(Shorewall::Rule["$rule"]) {
+
+ $originaldest_real = $originaldest ? {
+ '-' => '!127.0.0.1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16',
+ default => $originaldest,
+ }
+
+ $user_real = $user ? {
+ '-' => "!${shorewall::tor_user}",
+ default => $user,
+ }
+
+ $destzone = $shorewall::tor_transparent_proxy_host ? {
+ '127.0.0.1' => '$FW',
+ default => 'net'
+ }
+
+ shorewall::rule {
+ "$rule":
+ source => '$FW',
+ destination => "${destzone}:${shorewall::tor_transparent_proxy_host}:${shorewall::tor_transparent_proxy_port}",
+ proto => 'tcp:syn',
+ originaldest => $originaldest_real,
+ user => $user_real,
+ order => 110,
+ action => 'DNAT';
+ }
+
+ }
+
+}
diff --git a/puppet/modules/shorewall/manifests/rules/torify/reject_non_tor.pp b/puppet/modules/shorewall/manifests/rules/torify/reject_non_tor.pp
new file mode 100644
index 00000000..80240ec7
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/torify/reject_non_tor.pp
@@ -0,0 +1,32 @@
+define shorewall::rules::torify::reject_non_tor(
+ $user = '-',
+ $originaldest = '-',
+ $allow_rfc1918 = true
+){
+
+ # hash the destination as it may contain slashes
+ $originaldest_sha1 = sha1($originaldest)
+ $rule = "reject-non-tor-from-${user}-to=${originaldest_sha1}"
+
+ if $originaldest == '-' {
+ $originaldest_real = $allow_rfc1918 ? {
+ false => '!127.0.0.1',
+ default => '!127.0.0.1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16',
+ }
+ } else {
+ $originaldest_real = $originaldest
+ }
+
+ if !defined(Shorewall::Rule["$rule"]) {
+ shorewall::rule {
+ "$rule":
+ source => '$FW',
+ destination => 'all',
+ originaldest => $originaldest_real,
+ user => $user,
+ order => 120,
+ action => 'REJECT';
+ }
+ }
+
+}
diff --git a/puppet/modules/shorewall/manifests/rules/torify/user.pp b/puppet/modules/shorewall/manifests/rules/torify/user.pp
new file mode 100644
index 00000000..5caccfd6
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/torify/user.pp
@@ -0,0 +1,27 @@
+define shorewall::rules::torify::user(
+ $originaldest = '-',
+ $allow_rfc1918 = true
+){
+
+ $user = $name
+
+ include shorewall::rules::torify::allow_tor_transparent_proxy
+
+ if $originaldest == '-' and $user == '-' {
+ include shorewall::rules::torify::allow_tor_user
+ }
+
+ shorewall::rules::torify::redirect_tcp_to_tor {
+ "redirect-to-tor-user=${user}-to=${originaldest}":
+ user => $user,
+ originaldest => $originaldest
+ }
+
+ shorewall::rules::torify::reject_non_tor {
+ "reject-non-tor-user=${user}-to=${originaldest}":
+ user => "$user",
+ originaldest => $originaldest,
+ allow_rfc1918 => $allow_rfc1918;
+ }
+
+}
diff --git a/puppet/modules/shorewall/manifests/tcclasses.pp b/puppet/modules/shorewall/manifests/tcclasses.pp
new file mode 100644
index 00000000..4e30a556
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/tcclasses.pp
@@ -0,0 +1,12 @@
+define shorewall::tcclasses(
+ $interface,
+ $rate,
+ $ceil,
+ $priority,
+ $options = '',
+ $order = '1'
+){
+ shorewall::entry { "tcclasses-${order}-${name}":
+ line => "# ${name}\n${interface} ${order} ${rate} ${ceil} ${priority} ${options}",
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/tcdevices.pp b/puppet/modules/shorewall/manifests/tcdevices.pp
new file mode 100644
index 00000000..f4e88d80
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/tcdevices.pp
@@ -0,0 +1,11 @@
+define shorewall::tcdevices(
+ $in_bandwidth,
+ $out_bandwidth,
+ $options = '',
+ $redirected_interfaces = '',
+ $order = '100'
+){
+ shorewall::entry { "tcdevices-${order}-${name}":
+ line => "${name} ${in_bandwidth} ${out_bandwidth} ${options} ${redirected_interfaces}",
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/tcrules.pp b/puppet/modules/shorewall/manifests/tcrules.pp
new file mode 100644
index 00000000..b9ab4a9d
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/tcrules.pp
@@ -0,0 +1,12 @@
+define shorewall::tcrules(
+ $source,
+ $destination,
+ $protocol = 'all',
+ $ports,
+ $client_ports = '',
+ $order = '1'
+){
+ shorewall::entry { "tcrules-${order}-${name}":
+ line => "# ${name}\n${order} ${source} ${destination} ${protocol} ${ports} ${client_ports}",
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/tunnel.pp b/puppet/modules/shorewall/manifests/tunnel.pp
new file mode 100644
index 00000000..2cac9227
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/tunnel.pp
@@ -0,0 +1,11 @@
+define shorewall::tunnel(
+ $tunnel_type,
+ $zone,
+ $gateway = '0.0.0.0/0',
+ $gateway_zones = '',
+ $order = '1'
+) {
+ shorewall::entry { "tunnel-${order}-${name}":
+ line => "# ${name}\n${tunnel_type} ${zone} ${gateway} ${gateway_zones}",
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/ubuntu/karmic.pp b/puppet/modules/shorewall/manifests/ubuntu/karmic.pp
new file mode 100644
index 00000000..0df37894
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/ubuntu/karmic.pp
@@ -0,0 +1,5 @@
+class shorewall::ubuntu::karmic inherits shorewall::debian {
+ Package['shorewall']{
+ name => 'shorewall-shell',
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/zone.pp b/puppet/modules/shorewall/manifests/zone.pp
new file mode 100644
index 00000000..81e57711
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/zone.pp
@@ -0,0 +1,14 @@
+define shorewall::zone(
+ $type,
+ $options = '-',
+ $in = '-',
+ $out = '-',
+ $parent = '-',
+ $order = 100
+){
+ $real_name = $parent ? { '-' => $name, default => "${name}:${parent}" }
+ shorewall::entry { "zones-${order}-${name}":
+ line => "${real_name} ${type} ${options} ${in} ${out}"
+ }
+}
+
diff --git a/puppet/modules/shorewall/templates/debian_default.erb b/puppet/modules/shorewall/templates/debian_default.erb
new file mode 100644
index 00000000..ec64cbe0
--- /dev/null
+++ b/puppet/modules/shorewall/templates/debian_default.erb
@@ -0,0 +1,26 @@
+# prevent startup with default configuration
+# set the following varible to 1 in order to allow Shorewall to start
+
+# This file is brought to you by puppet
+
+startup=<%= scope.lookupvar('shorewall::startup') == "0" ? '0' : '1' %>
+
+# if your Shorewall configuration requires detection of the ip address of a ppp
+# interface, you must list such interfaces in "wait_interface" to get Shorewall to
+# wait until the interface is configured. Otherwise the script will fail because
+# it won't be able to detect the IP address.
+#
+# Example:
+# wait_interface="ppp0"
+# or
+# wait_interface="ppp0 ppp1"
+# or, if you have defined in /etc/shorewall/params
+# wait_interface=
+
+#
+# Startup options
+#
+
+OPTIONS=""
+
+# EOF
diff --git a/puppet/modules/site_apache/files/conf.d/security b/puppet/modules/site_apache/files/conf.d/security
new file mode 100644
index 00000000..a5ae5bdc
--- /dev/null
+++ b/puppet/modules/site_apache/files/conf.d/security
@@ -0,0 +1,55 @@
+#
+# Disable access to the entire file system except for the directories that
+# are explicitly allowed later.
+#
+# This currently breaks the configurations that come with some web application
+# Debian packages. It will be made the default for the release after lenny.
+#
+#<Directory />
+# AllowOverride None
+# Order Deny,Allow
+# Deny from all
+#</Directory>
+
+
+# Changing the following options will not really affect the security of the
+# server, but might make attacks slightly more difficult in some cases.
+
+#
+# ServerTokens
+# This directive configures what you return as the Server HTTP response
+# Header. The default is 'Full' which sends information about the OS-Type
+# and compiled in modules.
+# Set to one of: Full | OS | Minimal | Minor | Major | Prod
+# where Full conveys the most information, and Prod the least.
+#
+#ServerTokens Minimal
+ServerTokens Prod
+
+#
+# Optionally add a line containing the server version and virtual host
+# name to server-generated pages (internal error documents, FTP directory
+# listings, mod_status and mod_info output etc., but not CGI generated
+# documents or custom error documents).
+# Set to "EMail" to also include a mailto: link to the ServerAdmin.
+# Set to one of: On | Off | EMail
+#
+#ServerSignature Off
+ServerSignature Off
+
+#
+# Allow TRACE method
+#
+# Set to "extended" to also reflect the request body (only for testing and
+# diagnostic purposes).
+#
+# Set to one of: On | Off | extended
+#
+#TraceEnable Off
+TraceEnable On
+
+# Setting this header will prevent other sites from embedding pages from this
+# site as frames. This defends against clickjacking attacks.
+# Requires mod_headers to be enabled.
+#
+Header set X-Frame-Options: "DENY"
diff --git a/puppet/modules/site_apache/files/include.d/ssl_common.inc b/puppet/modules/site_apache/files/include.d/ssl_common.inc
new file mode 100644
index 00000000..2d282c84
--- /dev/null
+++ b/puppet/modules/site_apache/files/include.d/ssl_common.inc
@@ -0,0 +1,7 @@
+SSLEngine on
+SSLProtocol all -SSLv2 -SSLv3
+SSLHonorCipherOrder on
+SSLCompression off
+SSLCipherSuite "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!RC4:!MD5:!PSK!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA"
+
+RequestHeader set X_FORWARDED_PROTO 'https' \ No newline at end of file
diff --git a/puppet/modules/site_apache/manifests/common.pp b/puppet/modules/site_apache/manifests/common.pp
new file mode 100644
index 00000000..8a11759a
--- /dev/null
+++ b/puppet/modules/site_apache/manifests/common.pp
@@ -0,0 +1,30 @@
+# install basic apache modules needed for all services (nagios, webapp)
+class site_apache::common {
+
+ include apache::module::rewrite
+ include apache::module::env
+
+ class { '::apache':
+ no_default_site => true,
+ ssl => true,
+ ssl_cipher_suite => 'HIGH:MEDIUM:!aNULL:!MD5'
+ }
+
+ # needed for the mod_ssl config
+ include apache::module::mime
+
+ # load mods depending on apache version
+ if ( $::lsbdistcodename == 'jessie' ) {
+ # apache >= 2.4, debian jessie
+ # needed for mod_ssl config
+ include apache::module::socache_shmcb
+ # generally needed
+ include apache::module::mpm_prefork
+ } else {
+ # apache < 2.4, debian wheezy
+ # for "Order" directive, i.e. main apache2.conf
+ include apache::module::authz_host
+ }
+
+ include site_apache::common::tls
+}
diff --git a/puppet/modules/site_apache/manifests/common/tls.pp b/puppet/modules/site_apache/manifests/common/tls.pp
new file mode 100644
index 00000000..040868bf
--- /dev/null
+++ b/puppet/modules/site_apache/manifests/common/tls.pp
@@ -0,0 +1,6 @@
+class site_apache::common::tls {
+ # class to setup common SSL configurations
+
+ apache::config::include{ 'ssl_common.inc': }
+
+}
diff --git a/puppet/modules/site_apache/templates/vhosts.d/api.conf.erb b/puppet/modules/site_apache/templates/vhosts.d/api.conf.erb
new file mode 100644
index 00000000..bfa5d04d
--- /dev/null
+++ b/puppet/modules/site_apache/templates/vhosts.d/api.conf.erb
@@ -0,0 +1,48 @@
+<VirtualHost *:80>
+ ServerName <%= @api_domain %>
+ RewriteEngine On
+ RewriteRule ^.*$ https://<%= @api_domain -%>:<%= @api_port -%>%{REQUEST_URI} [R=permanent,L]
+ CustomLog ${APACHE_LOG_DIR}/other_vhosts_access.log common
+</VirtualHost>
+
+Listen 0.0.0.0:<%= @api_port %>
+
+<VirtualHost *:<%= @api_port -%>>
+ ServerName <%= @api_domain %>
+ CustomLog ${APACHE_LOG_DIR}/other_vhosts_access.log common
+
+ SSLCACertificatePath /etc/ssl/certs
+ SSLCertificateKeyFile <%= scope.lookupvar('x509::variables::keys') %>/<%= scope.lookupvar('site_config::params::cert_name') %>.key
+ SSLCertificateFile <%= scope.lookupvar('x509::variables::certs') %>/<%= scope.lookupvar('site_config::params::cert_name') %>.crt
+
+ Include include.d/ssl_common.inc
+
+ <IfModule mod_headers.c>
+<% if @webapp['secure'] -%>
+ Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
+<% end -%>
+ Header always unset X-Powered-By
+ Header always unset X-Runtime
+ </IfModule>
+
+ DocumentRoot /srv/leap/webapp/public
+ <% if scope.function_guess_apache_version([]) == '2.4' %>
+ <Directory /srv/leap/webapp/public>
+ AllowOverride None
+ Require all granted
+ </Directory>
+ <% end %>
+
+ # Check for maintenance file and redirect all requests
+ RewriteEngine On
+ RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
+ RewriteCond %{SCRIPT_FILENAME} !maintenance.html
+ RewriteCond %{REQUEST_URI} !/images/maintenance.jpg
+ RewriteRule ^.*$ %{DOCUMENT_ROOT}/system/maintenance.html [L]
+
+ # http://www.modrails.com/documentation/Users%20guide%20Apache.html#_passengerallowencodedslashes_lt_on_off_gt
+ AllowEncodedSlashes on
+ PassengerAllowEncodedSlashes on
+ PassengerFriendlyErrorPages off
+ SetEnv TMPDIR /var/tmp
+</VirtualHost>
diff --git a/puppet/modules/site_apache/templates/vhosts.d/common.conf.erb b/puppet/modules/site_apache/templates/vhosts.d/common.conf.erb
new file mode 100644
index 00000000..bf60e794
--- /dev/null
+++ b/puppet/modules/site_apache/templates/vhosts.d/common.conf.erb
@@ -0,0 +1,76 @@
+<VirtualHost *:80>
+ ServerName <%= @webapp_domain %>
+ ServerAlias <%= @domain_name %>
+ ServerAlias <%= @domain %>
+ ServerAlias www.<%= @domain %>
+ RewriteEngine On
+ RewriteRule ^.*$ https://<%= @webapp_domain -%>%{REQUEST_URI} [R=permanent,L]
+ CustomLog ${APACHE_LOG_DIR}/other_vhosts_access.log common
+</VirtualHost>
+
+<VirtualHost *:443>
+ ServerName <%= @webapp_domain %>
+ ServerAlias <%= @domain_name %>
+ ServerAlias <%= @domain %>
+ ServerAlias www.<%= @domain %>
+ CustomLog ${APACHE_LOG_DIR}/other_vhosts_access.log common
+
+ SSLCACertificatePath /etc/ssl/certs
+ SSLCertificateKeyFile <%= scope.lookupvar('x509::variables::keys') %>/<%= scope.lookupvar('site_config::params::commercial_cert_name') %>.key
+ SSLCertificateFile <%= scope.lookupvar('x509::variables::certs') %>/<%= scope.lookupvar('site_config::params::commercial_cert_name') %>.crt
+
+ Include include.d/ssl_common.inc
+
+ <IfModule mod_headers.c>
+<% if (defined? @services) and (@services.include? 'webapp') and (@webapp['secure']) -%>
+ Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
+<% end -%>
+ Header always unset X-Powered-By
+ Header always unset X-Runtime
+ </IfModule>
+
+<% if (defined? @services) and (@services.include? 'webapp') -%>
+ DocumentRoot /srv/leap/webapp/public
+ <% if scope.function_guess_apache_version([]) == '2.4' %>
+ <Directory /srv/leap/webapp/public>
+ AllowOverride None
+ Require all granted
+ </Directory>
+ <% end %>
+
+ RewriteEngine On
+ # Check for maintenance file and redirect all requests
+ RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
+ RewriteCond %{SCRIPT_FILENAME} !maintenance.html
+ RewriteCond %{REQUEST_URI} !/images/maintenance.jpg
+ RewriteRule ^.*$ %{DOCUMENT_ROOT}/system/maintenance.html [L]
+
+ # http://www.modrails.com/documentation/Users%20guide%20Apache.html#_passengerallowencodedslashes_lt_on_off_gt
+ AllowEncodedSlashes on
+ PassengerAllowEncodedSlashes on
+ PassengerFriendlyErrorPages off
+ SetEnv TMPDIR /var/tmp
+
+ # Allow rails assets to be cached for a very long time (since the URLs change whenever the content changes)
+ <Location /assets/>
+ Header unset ETag
+ FileETag None
+ ExpiresActive On
+ ExpiresDefault "access plus 1 year"
+ </Location>
+<% end -%>
+
+
+<% if (defined? @services) and (@services.include? 'monitor') -%>
+ <DirectoryMatch (/usr/share/nagios3/htdocs|/usr/lib/cgi-bin/nagios3|/etc/nagios3/stylesheets|/usr/share/pnp4nagios)>
+ <% if (defined? @services) and (@services.include? 'webapp') -%>
+ PassengerEnabled off
+ <% end -%>
+ AllowOverride all
+ # Nagios won't work with setting this option to "DENY",
+ # as set in conf.d/security (#4169). Therefor we allow
+ # it here, only for nagios.
+ Header set X-Frame-Options: "ALLOW"
+ </DirectoryMatch>
+<% end -%>
+</VirtualHost>
diff --git a/puppet/modules/site_apache/templates/vhosts.d/hidden_service.conf.erb b/puppet/modules/site_apache/templates/vhosts.d/hidden_service.conf.erb
new file mode 100644
index 00000000..232b1577
--- /dev/null
+++ b/puppet/modules/site_apache/templates/vhosts.d/hidden_service.conf.erb
@@ -0,0 +1,55 @@
+<VirtualHost 127.0.0.1:80>
+ ServerName <%= @tor_domain %>
+
+ <IfModule mod_headers.c>
+ Header always unset X-Powered-By
+ Header always unset X-Runtime
+ </IfModule>
+
+<% if (defined? @services) and (@services.include? 'webapp') -%>
+ DocumentRoot /srv/leap/webapp/public
+ <% if scope.function_guess_apache_version([]) == '2.4' %>
+ <Directory /srv/leap/webapp/public>
+ AllowOverride None
+ Require all granted
+ </Directory>
+ <% end %>
+
+ RewriteEngine On
+ # Check for maintenance file and redirect all requests
+ RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
+ RewriteCond %{SCRIPT_FILENAME} !maintenance.html
+ RewriteCond %{REQUEST_URI} !/images/maintenance.jpg
+ RewriteRule ^.*$ %{DOCUMENT_ROOT}/system/maintenance.html [L]
+
+ # http://www.modrails.com/documentation/Users%20guide%20Apache.html#_passengerallowencodedslashes_lt_on_off_gt
+ AllowEncodedSlashes on
+ PassengerAllowEncodedSlashes on
+ PassengerFriendlyErrorPages off
+ SetEnv TMPDIR /var/tmp
+
+ # Allow rails assets to be cached for a very long time (since the URLs change whenever the content changes)
+ <Location /assets/>
+ Header unset ETag
+ FileETag None
+ ExpiresActive On
+ ExpiresDefault "access plus 1 year"
+ </Location>
+<% end -%>
+
+<% if (defined? @services) and (@services.include? 'static') -%>
+ DocumentRoot "/srv/static/root/public"
+ <% if scope.function_guess_apache_version([]) == '2.4' %>
+ <Directory /srv/static/root/public>
+ AllowOverride None
+ Require all granted
+ </Directory>
+ <% end %>
+ AccessFileName .htaccess
+
+ Alias /provider.json /srv/leap/provider.json
+ <Location /provider.json>
+ Header set X-Minimum-Client-Version 0.5
+ </Location>
+<% end -%>
+</VirtualHost>
diff --git a/puppet/modules/site_apt/files/Debian/51unattended-upgrades-leap b/puppet/modules/site_apt/files/Debian/51unattended-upgrades-leap
new file mode 100644
index 00000000..bbaac6a2
--- /dev/null
+++ b/puppet/modules/site_apt/files/Debian/51unattended-upgrades-leap
@@ -0,0 +1,6 @@
+// this file is managed by puppet !
+
+Unattended-Upgrade::Allowed-Origins {
+ "leap.se:stable";
+}
+
diff --git a/puppet/modules/site_apt/files/keys/leap-archive.gpg b/puppet/modules/site_apt/files/keys/leap-archive.gpg
new file mode 100644
index 00000000..dd7f3be6
--- /dev/null
+++ b/puppet/modules/site_apt/files/keys/leap-archive.gpg
Binary files differ
diff --git a/puppet/modules/site_apt/files/keys/leap-experimental-archive.gpg b/puppet/modules/site_apt/files/keys/leap-experimental-archive.gpg
new file mode 100644
index 00000000..5cc9064b
--- /dev/null
+++ b/puppet/modules/site_apt/files/keys/leap-experimental-archive.gpg
Binary files differ
diff --git a/puppet/modules/site_apt/manifests/dist_upgrade.pp b/puppet/modules/site_apt/manifests/dist_upgrade.pp
new file mode 100644
index 00000000..0eb98cea
--- /dev/null
+++ b/puppet/modules/site_apt/manifests/dist_upgrade.pp
@@ -0,0 +1,17 @@
+# upgrade all packages
+class site_apt::dist_upgrade {
+
+ # facter returns 'true' as string
+ # lint:ignore:quoted_booleans
+ if $::apt_running == 'true' {
+ # lint:endignore
+ fail ('apt-get is running in background - Please wait until it finishes. Exiting.')
+ } else {
+ exec{'initial_apt_dist_upgrade':
+ command => "/usr/bin/apt-get -q -y -o 'DPkg::Options::=--force-confold' dist-upgrade",
+ refreshonly => false,
+ timeout => 1200,
+ require => Exec['apt_updated']
+ }
+ }
+}
diff --git a/puppet/modules/site_apt/manifests/init.pp b/puppet/modules/site_apt/manifests/init.pp
new file mode 100644
index 00000000..455425c1
--- /dev/null
+++ b/puppet/modules/site_apt/manifests/init.pp
@@ -0,0 +1,55 @@
+# setup apt on all nodes
+class site_apt {
+
+ $sources = hiera('sources')
+ $apt_config = $sources['apt']
+
+ # debian repo urls
+ $apt_url_basic = $apt_config['basic']
+ $apt_url_security = $apt_config['security']
+ $apt_url_backports = $apt_config['backports']
+
+ # leap repo url
+ $platform_sources = $sources['platform']
+ $apt_url_platform_basic = $platform_sources['apt']['basic']
+
+ # needed on jessie hosts for getting pnp4nagios from testing
+ if ( $::operatingsystemmajrelease == '8' ) {
+ $use_next_release = true
+ } else {
+ $use_next_release = false
+ }
+
+ class { 'apt':
+ custom_key_dir => 'puppet:///modules/site_apt/keys',
+ debian_url => $apt_url_basic,
+ security_url => $apt_url_security,
+ backports_url => $apt_url_backports,
+ use_next_release => $use_next_release
+ }
+
+ # enable http://deb.leap.se debian package repository
+ include site_apt::leap_repo
+
+ apt::apt_conf { '90disable-pdiffs':
+ content => 'Acquire::PDiffs "false";';
+ }
+
+ include ::site_apt::unattended_upgrades
+
+ # not currently used
+ #apt::sources_list { 'secondary.list':
+ # content => template('site_apt/secondary.list');
+ #}
+
+ apt::preferences_snippet { 'leap':
+ priority => 999,
+ package => '*',
+ pin => 'origin "deb.leap.se"'
+ }
+
+ # All packages should be installed after 'update_apt' is called,
+ # which does an 'apt-get update'.
+ Exec['update_apt'] -> Package <||>
+
+}
diff --git a/puppet/modules/site_apt/manifests/leap_repo.pp b/puppet/modules/site_apt/manifests/leap_repo.pp
new file mode 100644
index 00000000..5eedce45
--- /dev/null
+++ b/puppet/modules/site_apt/manifests/leap_repo.pp
@@ -0,0 +1,16 @@
+# install leap deb repo together with leap-keyring package
+# containing the apt signing key
+class site_apt::leap_repo {
+ $platform = hiera_hash('platform')
+ $major_version = $platform['major_version']
+
+ apt::sources_list { 'leap.list':
+ content => "deb ${::site_apt::apt_url_platform_basic} ${::lsbdistcodename} main\n",
+ before => Exec[refresh_apt]
+ }
+
+ package { 'leap-archive-keyring':
+ ensure => latest
+ }
+
+}
diff --git a/puppet/modules/site_apt/manifests/preferences/check_mk.pp b/puppet/modules/site_apt/manifests/preferences/check_mk.pp
new file mode 100644
index 00000000..580e0d3f
--- /dev/null
+++ b/puppet/modules/site_apt/manifests/preferences/check_mk.pp
@@ -0,0 +1,9 @@
+class site_apt::preferences::check_mk {
+
+ apt::preferences_snippet { 'check-mk':
+ package => 'check-mk-*',
+ release => "${::lsbdistcodename}-backports",
+ priority => 999;
+ }
+
+}
diff --git a/puppet/modules/site_apt/manifests/preferences/passenger.pp b/puppet/modules/site_apt/manifests/preferences/passenger.pp
new file mode 100644
index 00000000..8cd41f91
--- /dev/null
+++ b/puppet/modules/site_apt/manifests/preferences/passenger.pp
@@ -0,0 +1,14 @@
+#
+# currently, this is only used by static_site to get passenger v4.
+#
+# UPGRADE: this is not needed for jessie.
+#
+class site_apt::preferences::passenger {
+
+ apt::preferences_snippet { 'passenger':
+ package => 'libapache2-mod-passenger',
+ release => "${::lsbdistcodename}-backports",
+ priority => 999;
+ }
+
+}
diff --git a/puppet/modules/site_apt/manifests/preferences/rsyslog.pp b/puppet/modules/site_apt/manifests/preferences/rsyslog.pp
new file mode 100644
index 00000000..bfeaa7da
--- /dev/null
+++ b/puppet/modules/site_apt/manifests/preferences/rsyslog.pp
@@ -0,0 +1,13 @@
+class site_apt::preferences::rsyslog {
+
+ apt::preferences_snippet {
+ 'rsyslog_anon_depends':
+ package => 'libestr0 librelp0 rsyslog*',
+ priority => '999',
+ pin => 'release a=wheezy-backports',
+ before => Class['rsyslog::install'];
+
+ 'fixed_rsyslog_anon_package':
+ ensure => absent;
+ }
+}
diff --git a/puppet/modules/site_apt/manifests/unattended_upgrades.pp b/puppet/modules/site_apt/manifests/unattended_upgrades.pp
new file mode 100644
index 00000000..42f1f4c6
--- /dev/null
+++ b/puppet/modules/site_apt/manifests/unattended_upgrades.pp
@@ -0,0 +1,20 @@
+# configute unattended upgrades so packages from both Debian and LEAP
+# repos get upgraded unattended
+class site_apt::unattended_upgrades {
+ # override unattended-upgrades package resource to make sure
+ # that it is upgraded on every deploy (#6245)
+
+ # configure upgrades for Debian
+ class { 'apt::unattended_upgrades':
+ ensure_version => latest
+ }
+
+ # configure LEAP upgrades
+ apt::apt_conf { '51unattended-upgrades-leap':
+ source => [
+ "puppet:///modules/site_apt/${::lsbdistid}/51unattended-upgrades-leap"],
+ require => Package['unattended-upgrades'],
+ refresh_apt => false,
+ }
+
+}
diff --git a/puppet/modules/site_apt/templates/jessie/postfix.seeds b/puppet/modules/site_apt/templates/jessie/postfix.seeds
new file mode 100644
index 00000000..1a878ccc
--- /dev/null
+++ b/puppet/modules/site_apt/templates/jessie/postfix.seeds
@@ -0,0 +1 @@
+postfix postfix/main_mailer_type select No configuration
diff --git a/puppet/modules/site_apt/templates/preferences.include_squeeze b/puppet/modules/site_apt/templates/preferences.include_squeeze
new file mode 100644
index 00000000..d6d36b60
--- /dev/null
+++ b/puppet/modules/site_apt/templates/preferences.include_squeeze
@@ -0,0 +1,25 @@
+Explanation: Debian wheezy
+Package: *
+Pin: release o=Debian,n=wheezy
+Pin-Priority: 990
+
+Explanation: Debian wheezy-updates
+Package: *
+Pin: release o=Debian,n=wheezy-updates
+Pin-Priority: 990
+
+Explanation: Debian sid
+Package: *
+Pin: release o=Debian,n=sid
+Pin-Priority: 1
+
+Explanation: Debian squeeze
+Package: *
+Pin: release o=Debian,n=squeeze
+Pin-Priority: 980
+
+Explanation: Debian fallback
+Package: *
+Pin: release o=Debian
+Pin-Priority: -10
+
diff --git a/puppet/modules/site_apt/templates/secondary.list b/puppet/modules/site_apt/templates/secondary.list
new file mode 100644
index 00000000..0c024549
--- /dev/null
+++ b/puppet/modules/site_apt/templates/secondary.list
@@ -0,0 +1,3 @@
+# basic
+deb http://ftp.debian.org/debian/ <%= @lsbdistcodename %> main contrib non-free
+
diff --git a/puppet/modules/site_apt/templates/wheezy/postfix.seeds b/puppet/modules/site_apt/templates/wheezy/postfix.seeds
new file mode 100644
index 00000000..1a878ccc
--- /dev/null
+++ b/puppet/modules/site_apt/templates/wheezy/postfix.seeds
@@ -0,0 +1 @@
+postfix postfix/main_mailer_type select No configuration
diff --git a/puppet/modules/site_check_mk/files/agent/local_checks/all_hosts/run_node_tests.sh b/puppet/modules/site_check_mk/files/agent/local_checks/all_hosts/run_node_tests.sh
new file mode 100644
index 00000000..1dd0afc9
--- /dev/null
+++ b/puppet/modules/site_check_mk/files/agent/local_checks/all_hosts/run_node_tests.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+#
+# runs node tests
+
+/srv/leap/bin/run_tests --checkmk
diff --git a/puppet/modules/site_check_mk/files/agent/local_checks/couchdb/leap_couch_stats.sh b/puppet/modules/site_check_mk/files/agent/local_checks/couchdb/leap_couch_stats.sh
new file mode 100755
index 00000000..c7477b18
--- /dev/null
+++ b/puppet/modules/site_check_mk/files/agent/local_checks/couchdb/leap_couch_stats.sh
@@ -0,0 +1,122 @@
+#!/bin/bash
+#
+# todo:
+# - thresholds
+# - couch response time
+# - make CURL/URL/DBLIST_EXCLUDE vars configurable
+# - move load_nagios_utils() to helper library so we can use it from multiple scripts
+
+start_time=$(date +%s.%N)
+
+CURL='curl -s --netrc-file /etc/couchdb/couchdb.netrc'
+URL='http://127.0.0.1:5984'
+TMPFILE=$(mktemp)
+DBLIST_EXCLUDE='(user-|sessions_|tokens_|_replicator|_users)'
+PREFIX='Couchdb_'
+
+
+load_nagios_utils () {
+ # load the nagios utils
+ # in debian, the package nagios-plugins-common installs utils.sh to /usr/lib/nagios/plugins/utils.sh
+ utilsfn=
+ for d in $PROGPATH /usr/lib/nagios/plugins /usr/lib64/nagios/plugins /usr/local/nagios/libexec /opt/nagios-plugins/libexec . ; do
+ if [ -f "$d/utils.sh" ]; then
+ utilsfn=$d/utils.sh;
+ fi
+ done
+ if [ "$utilsfn" = "" ]; then
+ echo "UNKNOWN - cannot find utils.sh (part of nagios plugins)";
+ exit 3;
+ fi
+ . "$utilsfn";
+ STATE[$STATE_OK]='OK'
+ STATE[$STATE_WARNING]='Warning'
+ STATE[$STATE_CRITICAL]='Critical'
+ STATE[$STATE_UNKNOWN]='Unknown'
+ STATE[$STATE_DEPENDENT]='Dependend'
+}
+
+get_global_stats_perf () {
+ trap "localexit=3" ERR
+ local localexit db_count
+ localexit=0
+
+ # get a list of all dbs
+ $CURL -X GET $URL/_all_dbs | json_pp | egrep -v '(\[|\])' > $TMPFILE
+
+ db_count=$( wc -l < $TMPFILE)
+ excluded_db_count=$( egrep -c "$DBLIST_EXCLUDE" $TMPFILE )
+
+ echo "db_count=$db_count|excluded_db_count=$excluded_db_count"
+ return ${localexit}
+}
+
+db_stats () {
+ trap "localexit=3" ERR
+ local db db_stats doc_count del_doc_count localexit
+ localexit=0
+
+ db="$1"
+ name="$2"
+
+ if [ -z "$name" ]
+ then
+ name="$db"
+ fi
+
+ perf="$perf|${db}_docs=$( $CURL -s -X GET ${URL}/$db | json_pp |grep 'doc_count' | sed 's/[^0-9]//g' )"
+ db_stats=$( $CURL -s -X GET ${URL}/$db | json_pp )
+
+ doc_count=$( echo "$db_stats" | grep 'doc_count' | grep -v 'deleted_doc_count' | sed 's/[^0-9]//g' )
+ del_doc_count=$( echo "$db_stats" | grep 'doc_del_count' | sed 's/[^0-9]//g' )
+
+ # don't divide by zero
+ if [ $del_doc_count -eq 0 ]
+ then
+ del_doc_perc=0
+ else
+ del_doc_perc=$(( del_doc_count * 100 / doc_count ))
+ fi
+
+ bytes=$( echo "$db_stats" | grep disk_size | sed 's/[^0-9]//g' )
+ disk_size=$( echo "scale = 2; $bytes / 1024 / 1024" | bc -l )
+
+ echo -n "${localexit} ${PREFIX}${name}_database ${name}_docs=$doc_count|${name}_deleted_docs=$del_doc_count|${name}_deleted_docs_percentage=${del_doc_perc}%"
+ printf "|${name}_disksize_mb=%02.2fmb ${STATE[localexit]}: database $name\n" "$disk_size"
+
+ return ${localexit}
+}
+
+# main
+
+load_nagios_utils
+
+# per-db stats
+# get a list of all dbs
+$CURL -X GET $URL/_all_dbs | json_pp | egrep -v '(\[|\])' > $TMPFILE
+
+# get list of dbs to check
+dbs=$( egrep -v "${DBLIST_EXCLUDE}" $TMPFILE | tr -d '\n"' | sed 's/,/ /g' )
+
+for db in $dbs
+do
+ db_stats "$db"
+done
+
+# special handling for rotated dbs
+suffix=$(($(date +'%s') / (60*60*24*30)))
+db_stats "sessions_${suffix}" "sessions"
+db_stats "tokens_${suffix}" "tokens"
+
+
+# show global couchdb stats
+global_stats_perf=$(get_global_stats_perf)
+exitcode=$?
+
+end_time=$(date +%s.%N)
+duration=$( echo "scale = 2; $end_time - $start_time" | bc -l )
+
+printf "${exitcode} ${PREFIX}global_stats ${global_stats_perf}|script_duration=%02.2fs ${STATE[exitcode]}: global couchdb status\n" "$duration"
+
+rm "$TMPFILE"
+
diff --git a/puppet/modules/site_check_mk/files/agent/local_checks/mx/check_leap_mx.sh b/puppet/modules/site_check_mk/files/agent/local_checks/mx/check_leap_mx.sh
new file mode 100755
index 00000000..4711e247
--- /dev/null
+++ b/puppet/modules/site_check_mk/files/agent/local_checks/mx/check_leap_mx.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+
+WARN=1
+CRIT=5
+
+# in minutes
+MAXAGE=10
+
+STATUS[0]='OK'
+STATUS[1]='Warning'
+STATUS[2]='Critical'
+CHECKNAME='Leap_MX_Queue'
+
+WATCHDIR='/var/mail/leap-mx/Maildir/new/'
+
+
+total=`find $WATCHDIR -type f -mmin +$MAXAGE | wc -l`
+
+if [ $total -lt $WARN ]
+then
+ exitcode=0
+else
+ if [ $total -le $CRIT ]
+ then
+ exitcode=1
+ else
+ exitcode=2
+ fi
+fi
+
+echo "${exitcode} ${CHECKNAME} stale_files=${total} ${STATUS[exitcode]}: ${total} stale files (>=${MAXAGE} min) in ${WATCHDIR}."
+
diff --git a/puppet/modules/site_check_mk/files/agent/logwatch/bigcouch.cfg b/puppet/modules/site_check_mk/files/agent/logwatch/bigcouch.cfg
new file mode 100644
index 00000000..0f378a5a
--- /dev/null
+++ b/puppet/modules/site_check_mk/files/agent/logwatch/bigcouch.cfg
@@ -0,0 +1,28 @@
+/opt/bigcouch/var/log/bigcouch.log nocontext=1
+# ignore requests that are fine
+ I undefined - -.*200$
+ I undefined - -.*201$
+ I 127.0.0.1 undefined.* ok
+ I 127.0.0.1 localhost:5984 .* ok
+ # https://leap.se/code/issues/5246
+ I Shutting down group server
+ # ignore bigcouch conflict errors
+ I Error in process.*{{nocatch,conflict}
+ # ignore "Uncaught error in HTTP request: {exit, normal}" error
+ # it's suppressed in later versions of bigcouch anhow
+ # see https://leap.se/code/issues/5226
+ I Uncaught error in HTTP request: {exit,normal}
+ I Uncaught error in HTTP request: {exit,
+ # Ignore rexi_EXIT bigcouch error (Bug #6512)
+ I Error in process <[0-9.]+> on node .* with exit value: {{rexi_EXIT,{(killed|noproc|shutdown),\[{couch_db,collect_results
+ # Ignore "Generic server terminating" bigcouch message (Feature #6544)
+ I Generic server <.*> terminating
+ I {error_report,<.*>,
+ I {error_info,
+ C Uncaught error in HTTP request: {error,
+ C Response abnormally terminated: {nodedown,
+ C rexi_DOWN,noproc
+ C rexi_DOWN,noconnection
+ C error
+ C Connection attempt from disallowed node
+ W Apache CouchDB has started
diff --git a/puppet/modules/site_check_mk/files/agent/logwatch/leap_mx.cfg b/puppet/modules/site_check_mk/files/agent/logwatch/leap_mx.cfg
new file mode 100644
index 00000000..166d0230
--- /dev/null
+++ b/puppet/modules/site_check_mk/files/agent/logwatch/leap_mx.cfg
@@ -0,0 +1,4 @@
+/var/log/leap/mx.log
+ W Don't know how to deliver mail
+ W No public key, stopping the processing chain
+
diff --git a/puppet/modules/site_check_mk/files/agent/logwatch/logwatch.cfg b/puppet/modules/site_check_mk/files/agent/logwatch/logwatch.cfg
new file mode 100644
index 00000000..4f16d1bd
--- /dev/null
+++ b/puppet/modules/site_check_mk/files/agent/logwatch/logwatch.cfg
@@ -0,0 +1,31 @@
+# This file is managed by Puppet. DO NOT EDIT.
+
+# logwatch.cfg
+# This file configures mk_logwatch. Define your logfiles
+# and patterns to be looked for here.
+
+# Name one or more logfiles
+/var/log/messages
+# Patterns are indented with one space are prefixed with:
+# C: Critical messages
+# W: Warning messages
+# I: ignore these lines (OK)
+# The first match decided. Lines that do not match any pattern
+# are ignored
+ C Fail event detected on md device
+ I mdadm.*: Rebuild.*event detected
+ W mdadm\[
+ W ata.*hard resetting link
+ W ata.*soft reset failed (.*FIS failed)
+ W device-mapper: thin:.*reached low water mark
+ C device-mapper: thin:.*no free space
+
+/var/log/auth.log
+ W sshd.*Corrupted MAC on input
+
+/var/log/kern.log
+ C panic
+ C Oops
+ W generic protection rip
+ W .*Unrecovered read error - auto reallocate failed
+
diff --git a/puppet/modules/site_check_mk/files/agent/logwatch/openvpn.cfg b/puppet/modules/site_check_mk/files/agent/logwatch/openvpn.cfg
new file mode 100644
index 00000000..d99dcde9
--- /dev/null
+++ b/puppet/modules/site_check_mk/files/agent/logwatch/openvpn.cfg
@@ -0,0 +1,19 @@
+/var/log/leap/openvpn.log
+# ignore openvpn TLS initialization errors when clients
+# suddenly hangup before properly establishing
+# a tls connection
+ I ovpn-.*TLS Error: Unroutable control packet received from
+ I ovpn-.*TLS Error: TLS key negotiation failed to occur within 60 seconds \(check your network connectivity\)
+ I ovpn-.*TLS Error: TLS handshake failed
+ I ovpn-.*TLS Error: TLS object -> incoming plaintext read error
+ I ovpn-.*Fatal TLS error \(check_tls_errors_co\), restarting
+ I ovpn-.*TLS_ERROR: BIO read tls_read_plaintext error: error:140890B2:SSL routines:SSL3_GET_CLIENT_CERTIFICATE:no certificate
+ I ovpn-.*TLS_ERROR: BIO read tls_read_plaintext error: error:140890C7:SSL routines:SSL3_GET_CLIENT_CERTIFICATE:peer did not return a certificate
+ I ovpn-.*TLS Error: unknown opcode received from
+ I ovpn-.*Authenticate/Decrypt packet error: packet HMAC authentication failed
+ I ovpn-.*TLS Error: reading acknowledgement record from packet
+ I ovpn-.*TLS Error: session-id not found in packet from
+
+ I ovpn-.*SIGUSR1\[soft,tls-error\] received, client-instance restarting
+ I ovpn-.*VERIFY ERROR: depth=0, error=certificate has expired
+
diff --git a/puppet/modules/site_check_mk/files/agent/logwatch/soledad.cfg b/puppet/modules/site_check_mk/files/agent/logwatch/soledad.cfg
new file mode 100644
index 00000000..3af5045b
--- /dev/null
+++ b/puppet/modules/site_check_mk/files/agent/logwatch/soledad.cfg
@@ -0,0 +1,6 @@
+/var/log/soledad.log
+ C WSGI application error
+ C Error
+ C error
+# Removed this line because we determined it was better to ignore it (#6566)
+# W Timing out client:
diff --git a/puppet/modules/site_check_mk/files/agent/logwatch/stunnel.cfg b/puppet/modules/site_check_mk/files/agent/logwatch/stunnel.cfg
new file mode 100644
index 00000000..b1e6cf2f
--- /dev/null
+++ b/puppet/modules/site_check_mk/files/agent/logwatch/stunnel.cfg
@@ -0,0 +1,10 @@
+/var/log/leap/stunnel.log
+# check for stunnel failures
+#
+# these are temporary failures and happen very often, so we
+# ignore them until we tuned stunnel timeouts/logging,
+# see https://leap.se/code/issues/5218
+ I stunnel:.*Connection reset by peer
+ I stunnel:.*Peer suddenly disconnected
+ I stunnel:.*Connection refused
+
diff --git a/puppet/modules/site_check_mk/files/agent/logwatch/syslog/bigcouch.cfg b/puppet/modules/site_check_mk/files/agent/logwatch/syslog/bigcouch.cfg
new file mode 100644
index 00000000..f53f0780
--- /dev/null
+++ b/puppet/modules/site_check_mk/files/agent/logwatch/syslog/bigcouch.cfg
@@ -0,0 +1,5 @@
+# on one-node bigcouch setups, we'll get this msg
+# a lot, so we ignore it here until we fix
+# https://leap.se/code/issues/5244
+ I epmd: got partial packet only on file descriptor
+
diff --git a/puppet/modules/site_check_mk/files/agent/logwatch/syslog/couchdb.cfg b/puppet/modules/site_check_mk/files/agent/logwatch/syslog/couchdb.cfg
new file mode 100644
index 00000000..5f8d5b95
--- /dev/null
+++ b/puppet/modules/site_check_mk/files/agent/logwatch/syslog/couchdb.cfg
@@ -0,0 +1,2 @@
+ C /usr/local/bin/couch-doc-update.*failed
+ C /usr/local/bin/couch-doc-update.*ERROR
diff --git a/puppet/modules/site_check_mk/files/agent/logwatch/syslog_header.cfg b/puppet/modules/site_check_mk/files/agent/logwatch/syslog_header.cfg
new file mode 100644
index 00000000..f60d752b
--- /dev/null
+++ b/puppet/modules/site_check_mk/files/agent/logwatch/syslog_header.cfg
@@ -0,0 +1 @@
+/var/log/syslog
diff --git a/puppet/modules/site_check_mk/files/agent/logwatch/syslog_tail.cfg b/puppet/modules/site_check_mk/files/agent/logwatch/syslog_tail.cfg
new file mode 100644
index 00000000..7daf0cac
--- /dev/null
+++ b/puppet/modules/site_check_mk/files/agent/logwatch/syslog_tail.cfg
@@ -0,0 +1,21 @@
+# some general patterns
+ I Error: Driver 'pcspkr' is already registered, aborting...
+# ignore postfix errors on lost connection (Bug #6476)
+ I postfix/smtpd.*SSL_accept error from.*lost connection
+# ignore postfix too many errors after DATA (#6545)
+ I postfix/smtpd.*too many errors after DATA from
+ C panic
+ C Oops
+ C Error
+# ignore ipv6 icmp errors for now (Bug #6540)
+ I kernel: .*icmpv6_send: no reply to icmp error
+ C error
+ W generic protection rip
+ W .*Unrecovered read error - auto reallocate failed
+# 401 Unauthorized error logged by webapp and possible other
+# applications
+ C Unauthorized
+# catch abnormal termination of processes (due to segfault/fpe
+# signals etc).
+# see https://github.com/pixelated/pixelated-user-agent/issues/683
+ C systemd.*: main process exited, code=killed, status=
diff --git a/puppet/modules/site_check_mk/files/agent/logwatch/webapp.cfg b/puppet/modules/site_check_mk/files/agent/logwatch/webapp.cfg
new file mode 100644
index 00000000..337d9ec6
--- /dev/null
+++ b/puppet/modules/site_check_mk/files/agent/logwatch/webapp.cfg
@@ -0,0 +1,8 @@
+/var/log/leap/webapp.log
+# check for webapp errors
+ C Completed 500
+# couch connection issues
+ C webapp.*Could not connect to couch database messages due to 401 Unauthorized: {"error":"unauthorized","reason":"You are not a server admin."}
+# ignore RoutingErrors that rails throw when it can't handle a url
+# see https://leap.se/code/issues/5173
+ I webapp.*ActionController::RoutingError
diff --git a/puppet/modules/site_check_mk/files/agent/nagios_plugins/check_unix_open_fds.pl b/puppet/modules/site_check_mk/files/agent/nagios_plugins/check_unix_open_fds.pl
new file mode 100755
index 00000000..06163d49
--- /dev/null
+++ b/puppet/modules/site_check_mk/files/agent/nagios_plugins/check_unix_open_fds.pl
@@ -0,0 +1,322 @@
+#!/usr/bin/perl -w
+
+# check_unix_open_fds Nagios Plugin
+#
+# TComm - Carlos Peris Pla
+#
+# This nagios plugin is free software, and comes with ABSOLUTELY
+# NO WARRANTY. It may be used, redistributed and/or modified under
+# the terms of the GNU General Public Licence (see
+# http://www.fsf.org/licensing/licenses/gpl.txt).
+
+
+# MODULE DECLARATION
+
+use strict;
+use Nagios::Plugin;
+
+
+# FUNCTION DECLARATION
+
+sub CreateNagiosManager ();
+sub CheckArguments ();
+sub PerformCheck ();
+
+
+# CONSTANT DEFINITION
+
+use constant NAME => 'check_unix_open_fds';
+use constant VERSION => '0.1b';
+use constant USAGE => "Usage:\ncheck_unix_open_fds -w <process_threshold,application_threshold> -c <process_threshold,application_threshold>\n".
+ "\t\t[-V <version>]\n";
+use constant BLURB => "This plugin checks, in UNIX systems with the command lsof installed and with its SUID bit activated, the number\n".
+ "of file descriptors opened by an application and its processes.\n";
+use constant LICENSE => "This nagios plugin is free software, and comes with ABSOLUTELY\n".
+ "no WARRANTY. It may be used, redistributed and/or modified under\n".
+ "the terms of the GNU General Public Licence\n".
+ "(see http://www.fsf.org/licensing/licenses/gpl.txt).\n";
+use constant EXAMPLE => "\n\n".
+ "Example:\n".
+ "\n".
+ "check_unix_open_fds -a /usr/local/nagios/bin/ndo2db -w 20,75 -c 25,85\n".
+ "\n".
+ "It returns CRITICAL if number of file descriptors opened by ndo2db is higher than 85,\n".
+ "if not it returns WARNING if number of file descriptors opened by ndo2db is higher \n".
+ "than 75, if not it returns CRITICAL if number of file descriptors opened by any process\n".
+ "of ndo2db is higher than 25, if not it returns WARNING if number of file descriptors \n".
+ "opened by any process of ndo2db is higher than 20.\n".
+ "In other cases it returns OK if check has been performed succesfully.\n\n";
+
+
+# VARIABLE DEFINITION
+
+my $Nagios;
+my $Error;
+my $PluginResult;
+my $PluginOutput;
+my @WVRange;
+my @CVRange;
+
+
+# MAIN FUNCTION
+
+# Get command line arguments
+$Nagios = &CreateNagiosManager(USAGE, VERSION, BLURB, LICENSE, NAME, EXAMPLE);
+eval {$Nagios->getopts};
+
+if (!$@) {
+ # Command line parsed
+ if (&CheckArguments($Nagios, \$Error, \@WVRange, \@CVRange)) {
+ # Argument checking passed
+ $PluginResult = &PerformCheck($Nagios, \$PluginOutput, \@WVRange, \@CVRange)
+ }
+ else {
+ # Error checking arguments
+ $PluginOutput = $Error;
+ $PluginResult = UNKNOWN;
+ }
+ $Nagios->nagios_exit($PluginResult,$PluginOutput);
+}
+else {
+ # Error parsing command line
+ $Nagios->nagios_exit(UNKNOWN,$@);
+}
+
+
+
+# FUNCTION DEFINITIONS
+
+# Creates and configures a Nagios plugin object
+# Input: strings (usage, version, blurb, license, name and example) to configure argument parsing functionality
+# Return value: reference to a Nagios plugin object
+
+sub CreateNagiosManager() {
+ # Create GetOpt object
+ my $Nagios = Nagios::Plugin->new(usage => $_[0], version => $_[1], blurb => $_[2], license => $_[3], plugin => $_[4], extra => $_[5]);
+
+ # Add argument units
+ $Nagios->add_arg(spec => 'application|a=s',
+ help => 'Application path for which you want to check the number of open file descriptors',
+ required => 1);
+
+ # Add argument warning
+ $Nagios->add_arg(spec => 'warning|w=s',
+ help => "Warning thresholds. Format: <process_threshold,application_threshold>",
+ required => 1);
+ # Add argument critical
+ $Nagios->add_arg(spec => 'critical|c=s',
+ help => "Critical thresholds. Format: <process_threshold,application_threshold>",
+ required => 1);
+
+ # Return value
+ return $Nagios;
+}
+
+
+# Checks argument values and sets some default values
+# Input: Nagios Plugin object
+# Output: reference to Error description string, Memory Unit, Swap Unit, reference to WVRange ($_[4]), reference to CVRange ($_[5])
+# Return value: True if arguments ok, false if not
+
+sub CheckArguments() {
+ my ($Nagios, $Error, $WVRange, $CVRange) = @_;
+ my $commas;
+ my $units;
+ my $i;
+ my $firstpos;
+ my $secondpos;
+
+ # Check Warning thresholds list
+ $commas = $Nagios->opts->warning =~ tr/,//;
+ if ($commas !=1){
+ ${$Error} = "Invalid Warning list format. One comma is expected.";
+ return 0;
+ }
+ else{
+ $i=0;
+ $firstpos=0;
+ my $warning=$Nagios->opts->warning;
+ while ($warning =~ /[,]/g) {
+ $secondpos=pos $warning;
+ if ($secondpos - $firstpos==1){
+ @{$WVRange}[$i] = "~:";
+ }
+ else{
+ @{$WVRange}[$i] = substr $Nagios->opts->warning, $firstpos, ($secondpos-$firstpos-1);
+ }
+ $firstpos=$secondpos;
+ $i++
+ }
+ if (length($Nagios->opts->warning) - $firstpos==0){#La coma es el ultimo elemento del string
+ @{$WVRange}[$i] = "~:";
+ }
+ else{
+ @{$WVRange}[$i] = substr $Nagios->opts->warning, $firstpos, (length($Nagios->opts->warning)-$firstpos);
+ }
+
+ if (@{$WVRange}[0] !~/^(@?(\d+|(\d+|~):(\d+)?))?$/){
+ ${$Error} = "Invalid Process Warning threshold in ${$WVRange[0]}";
+ return 0;
+ }if (@{$WVRange}[1] !~/^(@?(\d+|(\d+|~):(\d+)?))?$/){
+ ${$Error} = "Invalid Application Warning threshold in ${$WVRange[1]}";
+ return 0;
+ }
+ }
+
+ # Check Critical thresholds list
+ $commas = $Nagios->opts->critical =~ tr/,//;
+ if ($commas !=1){
+ ${$Error} = "Invalid Critical list format. One comma is expected.";
+ return 0;
+ }
+ else{
+ $i=0;
+ $firstpos=0;
+ my $critical=$Nagios->opts->critical;
+ while ($critical =~ /[,]/g) {
+ $secondpos=pos $critical ;
+ if ($secondpos - $firstpos==1){
+ @{$CVRange}[$i] = "~:";
+ }
+ else{
+ @{$CVRange}[$i] =substr $Nagios->opts->critical, $firstpos, ($secondpos-$firstpos-1);
+ }
+ $firstpos=$secondpos;
+ $i++
+ }
+ if (length($Nagios->opts->critical) - $firstpos==0){#La coma es el ultimo elemento del string
+ @{$CVRange}[$i] = "~:";
+ }
+ else{
+ @{$CVRange}[$i] = substr $Nagios->opts->critical, $firstpos, (length($Nagios->opts->critical)-$firstpos);
+ }
+
+ if (@{$CVRange}[0] !~/^(@?(\d+|(\d+|~):(\d+)?))?$/) {
+ ${$Error} = "Invalid Process Critical threshold in @{$CVRange}[0]";
+ return 0;
+ }
+ if (@{$CVRange}[1] !~/^(@?(\d+|(\d+|~):(\d+)?))?$/) {
+ ${$Error} = "Invalid Application Critical threshold in @{$CVRange}[1]";
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+
+# Performs whole check:
+# Input: Nagios Plugin object, reference to Plugin output string, Application, referece to WVRange, reference to CVRange
+# Output: Plugin output string
+# Return value: Plugin return value
+
+sub PerformCheck() {
+ my ($Nagios, $PluginOutput, $WVRange, $CVRange) = @_;
+ my $Application;
+ my @AppNameSplitted;
+ my $ApplicationName;
+ my $PsCommand;
+ my $PsResult;
+ my @PsResultLines;
+ my $ProcLine;
+ my $ProcPid;
+ my $LsofCommand;
+ my $LsofResult;
+ my $ProcCount = 0;
+ my $FDCount = 0;
+ my $ProcFDAvg = 0;
+ my $PerProcMaxFD = 0;
+ my $ProcOKFlag = 0;
+ my $ProcWarningFlag = 0;
+ my $ProcCriticalFlag = 0;
+ my $OKFlag = 0;
+ my $WarningFlag = 0;
+ my $CriticalFlag = 0;
+ my $LastWarningProcFDs = 0;
+ my $LastWarningProc = -1;
+ my $LastCriticalProcFDs = 0;
+ my $LastCriticalProc = -1;
+ my $ProcPluginReturnValue = UNKNOWN;
+ my $AppPluginReturnValue = UNKNOWN;
+ my $PluginReturnValue = UNKNOWN;
+ my $PerformanceData = "";
+ my $PerfdataUnit = "FDs";
+
+ $Application = $Nagios->opts->application;
+ $PsCommand = "ps -eaf | grep $Application";
+ $PsResult = `$PsCommand`;
+ @AppNameSplitted = split(/\//, $Application);
+ $ApplicationName = $AppNameSplitted[$#AppNameSplitted];
+ @PsResultLines = split(/\n/, $PsResult);
+ if ( $#PsResultLines > 1 ) {
+ foreach my $Proc (split(/\n/, $PsResult)) {
+ if ($Proc !~ /check_unix_open_fds/ && $Proc !~ / grep /) {
+ $ProcCount += 1;
+ $ProcPid = (split(/\s+/, $Proc))[1];
+ $LsofCommand = "lsof -p $ProcPid | wc -l";
+ $LsofResult = `$LsofCommand`;
+ $LsofResult = ($LsofResult > 0 ) ? ($LsofResult - 1) : 0;
+ $FDCount += $LsofResult;
+ if ($LsofResult >= $PerProcMaxFD) { $PerProcMaxFD = $LsofResult; }
+ $ProcPluginReturnValue = $Nagios->check_threshold(check => $LsofResult,warning => @{$WVRange}[0],critical => @{$CVRange}[0]);
+ if ($ProcPluginReturnValue eq OK) {
+ $ProcOKFlag = 1;
+ }
+ elsif ($ProcPluginReturnValue eq WARNING) {
+ $ProcWarningFlag = 1;
+ if ($LsofResult >= $LastWarningProcFDs) {
+ $LastWarningProcFDs = $LsofResult;
+ $LastWarningProc = $ProcPid;
+ }
+ }
+ #if ($LsofResult >= $PCT) {
+ elsif ($ProcPluginReturnValue eq CRITICAL) {
+ $ProcCriticalFlag = 1;
+ if ($LsofResult >= $LastCriticalProcFDs) {
+ $LastCriticalProcFDs = $LsofResult;
+ $LastCriticalProc = $ProcPid;
+ }
+ }
+ }
+ }
+ if ($ProcCount) { $ProcFDAvg = int($FDCount / $ProcCount); }
+ $AppPluginReturnValue = $Nagios->check_threshold(check => $FDCount,warning => @{$WVRange}[1],critical => @{$CVRange}[1]);
+ #if ($FDCount >= $TWT) {
+ if ($AppPluginReturnValue eq OK) { $OKFlag = 1; }
+ elsif ($AppPluginReturnValue eq WARNING) { $WarningFlag = 1; }
+ elsif ($AppPluginReturnValue eq CRITICAL) { $CriticalFlag = 1; }
+
+ # PluginReturnValue and PluginOutput
+ if ($CriticalFlag) {
+ $PluginReturnValue = CRITICAL;
+ ${$PluginOutput} .= "$ApplicationName handling $FDCount files (critical threshold set to @{$CVRange}[1])";
+ }
+ elsif ($WarningFlag) {
+ $PluginReturnValue = WARNING;
+ ${$PluginOutput} .= "$ApplicationName handling $FDCount files (warning threshold set to @{$WVRange}[1])";
+ }
+ elsif ($ProcCriticalFlag) {
+ $PluginReturnValue = CRITICAL;
+ ${$PluginOutput} .= "Process ID $LastCriticalProc handling $LastCriticalProcFDs files (critical threshold set to @{$CVRange}[0])";
+ }
+ elsif ($ProcWarningFlag) {
+ $PluginReturnValue = WARNING;
+ ${$PluginOutput} .= "Process ID $LastWarningProc handling $LastWarningProcFDs files (warning threshold set to @{$WVRange}[0])";
+ }
+ elsif ($OKFlag && $ProcOKFlag) {
+ $PluginReturnValue = OK;
+ ${$PluginOutput} .= "$ApplicationName handling $FDCount files";
+ }
+ }
+ else {
+ ${$PluginOutput} .= "No existe la aplicacion $ApplicationName";
+ }
+
+
+ $PerformanceData .= "ProcCount=$ProcCount$PerfdataUnit FDCount=$FDCount$PerfdataUnit ProcFDAvg=$ProcFDAvg$PerfdataUnit PerProcMaxFD=$PerProcMaxFD$PerfdataUnit";
+
+ # Output with performance data:
+ ${$PluginOutput} .= " | $PerformanceData";
+
+ return $PluginReturnValue;
+}
diff --git a/puppet/modules/site_check_mk/files/agent/plugins/mk_logwatch.1.2.4 b/puppet/modules/site_check_mk/files/agent/plugins/mk_logwatch.1.2.4
new file mode 100755
index 00000000..3dbca322
--- /dev/null
+++ b/puppet/modules/site_check_mk/files/agent/plugins/mk_logwatch.1.2.4
@@ -0,0 +1,374 @@
+#!/usr/bin/python
+# -*- encoding: utf-8; py-indent-offset: 4 -*-
+# +------------------------------------------------------------------+
+# | ____ _ _ __ __ _ __ |
+# | / ___| |__ ___ ___| | __ | \/ | |/ / |
+# | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
+# | | |___| | | | __/ (__| < | | | | . \ |
+# | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
+# | |
+# | Copyright Mathias Kettner 2010 mk@mathias-kettner.de |
+# +------------------------------------------------------------------+
+#
+# This file is part of Check_MK.
+# The official homepage is at http://mathias-kettner.de/check_mk.
+#
+# check_mk is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation in version 2. check_mk is distributed
+# in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
+# out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. See the GNU General Public License for more de-
+# ails. You should have received a copy of the GNU General Public
+# License along with GNU Make; see the file COPYING. If not, write
+# to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+# Boston, MA 02110-1301 USA.
+
+# Call with -d for debug mode: colored output, no saving of status
+
+import sys, os, re, time
+import glob
+
+if '-d' in sys.argv[1:] or '--debug' in sys.argv[1:]:
+ tty_red = '\033[1;31m'
+ tty_green = '\033[1;32m'
+ tty_yellow = '\033[1;33m'
+ tty_blue = '\033[1;34m'
+ tty_normal = '\033[0m'
+ debug = True
+else:
+ tty_red = ''
+ tty_green = ''
+ tty_yellow = ''
+ tty_blue = ''
+ tty_normal = ''
+ debug = False
+
+# The configuration file and status file are searched
+# in the directory named by the environment variable
+# LOGWATCH_DIR. If that is not set, MK_CONFDIR is used.
+# If that is not set either, the current directory ist
+# used.
+logwatch_dir = os.getenv("LOGWATCH_DIR")
+if not logwatch_dir:
+ logwatch_dir = os.getenv("MK_CONFDIR")
+ if not logwatch_dir:
+ logwatch_dir = "."
+
+print "<<<logwatch>>>"
+
+config_filename = logwatch_dir + "/logwatch.cfg"
+status_filename = logwatch_dir + "/logwatch.state"
+config_dir = logwatch_dir + "/logwatch.d/*.cfg"
+
+def is_not_comment(line):
+ if line.lstrip().startswith('#') or \
+ line.strip() == '':
+ return False
+ return True
+
+def parse_filenames(line):
+ return line.split()
+
+def parse_pattern(level, pattern):
+ if level not in [ 'C', 'W', 'I', 'O' ]:
+ raise(Exception("Invalid pattern line '%s'" % line))
+ try:
+ compiled = re.compile(pattern)
+ except:
+ raise(Exception("Invalid regular expression in line '%s'" % line))
+ return (level, compiled)
+
+def read_config():
+ config_lines = [ line.rstrip() for line in filter(is_not_comment, file(config_filename).readlines()) ]
+ # Add config from a logwatch.d folder
+ for config_file in glob.glob(config_dir):
+ config_lines += [ line.rstrip() for line in filter(is_not_comment, file(config_file).readlines()) ]
+
+ have_filenames = False
+ config = []
+
+ for line in config_lines:
+ rewrite = False
+ if line[0].isspace(): # pattern line
+ if not have_filenames:
+ raise Exception("Missing logfile names")
+ level, pattern = line.split(None, 1)
+ if level == 'A':
+ cont_list.append(parse_cont_pattern(pattern))
+ elif level == 'R':
+ rewrite_list.append(pattern)
+ else:
+ level, compiled = parse_pattern(level, pattern)
+ cont_list = [] # List of continuation patterns
+ rewrite_list = [] # List of rewrite patterns
+ patterns.append((level, compiled, cont_list, rewrite_list))
+ else: # filename line
+ patterns = []
+ config.append((parse_filenames(line), patterns))
+ have_filenames = True
+ return config
+
+def parse_cont_pattern(pattern):
+ try:
+ return int(pattern)
+ except:
+ try:
+ return re.compile(pattern)
+ except:
+ if debug:
+ raise
+ raise Exception("Invalid regular expression in line '%s'" % pattern)
+
+# structure of statusfile
+# # LOGFILE OFFSET INODE
+# /var/log/messages|7767698|32455445
+# /var/test/x12134.log|12345|32444355
+def read_status():
+ if debug:
+ return {}
+
+ status = {}
+ for line in file(status_filename):
+ # TODO: Remove variants with spaces. rsplit is
+ # not portable. split fails if logfilename contains
+ # spaces
+ inode = -1
+ try:
+ parts = line.split('|')
+ filename = parts[0]
+ offset = parts[1]
+ if len(parts) >= 3:
+ inode = parts[2]
+
+ except:
+ try:
+ filename, offset = line.rsplit(None, 1)
+ except:
+ filename, offset = line.split(None, 1)
+ status[filename] = int(offset), int(inode)
+ return status
+
+def save_status(status):
+ f = file(status_filename, "w")
+ for filename, (offset, inode) in status.items():
+ f.write("%s|%d|%d\n" % (filename, offset, inode))
+
+pushed_back_line = None
+def next_line(f):
+ global pushed_back_line
+ if pushed_back_line != None:
+ line = pushed_back_line
+ pushed_back_line = None
+ return line
+ else:
+ try:
+ line = f.next()
+ return line
+ except:
+ return None
+
+
+def process_logfile(logfile, patterns):
+ global pushed_back_line
+
+ # Look at which file offset we have finished scanning
+ # the logfile last time. If we have never seen this file
+ # before, we set the offset to -1
+ offset, prev_inode = status.get(logfile, (-1, -1))
+ try:
+ fl = os.open(logfile, os.O_RDONLY)
+ inode = os.fstat(fl)[1] # 1 = st_ino
+ except:
+ if debug:
+ raise
+ print "[[[%s:cannotopen]]]" % logfile
+ return
+
+ print "[[[%s]]]" % logfile
+
+ # Seek to the current end in order to determine file size
+ current_end = os.lseek(fl, 0, 2) # os.SEEK_END not available in Python 2.4
+ status[logfile] = current_end, inode
+
+ # If we have never seen this file before, we just set the
+ # current pointer to the file end. We do not want to make
+ # a fuss about ancient log messages...
+ if offset == -1:
+ if not debug:
+ return
+ else:
+ offset = 0
+
+
+ # If the inode of the logfile has changed it has appearently
+ # been started from new (logfile rotation). At least we must
+ # assume that. In some rare cases (restore of a backup, etc)
+ # we are wrong and resend old log messages
+ if prev_inode >= 0 and inode != prev_inode:
+ offset = 0
+
+ # Our previously stored offset is the current end ->
+ # no new lines in this file
+ if offset == current_end:
+ return # nothing new
+
+ # If our offset is beyond the current end, the logfile has been
+ # truncated or wrapped while keeping the same inode. We assume
+ # that it contains all new data in that case and restart from
+ # offset 0.
+ if offset > current_end:
+ offset = 0
+
+ # now seek to offset where interesting data begins
+ os.lseek(fl, offset, 0) # os.SEEK_SET not available in Python 2.4
+ f = os.fdopen(fl)
+ worst = -1
+ outputtxt = ""
+ lines_parsed = 0
+ start_time = time.time()
+
+ while True:
+ line = next_line(f)
+ if line == None:
+ break # End of file
+
+ lines_parsed += 1
+ # Check if maximum number of new log messages is exceeded
+ if opt_maxlines != None and lines_parsed > opt_maxlines:
+ outputtxt += "%s Maximum number (%d) of new log messages exceeded.\n" % (
+ opt_overflow, opt_maxlines)
+ worst = max(worst, opt_overflow_level)
+ os.lseek(fl, 0, 2) # Seek to end of file, skip all other messages
+ break
+
+ # Check if maximum processing time (per file) is exceeded. Check only
+ # every 100'th line in order to save system calls
+ if opt_maxtime != None and lines_parsed % 100 == 10 \
+ and time.time() - start_time > opt_maxtime:
+ outputtxt += "%s Maximum parsing time (%.1f sec) of this log file exceeded.\n" % (
+ opt_overflow, opt_maxtime)
+ worst = max(worst, opt_overflow_level)
+ os.lseek(fl, 0, 2) # Seek to end of file, skip all other messages
+ break
+
+ level = "."
+ for lev, pattern, cont_patterns, replacements in patterns:
+ matches = pattern.search(line[:-1])
+ if matches:
+ level = lev
+ levelint = {'C': 2, 'W': 1, 'O': 0, 'I': -1, '.': -1}[lev]
+ worst = max(levelint, worst)
+
+ # Check for continuation lines
+ for cont_pattern in cont_patterns:
+ if type(cont_pattern) == int: # add that many lines
+ for x in range(cont_pattern):
+ cont_line = next_line(f)
+ if cont_line == None: # end of file
+ break
+ line = line[:-1] + "\1" + cont_line
+
+ else: # pattern is regex
+ while True:
+ cont_line = next_line(f)
+ if cont_line == None: # end of file
+ break
+ elif cont_pattern.search(cont_line[:-1]):
+ line = line[:-1] + "\1" + cont_line
+ else:
+ pushed_back_line = cont_line # sorry for stealing this line
+ break
+
+ # Replacement
+ for replace in replacements:
+ line = replace.replace('\\0', line) + "\n"
+ for nr, group in enumerate(matches.groups()):
+ line = line.replace('\\%d' % (nr+1), group)
+
+ break # matching rule found and executed
+
+ color = {'C': tty_red, 'W': tty_yellow, 'O': tty_green, 'I': tty_blue, '.': ''}[level]
+ if debug:
+ line = line.replace("\1", "\nCONT:")
+ if level == "I":
+ level = "."
+ if opt_nocontext and level == '.':
+ continue
+ outputtxt += "%s%s %s%s\n" % (color, level, line[:-1], tty_normal)
+
+ new_offset = os.lseek(fl, 0, 1) # os.SEEK_CUR not available in Python 2.4
+ status[logfile] = new_offset, inode
+
+ # output all lines if at least one warning, error or ok has been found
+ if worst > -1:
+ sys.stdout.write(outputtxt)
+ sys.stdout.flush()
+
+try:
+ config = read_config()
+except Exception, e:
+ if debug:
+ raise
+ print "CANNOT READ CONFIG FILE: %s" % e
+ sys.exit(1)
+
+# Simply ignore errors in the status file. In case of a corrupted status file we simply begin
+# with an empty status. That keeps the monitoring up and running - even if we might loose a
+# message in the extreme case of a corrupted status file.
+try:
+ status = read_status()
+except Exception, e:
+ status = {}
+
+
+# The filename line may contain options like 'maxlines=100' or 'maxtime=10'
+for filenames, patterns in config:
+ # Initialize options with default values
+ opt_maxlines = None
+ opt_maxtime = None
+ opt_regex = None
+ opt_overflow = 'C'
+ opt_overflow_level = 2
+ opt_nocontext = False
+ try:
+ options = [ o.split('=', 1) for o in filenames if '=' in o ]
+ for key, value in options:
+ if key == 'maxlines':
+ opt_maxlines = int(value)
+ elif key == 'maxtime':
+ opt_maxtime = float(value)
+ elif key == 'overflow':
+ if value not in [ 'C', 'I', 'W', 'O' ]:
+ raise Exception("Invalid value %s for overflow. Allowed are C, I, O and W" % value)
+ opt_overflow = value
+ opt_overflow_level = {'C':2, 'W':1, 'O':0, 'I':0}[value]
+ elif key == 'regex':
+ opt_regex = re.compile(value)
+ elif key == 'iregex':
+ opt_regex = re.compile(value, re.I)
+ elif key == 'nocontext':
+ opt_nocontext = True
+ else:
+ raise Exception("Invalid option %s" % key)
+ except Exception, e:
+ if debug:
+ raise
+ print "INVALID CONFIGURATION: %s" % e
+ sys.exit(1)
+
+
+ for glob in filenames:
+ if '=' in glob:
+ continue
+ logfiles = [ l.strip() for l in os.popen("ls %s 2>/dev/null" % glob).readlines() ]
+ if opt_regex:
+ logfiles = [ f for f in logfiles if opt_regex.search(f) ]
+ if len(logfiles) == 0:
+ print '[[[%s:missing]]]' % glob
+ else:
+ for logfile in logfiles:
+ process_logfile(logfile, patterns)
+
+if not debug:
+ save_status(status)
diff --git a/puppet/modules/site_check_mk/files/extra_service_conf.mk b/puppet/modules/site_check_mk/files/extra_service_conf.mk
new file mode 100644
index 00000000..c7120a96
--- /dev/null
+++ b/puppet/modules/site_check_mk/files/extra_service_conf.mk
@@ -0,0 +1,14 @@
+# retry 3 times before setting a service into a hard state
+# and send out notification
+extra_service_conf["max_check_attempts"] = [
+ ("4", ALL_HOSTS , ALL_SERVICES )
+]
+
+#
+# run check_mk_agent every 4 minutes if it terminates successfully.
+# see https://leap.se/code/issues/6539 for the rationale
+#
+extra_service_conf["normal_check_interval"] = [
+ ("4", ALL_HOSTS , "Check_MK" )
+]
+
diff --git a/puppet/modules/site_check_mk/files/ignored_services.mk b/puppet/modules/site_check_mk/files/ignored_services.mk
new file mode 100644
index 00000000..35dc4433
--- /dev/null
+++ b/puppet/modules/site_check_mk/files/ignored_services.mk
@@ -0,0 +1,3 @@
+ignored_services = [
+ ( ALL_HOSTS, [ "NTP Time" ] )
+]
diff --git a/puppet/modules/site_check_mk/manifests/agent.pp b/puppet/modules/site_check_mk/manifests/agent.pp
new file mode 100644
index 00000000..b95d5d64
--- /dev/null
+++ b/puppet/modules/site_check_mk/manifests/agent.pp
@@ -0,0 +1,35 @@
+# installs check-mk agent
+class site_check_mk::agent {
+
+ $ssh_hash = hiera('ssh')
+ $pubkey = $ssh_hash['authorized_keys']['monitor']['key']
+ $type = $ssh_hash['authorized_keys']['monitor']['type']
+
+
+ # /usr/bin/mk-job depends on /usr/bin/time
+ ensure_packages('time')
+
+ class { 'site_apt::preferences::check_mk': } ->
+
+ class { 'check_mk::agent':
+ agent_package_name => 'check-mk-agent',
+ agent_logwatch_package_name => 'check-mk-agent-logwatch',
+ method => 'ssh',
+ authdir => '/root/.ssh',
+ authfile => 'authorized_keys',
+ register_agent => false,
+ require => Package['time']
+ } ->
+
+ class { 'site_check_mk::agent::mrpe': } ->
+ class { 'site_check_mk::agent::logwatch': } ->
+
+ file {
+ [ '/srv/leap/nagios', '/srv/leap/nagios/plugins' ]:
+ ensure => directory;
+ '/usr/lib/check_mk_agent/local/run_node_tests.sh':
+ source => 'puppet:///modules/site_check_mk/agent/local_checks/all_hosts/run_node_tests.sh',
+ mode => '0755';
+ }
+
+}
diff --git a/puppet/modules/site_check_mk/manifests/agent/couchdb.pp b/puppet/modules/site_check_mk/manifests/agent/couchdb.pp
new file mode 100644
index 00000000..1554fd3c
--- /dev/null
+++ b/puppet/modules/site_check_mk/manifests/agent/couchdb.pp
@@ -0,0 +1,34 @@
+# configure logwatch and nagios checks for couchdb (both bigcouch and plain
+# couchdb installations)
+class site_check_mk::agent::couchdb {
+
+ concat::fragment { 'syslog_couchdb':
+ source => 'puppet:///modules/site_check_mk/agent/logwatch/syslog/couchdb.cfg',
+ target => '/etc/check_mk/logwatch.d/syslog.cfg',
+ order => '02';
+ }
+
+ # check different couchdb stats
+ file { '/usr/lib/check_mk_agent/local/leap_couch_stats.sh':
+ source => 'puppet:///modules/site_check_mk/agent/local_checks/couchdb/leap_couch_stats.sh',
+ mode => '0755',
+ require => Package['check_mk-agent']
+ }
+
+ # check open files for bigcouch proc
+ include site_check_mk::agent::package::perl_plugin
+ file { '/srv/leap/nagios/plugins/check_unix_open_fds.pl':
+ source => 'puppet:///modules/site_check_mk/agent/nagios_plugins/check_unix_open_fds.pl',
+ mode => '0755'
+ }
+ augeas {
+ 'Couchdb_open_files':
+ incl => '/etc/check_mk/mrpe.cfg',
+ lens => 'Spacevars.lns',
+ changes => [
+ 'rm /files/etc/check_mk/mrpe.cfg/Couchdb_open_files',
+ 'set Couchdb_open_files \'/srv/leap/nagios/plugins/check_unix_open_fds.pl -a beam -w 28672,28672 -c 30720,30720\'' ],
+ require => File['/etc/check_mk/mrpe.cfg'];
+ }
+
+}
diff --git a/puppet/modules/site_check_mk/manifests/agent/couchdb/bigcouch.pp b/puppet/modules/site_check_mk/manifests/agent/couchdb/bigcouch.pp
new file mode 100644
index 00000000..82c3ac72
--- /dev/null
+++ b/puppet/modules/site_check_mk/manifests/agent/couchdb/bigcouch.pp
@@ -0,0 +1,49 @@
+# configure logwatch and nagios checks for bigcouch
+class site_check_mk::agent::couchdb::bigcouch {
+
+ # watch bigcouch logs
+ # currently disabled because bigcouch is too noisy
+ # see https://leap.se/code/issues/7375 for more details
+ # and site_config::remove_files for removing leftovers
+ #file { '/etc/check_mk/logwatch.d/bigcouch.cfg':
+ # source => 'puppet:///modules/site_check_mk/agent/logwatch/bigcouch.cfg',
+ #}
+
+ # check syslog msg from:
+ # - empd
+ # - /usr/local/bin/couch-doc-update
+ concat::fragment { 'syslog_bigcouch':
+ source => 'puppet:///modules/site_check_mk/agent/logwatch/syslog/bigcouch.cfg',
+ target => '/etc/check_mk/logwatch.d/syslog.cfg',
+ order => '02';
+ }
+
+ # check bigcouch processes
+ augeas {
+ 'Bigcouch_epmd_procs':
+ incl => '/etc/check_mk/mrpe.cfg',
+ lens => 'Spacevars.lns',
+ changes => [
+ 'rm /files/etc/check_mk/mrpe.cfg/Bigcouch_epmd_procs',
+ 'set Bigcouch_epmd_procs \'/usr/lib/nagios/plugins/check_procs -w 1:1 -c 1:1 -a /opt/bigcouch/erts-5.9.1/bin/epmd\'' ],
+ require => File['/etc/check_mk/mrpe.cfg'];
+ 'Bigcouch_beam_procs':
+ incl => '/etc/check_mk/mrpe.cfg',
+ lens => 'Spacevars.lns',
+ changes => [
+ 'rm /files/etc/check_mk/mrpe.cfg/Bigcouch_beam_procs',
+ 'set Bigcouch_beam_procs \'/usr/lib/nagios/plugins/check_procs -w 1:1 -c 1:1 -a /opt/bigcouch/erts-5.9.1/bin/beam\'' ],
+ require => File['/etc/check_mk/mrpe.cfg'];
+ }
+
+ augeas {
+ 'Bigcouch_open_files':
+ incl => '/etc/check_mk/mrpe.cfg',
+ lens => 'Spacevars.lns',
+ changes => [
+ 'rm /files/etc/check_mk/mrpe.cfg/Bigcouch_open_files',
+ 'set Bigcouch_open_files \'/srv/leap/nagios/plugins/check_unix_open_fds.pl -a beam -w 28672,28672 -c 30720,30720\'' ],
+ require => File['/etc/check_mk/mrpe.cfg'];
+ }
+
+}
diff --git a/puppet/modules/site_check_mk/manifests/agent/couchdb/plain.pp b/puppet/modules/site_check_mk/manifests/agent/couchdb/plain.pp
new file mode 100644
index 00000000..3ec2267b
--- /dev/null
+++ b/puppet/modules/site_check_mk/manifests/agent/couchdb/plain.pp
@@ -0,0 +1,23 @@
+# configure logwatch and nagios checks for plain single couchdb master
+class site_check_mk::agent::couchdb::plain {
+
+ # remove bigcouch leftovers
+ augeas {
+ 'Bigcouch_epmd_procs':
+ incl => '/etc/check_mk/mrpe.cfg',
+ lens => 'Spacevars.lns',
+ changes => 'rm /files/etc/check_mk/mrpe.cfg/Bigcouch_epmd_procs',
+ require => File['/etc/check_mk/mrpe.cfg'];
+ 'Bigcouch_beam_procs':
+ incl => '/etc/check_mk/mrpe.cfg',
+ lens => 'Spacevars.lns',
+ changes => 'rm /files/etc/check_mk/mrpe.cfg/Bigcouch_beam_procs',
+ require => File['/etc/check_mk/mrpe.cfg'];
+ 'Bigcouch_open_files':
+ incl => '/etc/check_mk/mrpe.cfg',
+ lens => 'Spacevars.lns',
+ changes => 'rm /files/etc/check_mk/mrpe.cfg/Bigcouch_open_files',
+ require => File['/etc/check_mk/mrpe.cfg'];
+ }
+
+}
diff --git a/puppet/modules/site_check_mk/manifests/agent/haproxy.pp b/puppet/modules/site_check_mk/manifests/agent/haproxy.pp
new file mode 100644
index 00000000..6d52efba
--- /dev/null
+++ b/puppet/modules/site_check_mk/manifests/agent/haproxy.pp
@@ -0,0 +1,15 @@
+class site_check_mk::agent::haproxy {
+
+ include site_check_mk::agent::package::nagios_plugins_contrib
+
+ # local nagios plugin checks via mrpe
+ augeas { 'haproxy':
+ incl => '/etc/check_mk/mrpe.cfg',
+ lens => 'Spacevars.lns',
+ changes => [
+ 'rm /files/etc/check_mk/mrpe.cfg/Haproxy',
+ 'set Haproxy \'/usr/lib/nagios/plugins/check_haproxy -u "http://localhost:8000/haproxy;csv"\'' ],
+ require => File['/etc/check_mk/mrpe.cfg'];
+ }
+
+}
diff --git a/puppet/modules/site_check_mk/manifests/agent/haveged.pp b/puppet/modules/site_check_mk/manifests/agent/haveged.pp
new file mode 100644
index 00000000..cacbea8c
--- /dev/null
+++ b/puppet/modules/site_check_mk/manifests/agent/haveged.pp
@@ -0,0 +1,15 @@
+class site_check_mk::agent::haveged {
+
+# check haveged process
+ augeas {
+ 'haveged_proc':
+ incl => '/etc/check_mk/mrpe.cfg',
+ lens => 'Spacevars.lns',
+ changes => [
+ 'rm /files/etc/check_mk/mrpe.cfg/haveged_proc',
+ 'set haveged_proc \'/usr/lib/nagios/plugins/check_procs -w 1:1 -c 1:1 -a /usr/sbin/haveged\'' ],
+ require => File['/etc/check_mk/mrpe.cfg'];
+
+ }
+
+}
diff --git a/puppet/modules/site_check_mk/manifests/agent/logwatch.pp b/puppet/modules/site_check_mk/manifests/agent/logwatch.pp
new file mode 100644
index 00000000..423cace2
--- /dev/null
+++ b/puppet/modules/site_check_mk/manifests/agent/logwatch.pp
@@ -0,0 +1,36 @@
+class site_check_mk::agent::logwatch {
+ # Deploy mk_logwatch 1.2.4 so we can split the config
+ # into multiple config files in /etc/check_mk/logwatch.d
+ # see https://leap.se/code/issues/5135
+
+ file { '/usr/lib/check_mk_agent/plugins/mk_logwatch':
+ source => 'puppet:///modules/site_check_mk/agent/plugins/mk_logwatch.1.2.4',
+ mode => '0755',
+ require => Package['check-mk-agent-logwatch']
+ }
+
+ # only config files that watch a distinct logfile should go in logwatch.d/
+ file { '/etc/check_mk/logwatch.d':
+ ensure => directory,
+ recurse => true,
+ purge => true,
+ require => Package['check-mk-agent-logwatch']
+ }
+
+ # service that share a common logfile (i.e. /var/log/syslog) need to get
+ # concanated in one file, otherwise the last file sourced will override
+ # the config before
+ # see mk_logwatch: "logwatch.cfg overwrites config files in logwatch.d",
+ # https://leap.se/code/issues/5155
+
+ # first, we need to deploy a custom logwatch.cfg that doesn't include
+ # a section about /var/log/syslog
+
+ file { '/etc/check_mk/logwatch.cfg':
+ source => 'puppet:///modules/site_check_mk/agent/logwatch/logwatch.cfg',
+ require => Package['check_mk-agent-logwatch']
+ }
+
+ include concat::setup
+ include site_check_mk::agent::logwatch::syslog
+}
diff --git a/puppet/modules/site_check_mk/manifests/agent/logwatch/syslog.pp b/puppet/modules/site_check_mk/manifests/agent/logwatch/syslog.pp
new file mode 100644
index 00000000..c927780d
--- /dev/null
+++ b/puppet/modules/site_check_mk/manifests/agent/logwatch/syslog.pp
@@ -0,0 +1,18 @@
+class site_check_mk::agent::logwatch::syslog {
+
+ concat { '/etc/check_mk/logwatch.d/syslog.cfg':
+ warn => true
+ }
+
+ concat::fragment { 'syslog_header':
+ source => 'puppet:///modules/site_check_mk/agent/logwatch/syslog_header.cfg',
+ target => '/etc/check_mk/logwatch.d/syslog.cfg',
+ order => '01';
+ }
+ concat::fragment { 'syslog_tail':
+ source => 'puppet:///modules/site_check_mk/agent/logwatch/syslog_tail.cfg',
+ target => '/etc/check_mk/logwatch.d/syslog.cfg',
+ order => '99';
+ }
+
+}
diff --git a/puppet/modules/site_check_mk/manifests/agent/mrpe.pp b/puppet/modules/site_check_mk/manifests/agent/mrpe.pp
new file mode 100644
index 00000000..5e1f087a
--- /dev/null
+++ b/puppet/modules/site_check_mk/manifests/agent/mrpe.pp
@@ -0,0 +1,24 @@
+class site_check_mk::agent::mrpe {
+ # check_mk can use standard nagios plugins using
+ # a wrapper called mrpe
+ # see http://mathias-kettner.de/checkmk_mrpe.html
+
+ package { 'nagios-plugins-basic':
+ ensure => latest,
+ }
+
+ file { '/etc/check_mk/mrpe.cfg':
+ ensure => present,
+ require => Package['check-mk-agent']
+ } ->
+
+ augeas {
+ 'Apt':
+ incl => '/etc/check_mk/mrpe.cfg',
+ lens => 'Spacevars.lns',
+ changes => [
+ 'rm /files/etc/check_mk/mrpe.cfg/APT',
+ 'set APT \'/usr/lib/nagios/plugins/check_apt\'' ];
+ }
+
+}
diff --git a/puppet/modules/site_check_mk/manifests/agent/mx.pp b/puppet/modules/site_check_mk/manifests/agent/mx.pp
new file mode 100644
index 00000000..20cbcade
--- /dev/null
+++ b/puppet/modules/site_check_mk/manifests/agent/mx.pp
@@ -0,0 +1,27 @@
+# check check_mk agent checks for mx service
+class site_check_mk::agent::mx {
+
+ # watch logs
+ file { '/etc/check_mk/logwatch.d/leap_mx.cfg':
+ source => 'puppet:///modules/site_check_mk/agent/logwatch/leap_mx.cfg',
+ }
+
+ # local nagios plugin checks via mrpe
+ # removed because leap_cli integrates a check for running mx procs already,
+ # which is also integrated into nagios (called "Mx/Are_MX_daemons_running")
+ augeas {
+ 'Leap_MX_Procs':
+ incl => '/etc/check_mk/mrpe.cfg',
+ lens => 'Spacevars.lns',
+ changes => 'rm /files/etc/check_mk/mrpe.cfg/Leap_MX_Procs',
+ require => File['/etc/check_mk/mrpe.cfg'];
+ }
+
+ # check stale files in queue dir
+ file { '/usr/lib/check_mk_agent/local/check_leap_mx.sh':
+ source => 'puppet:///modules/site_check_mk/agent/local_checks/mx/check_leap_mx.sh',
+ mode => '0755',
+ require => Package['check_mk-agent']
+ }
+
+}
diff --git a/puppet/modules/site_check_mk/manifests/agent/openvpn.pp b/puppet/modules/site_check_mk/manifests/agent/openvpn.pp
new file mode 100644
index 00000000..0596a497
--- /dev/null
+++ b/puppet/modules/site_check_mk/manifests/agent/openvpn.pp
@@ -0,0 +1,10 @@
+class site_check_mk::agent::openvpn {
+
+ # check syslog
+ concat::fragment { 'syslog_openpvn':
+ source => 'puppet:///modules/site_check_mk/agent/logwatch/openvpn.cfg',
+ target => '/etc/check_mk/logwatch.d/syslog.cfg',
+ order => '02';
+ }
+
+}
diff --git a/puppet/modules/site_check_mk/manifests/agent/package/nagios_plugins_contrib.pp b/puppet/modules/site_check_mk/manifests/agent/package/nagios_plugins_contrib.pp
new file mode 100644
index 00000000..95a60d17
--- /dev/null
+++ b/puppet/modules/site_check_mk/manifests/agent/package/nagios_plugins_contrib.pp
@@ -0,0 +1,5 @@
+class site_check_mk::agent::package::nagios_plugins_contrib {
+ package { 'nagios-plugins-contrib':
+ ensure => installed,
+ }
+}
diff --git a/puppet/modules/site_check_mk/manifests/agent/package/perl_plugin.pp b/puppet/modules/site_check_mk/manifests/agent/package/perl_plugin.pp
new file mode 100644
index 00000000..4feda375
--- /dev/null
+++ b/puppet/modules/site_check_mk/manifests/agent/package/perl_plugin.pp
@@ -0,0 +1,5 @@
+class site_check_mk::agent::package::perl_plugin {
+ package { 'libnagios-plugin-perl':
+ ensure => installed,
+ }
+}
diff --git a/puppet/modules/site_check_mk/manifests/agent/soledad.pp b/puppet/modules/site_check_mk/manifests/agent/soledad.pp
new file mode 100644
index 00000000..f4a3f3a6
--- /dev/null
+++ b/puppet/modules/site_check_mk/manifests/agent/soledad.pp
@@ -0,0 +1,17 @@
+class site_check_mk::agent::soledad {
+
+ file { '/etc/check_mk/logwatch.d/soledad.cfg':
+ source => 'puppet:///modules/site_check_mk/agent/logwatch/soledad.cfg',
+ }
+
+ # local nagios plugin checks via mrpe
+
+ augeas { 'Soledad_Procs':
+ incl => '/etc/check_mk/mrpe.cfg',
+ lens => 'Spacevars.lns',
+ changes => [
+ 'rm /files/etc/check_mk/mrpe.cfg/Soledad_Procs',
+ 'set Soledad_Procs \'/usr/lib/nagios/plugins/check_procs -w 1:1 -c 1:1 -a "/usr/bin/python /usr/bin/twistd --uid=soledad --gid=soledad --pidfile=/var/run/soledad.pid --logfile=/var/log/soledad.log web --wsgi=leap.soledad.server.application --port=ssl:2323:privateKey=/etc/x509/keys/leap.key:certKey=/etc/x509/certs/leap.crt:sslmethod=SSLv23_METHOD"\'' ],
+ require => File['/etc/check_mk/mrpe.cfg'];
+ }
+}
diff --git a/puppet/modules/site_check_mk/manifests/agent/stunnel.pp b/puppet/modules/site_check_mk/manifests/agent/stunnel.pp
new file mode 100644
index 00000000..7f765771
--- /dev/null
+++ b/puppet/modules/site_check_mk/manifests/agent/stunnel.pp
@@ -0,0 +1,9 @@
+class site_check_mk::agent::stunnel {
+
+ concat::fragment { 'syslog_stunnel':
+ source => 'puppet:///modules/site_check_mk/agent/logwatch/stunnel.cfg',
+ target => '/etc/check_mk/logwatch.d/syslog.cfg',
+ order => '02';
+ }
+
+}
diff --git a/puppet/modules/site_check_mk/manifests/agent/webapp.pp b/puppet/modules/site_check_mk/manifests/agent/webapp.pp
new file mode 100644
index 00000000..9bf3b197
--- /dev/null
+++ b/puppet/modules/site_check_mk/manifests/agent/webapp.pp
@@ -0,0 +1,15 @@
+class site_check_mk::agent::webapp {
+
+ # remove leftovers of webapp python checks
+ file {
+ [ '/usr/lib/check_mk_agent/local/nagios-webapp_login.py',
+ '/usr/lib/check_mk_agent/local/soledad_sync.py' ]:
+ ensure => absent
+ }
+
+ # watch logs
+ file { '/etc/check_mk/logwatch.d/webapp.cfg':
+ source => 'puppet:///modules/site_check_mk/agent/logwatch/webapp.cfg',
+ }
+
+}
diff --git a/puppet/modules/site_check_mk/manifests/server.pp b/puppet/modules/site_check_mk/manifests/server.pp
new file mode 100644
index 00000000..7ff9eb4a
--- /dev/null
+++ b/puppet/modules/site_check_mk/manifests/server.pp
@@ -0,0 +1,103 @@
+# setup check_mk on the monitoring server
+class site_check_mk::server {
+
+ $ssh_hash = hiera('ssh')
+ $pubkey = $ssh_hash['authorized_keys']['monitor']['key']
+ $type = $ssh_hash['authorized_keys']['monitor']['type']
+ $seckey = $ssh_hash['monitor']['private_key']
+
+ $nagios_hiera = hiera_hash('nagios')
+ $hosts = $nagios_hiera['hosts']
+
+ $all_hosts = inline_template ('<% @hosts.keys.sort.each do |key| -%><% if @hosts[key]["environment"] != "disabled" %>"<%= @hosts[key]["domain_internal"] %>", <% end -%><% end -%>')
+ $domains_internal = $nagios_hiera['domains_internal']
+ $environments = $nagios_hiera['environments']
+
+ package { 'check-mk-server':
+ ensure => installed,
+ }
+
+ # we don't use check-mk-multisite, and the jessie version
+ # of this config file breaks with apache 2.4
+ # until https://gitlab.com/shared-puppet-modules-group/apache/issues/11
+ # is not fixed, we need to use a generic file type here
+ #apache::config::global { 'check-mk-multisite.conf':
+ # ensure => absent
+ #}
+
+ file { '/etc/apache2/conf-enabled/check-mk-multisite.conf':
+ ensure => absent,
+ require => Package['check-mk-server'];
+ }
+
+ # override paths to use the system check_mk rather than OMD
+ class { 'check_mk::config':
+ site => '',
+ etc_dir => '/etc',
+ nagios_subdir => 'nagios3',
+ bin_dir => '/usr/bin',
+ host_groups => undef,
+ use_storedconfigs => false,
+ inventory_only_on_changes => false,
+ require => Package['check-mk-server']
+ }
+
+ Exec['check_mk-refresh'] ->
+ Exec['check_mk-refresh-inventory-daily'] ->
+ Exec['check_mk-reload'] ->
+ Service['nagios']
+
+ file {
+ '/etc/check_mk/conf.d/use_ssh.mk':
+ content => template('site_check_mk/use_ssh.mk'),
+ notify => Exec['check_mk-refresh'],
+ require => Package['check-mk-server'];
+ '/etc/check_mk/conf.d/hostgroups.mk':
+ content => template('site_check_mk/hostgroups.mk'),
+ notify => Exec['check_mk-refresh'],
+ require => Package['check-mk-server'];
+ '/etc/check_mk/conf.d/host_contactgroups.mk':
+ content => template('site_check_mk/host_contactgroups.mk'),
+ notify => Exec['check_mk-refresh'],
+ require => Package['check-mk-server'];
+ '/etc/check_mk/conf.d/ignored_services.mk':
+ source => 'puppet:///modules/site_check_mk/ignored_services.mk',
+ notify => Exec['check_mk-refresh'],
+ require => Package['check-mk-server'];
+ '/etc/check_mk/conf.d/extra_service_conf.mk':
+ source => 'puppet:///modules/site_check_mk/extra_service_conf.mk',
+ notify => Exec['check_mk-refresh'],
+ require => Package['check-mk-server'];
+ '/etc/check_mk/conf.d/extra_host_conf.mk':
+ content => template('site_check_mk/extra_host_conf.mk'),
+ notify => Exec['check_mk-refresh'],
+ require => Package['check-mk-server'];
+
+ '/etc/check_mk/all_hosts_static':
+ content => $all_hosts,
+ notify => Exec['check_mk-refresh'],
+ require => Package['check-mk-server'];
+
+ '/etc/check_mk/.ssh':
+ ensure => directory,
+ require => Package['check-mk-server'];
+ '/etc/check_mk/.ssh/id_rsa':
+ content => $seckey,
+ owner => 'nagios',
+ mode => '0600',
+ require => Package['check-mk-server'];
+ '/etc/check_mk/.ssh/id_rsa.pub':
+ content => "${type} ${pubkey} monitor",
+ owner => 'nagios',
+ mode => '0644',
+ require => Package['check-mk-server'];
+
+ # check_icmp must be suid root or called by sudo
+ # see https://leap.se/code/issues/5171
+ '/usr/lib/nagios/plugins/check_icmp':
+ mode => '4755',
+ require => Package['nagios-plugins-basic'];
+ }
+
+ include check_mk::agent::local_checks
+}
diff --git a/puppet/modules/site_check_mk/templates/extra_host_conf.mk b/puppet/modules/site_check_mk/templates/extra_host_conf.mk
new file mode 100644
index 00000000..bc27b514
--- /dev/null
+++ b/puppet/modules/site_check_mk/templates/extra_host_conf.mk
@@ -0,0 +1,13 @@
+# retry 3 times before setting a host into a hard state
+# and send out notification
+extra_host_conf["max_check_attempts"] = [
+ ("4", ALL_HOSTS )
+]
+
+# Use hostnames as alias so notification mail subjects
+# are more readable and not so long. Alias defaults to
+# the fqdn of a host is not changed.
+extra_host_conf["alias"] = [
+<% @hosts.keys.sort.each do |key| -%> ( "<%= key.strip %>", ["<%= @hosts[key]['domain_internal']%>"]),
+<% end -%>
+]
diff --git a/puppet/modules/site_check_mk/templates/host_contactgroups.mk b/puppet/modules/site_check_mk/templates/host_contactgroups.mk
new file mode 100644
index 00000000..6a534967
--- /dev/null
+++ b/puppet/modules/site_check_mk/templates/host_contactgroups.mk
@@ -0,0 +1,17 @@
+<%
+ contact_groups = []
+ @environments.keys.sort.each do |env_name|
+ hosts = ""
+ @nagios_hosts.keys.sort.each do |hostname|
+ hostdata = @nagios_hosts[hostname]
+ domain_internal = hostdata['domain_internal']
+ if hostdata['environment'] == env_name
+ hosts << '"' + domain_internal + '", '
+ end
+ end
+ contact_groups << ' ( "%s", [%s] )' % [env_name, hosts]
+ end
+%>
+host_contactgroups = [
+<%= contact_groups.join(",\n") %>
+]
diff --git a/puppet/modules/site_check_mk/templates/hostgroups.mk b/puppet/modules/site_check_mk/templates/hostgroups.mk
new file mode 100644
index 00000000..7158dcd1
--- /dev/null
+++ b/puppet/modules/site_check_mk/templates/hostgroups.mk
@@ -0,0 +1,17 @@
+<%
+ host_groups = []
+ @environments.keys.sort.each do |env_name|
+ hosts = ""
+ @nagios_hosts.keys.sort.each do |hostname|
+ hostdata = @nagios_hosts[hostname]
+ domain_internal = hostdata['domain_internal']
+ if hostdata['environment'] == env_name
+ hosts << '"' + domain_internal + '", '
+ end
+ end
+ host_groups << ' ( "%s", [%s] )' % [env_name, hosts]
+ end
+%>
+host_groups = [
+<%= host_groups.join(",\n") %>
+]
diff --git a/puppet/modules/site_check_mk/templates/use_ssh.mk b/puppet/modules/site_check_mk/templates/use_ssh.mk
new file mode 100644
index 00000000..55269536
--- /dev/null
+++ b/puppet/modules/site_check_mk/templates/use_ssh.mk
@@ -0,0 +1,6 @@
+# http://mathias-kettner.de/checkmk_datasource_programs.html
+datasource_programs = [
+<% @nagios_hosts.sort.each do |name,config| %>
+ ( "ssh -l root -i /etc/check_mk/.ssh/id_rsa -p <%=config['ssh_port']%> <%=config['domain_internal']%> check_mk_agent", [ "<%=config['domain_internal']%>" ], ),<%- end -%>
+
+]
diff --git a/puppet/modules/site_config/files/xterm-title.sh b/puppet/modules/site_config/files/xterm-title.sh
new file mode 100644
index 00000000..3cff0e3a
--- /dev/null
+++ b/puppet/modules/site_config/files/xterm-title.sh
@@ -0,0 +1,8 @@
+# If this is an xterm set the title to user@host:dir
+case "$TERM" in
+xterm*|rxvt*)
+ PROMPT_COMMAND='echo -ne "\033]0;${USER}@${HOSTNAME}: ${PWD}\007"'
+ ;;
+*)
+ ;;
+esac
diff --git a/puppet/modules/site_config/lib/facter/dhcp_enabled.rb b/puppet/modules/site_config/lib/facter/dhcp_enabled.rb
new file mode 100644
index 00000000..33220da3
--- /dev/null
+++ b/puppet/modules/site_config/lib/facter/dhcp_enabled.rb
@@ -0,0 +1,22 @@
+require 'facter'
+def dhcp_enabled?(ifs, recurse=true)
+ dhcp = false
+ included_ifs = []
+ if FileTest.exists?(ifs)
+ File.open(ifs) do |file|
+ dhcp = file.enum_for(:each_line).any? do |line|
+ if recurse && line =~ /^\s*source\s+([^\s]+)/
+ included_ifs += Dir.glob($1)
+ end
+ line =~ /inet\s+dhcp/
+ end
+ end
+ end
+ dhcp || included_ifs.any? { |ifs| dhcp_enabled?(ifs, false) }
+end
+Facter.add(:dhcp_enabled) do
+ confine :osfamily => 'Debian'
+ setcode do
+ dhcp_enabled?('/etc/network/interfaces')
+ end
+end
diff --git a/puppet/modules/site_config/lib/facter/ip_interface.rb b/puppet/modules/site_config/lib/facter/ip_interface.rb
new file mode 100644
index 00000000..45764bfc
--- /dev/null
+++ b/puppet/modules/site_config/lib/facter/ip_interface.rb
@@ -0,0 +1,13 @@
+require 'facter/util/ip'
+
+Facter::Util::IP.get_interfaces.each do |interface|
+ ip = Facter.value("ipaddress_#{interface}")
+ if ip != nil
+ Facter.add("interface_" + ip ) do
+ setcode do
+ interface
+ end
+ end
+ end
+end
+
diff --git a/puppet/modules/site_config/manifests/caching_resolver.pp b/puppet/modules/site_config/manifests/caching_resolver.pp
new file mode 100644
index 00000000..8bf465c1
--- /dev/null
+++ b/puppet/modules/site_config/manifests/caching_resolver.pp
@@ -0,0 +1,27 @@
+# deploy local caching resolver
+class site_config::caching_resolver {
+ tag 'leap_base'
+
+ class { 'unbound':
+ root_hints => false,
+ anchor => false,
+ ssl => false,
+ settings => {
+ server => {
+ verbosity => '1',
+ interface => [ '127.0.0.1', '::1' ],
+ port => '53',
+ hide-identity => 'yes',
+ hide-version => 'yes',
+ harden-glue => 'yes',
+ access-control => [ '127.0.0.0/8 allow', '::1 allow' ]
+ }
+ }
+ }
+
+ concat::fragment { 'unbound glob include':
+ target => $unbound::params::config,
+ content => "include: /etc/unbound/unbound.conf.d/*.conf\n\n",
+ order => 10
+ }
+}
diff --git a/puppet/modules/site_config/manifests/default.pp b/puppet/modules/site_config/manifests/default.pp
new file mode 100644
index 00000000..256de1a1
--- /dev/null
+++ b/puppet/modules/site_config/manifests/default.pp
@@ -0,0 +1,71 @@
+# common things to set up on every node
+class site_config::default {
+ tag 'leap_base'
+
+ $services = hiera('services', [])
+ $domain_hash = hiera('domain')
+ include site_config::params
+ include site_config::setup
+
+ # default class, used by all hosts
+
+ include lsb, git
+
+ # configure sysctl parameters
+ include site_config::sysctl
+
+ # configure ssh and include ssh-keys
+ include site_sshd
+
+ # include classes for special environments
+ # i.e. openstack/aws nodes, vagrant nodes
+
+ # fix dhclient from changing resolver information
+ # facter returns 'true' as string
+ # lint:ignore:quoted_booleans
+ if $::dhcp_enabled == 'true' {
+ # lint:endignore
+ include site_config::dhclient
+ }
+
+ # configure /etc/resolv.conf
+ include site_config::resolvconf
+
+ # configure caching, local resolver
+ include site_config::caching_resolver
+
+ # install/configure syslog and core log rotations
+ include site_config::syslog
+
+ # provide a basic level of quality entropy
+ include haveged
+
+ # install/remove base packages
+ include site_config::packages
+
+ # include basic shorewall config
+ include site_shorewall::defaults
+
+ Package['git'] -> Vcsrepo<||>
+
+ # include basic shell config
+ include site_config::shell
+
+ # set up core leap files and directories
+ include site_config::files
+
+ # remove leftovers from previous deploys
+ include site_config::remove
+
+ if ! member($services, 'mx') {
+ include site_postfix::satellite
+ }
+
+ # if class custom exists, include it.
+ # possibility for users to define custom puppet recipes
+ if defined( '::custom') {
+ include ::custom
+ }
+
+ include site_check_mk::agent
+}
diff --git a/puppet/modules/site_config/manifests/dhclient.pp b/puppet/modules/site_config/manifests/dhclient.pp
new file mode 100644
index 00000000..a1f87d41
--- /dev/null
+++ b/puppet/modules/site_config/manifests/dhclient.pp
@@ -0,0 +1,40 @@
+# Unfortunately, there does not seem to be a way to reload the dhclient.conf
+# config file, or a convenient way to disable the modifications to
+# /etc/resolv.conf. So the following makes the functions involved noops and
+# ships a script to kill and restart dhclient. See the debian bugs:
+# #681698, #712796
+class site_config::dhclient {
+
+
+ include site_config::params
+
+ file { '/usr/local/sbin/reload_dhclient':
+ owner => 0,
+ group => 0,
+ mode => '0755',
+ content => template('site_config/reload_dhclient.erb');
+ }
+
+ exec { 'reload_dhclient':
+ refreshonly => true,
+ command => '/usr/local/sbin/reload_dhclient',
+ before => Class['site_config::resolvconf'],
+ require => File['/usr/local/sbin/reload_dhclient'],
+ }
+
+ file { '/etc/dhcp/dhclient-enter-hooks.d':
+ ensure => directory,
+ mode => '0755',
+ owner => 'root',
+ group => 'root',
+ }
+
+ file { '/etc/dhcp/dhclient-enter-hooks.d/disable_resolvconf':
+ content => 'make_resolv_conf() { : ; } ; set_hostname() { : ; }',
+ mode => '0644',
+ owner => 'root',
+ group => 'root',
+ require => File['/etc/dhcp/dhclient-enter-hooks.d'],
+ notify => Exec['reload_dhclient'];
+ }
+}
diff --git a/puppet/modules/site_config/manifests/files.pp b/puppet/modules/site_config/manifests/files.pp
new file mode 100644
index 00000000..d2ef8a98
--- /dev/null
+++ b/puppet/modules/site_config/manifests/files.pp
@@ -0,0 +1,24 @@
+# set up core leap files and directories
+class site_config::files {
+
+ file {
+ '/srv/leap':
+ ensure => directory,
+ owner => 'root',
+ group => 'root',
+ mode => '0711';
+
+ [ '/etc/leap', '/var/lib/leap']:
+ ensure => directory,
+ owner => 'root',
+ group => 'root',
+ mode => '0755';
+
+ '/var/log/leap':
+ ensure => directory,
+ owner => 'root',
+ group => 'adm',
+ mode => '0750';
+ }
+
+}
diff --git a/puppet/modules/site_config/manifests/hosts.pp b/puppet/modules/site_config/manifests/hosts.pp
new file mode 100644
index 00000000..878b6af0
--- /dev/null
+++ b/puppet/modules/site_config/manifests/hosts.pp
@@ -0,0 +1,44 @@
+class site_config::hosts() {
+ $hosts = hiera('hosts', false)
+
+ # calculate all the hostname aliases that might be used
+ $hostname = hiera('name')
+ $domain_hash = hiera('domain', {})
+ $dns = hiera('dns', {})
+ if $dns['aliases'] == undef {
+ $dns_aliases = []
+ } else {
+ $dns_aliases = $dns['aliases']
+ }
+ $my_hostnames = unique(concat(
+ [$domain_hash['full'], $hostname, $domain_hash['internal']], $dns_aliases
+ ))
+
+ file { '/etc/hostname':
+ ensure => present,
+ content => $hostname
+ }
+
+ exec { "/bin/hostname ${hostname}":
+ subscribe => [ File['/etc/hostname'], File['/etc/hosts'] ],
+ refreshonly => true;
+ }
+
+ # we depend on reliable hostnames from /etc/hosts for the stunnel services
+ # so restart stunnel service when /etc/hosts is modified
+ # because this is done in an early stage, the stunnel module may not
+ # have been deployed and will not be available for overriding, so
+ # this is handled in an unorthodox manner
+ exec { '/etc/init.d/stunnel4 restart':
+ subscribe => File['/etc/hosts'],
+ refreshonly => true,
+ onlyif => 'test -f /etc/init.d/stunnel4';
+ }
+
+ file { '/etc/hosts':
+ content => template('site_config/hosts'),
+ mode => '0644',
+ owner => root,
+ group => root;
+ }
+}
diff --git a/puppet/modules/site_config/manifests/initial_firewall.pp b/puppet/modules/site_config/manifests/initial_firewall.pp
new file mode 100644
index 00000000..93cfb847
--- /dev/null
+++ b/puppet/modules/site_config/manifests/initial_firewall.pp
@@ -0,0 +1,64 @@
+class site_config::initial_firewall {
+
+ # This class is intended to setup an initial firewall, before shorewall is
+ # configured. The purpose of this is for the rare case where shorewall fails
+ # to start, we should not expose services to the public.
+
+ $ssh_config = hiera('ssh')
+ $ssh_port = $ssh_config['port']
+
+ package { 'iptables':
+ ensure => present
+ }
+
+ file {
+ # This firewall enables ssh access, dns lookups and web lookups (for
+ # package installation) but otherwise restricts all outgoing and incoming
+ # ports
+ '/etc/network/ipv4firewall_up.rules':
+ content => template('site_config/ipv4firewall_up.rules.erb'),
+ owner => root,
+ group => 0,
+ mode => '0644';
+
+ # This firewall denys all ipv6 traffic - we will need to change this
+ # when we begin to support ipv6
+ '/etc/network/ipv6firewall_up.rules':
+ content => template('site_config/ipv6firewall_up.rules.erb'),
+ owner => root,
+ group => 0,
+ mode => '0644';
+
+ # Run the iptables-restore in if-pre-up so that the network is locked down
+ # until the correct interfaces and ips are connected
+ '/etc/network/if-pre-up.d/ipv4tables':
+ content => "#!/bin/sh\n/sbin/iptables-restore < /etc/network/ipv4firewall_up.rules\n",
+ owner => root,
+ group => 0,
+ mode => '0744';
+
+ # Same as above for IPv6
+ '/etc/network/if-pre-up.d/ipv6tables':
+ content => "#!/bin/sh\n/sbin/ip6tables-restore < /etc/network/ipv6firewall_up.rules\n",
+ owner => root,
+ group => 0,
+ mode => '0744';
+ }
+
+ # Immediately setup these firewall rules, but only if shorewall is not running
+ exec {
+ 'default_ipv4_firewall':
+ command => '/sbin/iptables-restore < /etc/network/ipv4firewall_up.rules',
+ logoutput => true,
+ unless => 'test -x /etc/init.d/shorewall && /etc/init.d/shorewall status',
+ subscribe => File['/etc/network/ipv4firewall_up.rules'],
+ require => File['/etc/network/ipv4firewall_up.rules'];
+
+ 'default_ipv6_firewall':
+ command => '/sbin/ip6tables-restore < /etc/network/ipv6firewall_up.rules',
+ logoutput => true,
+ unless => 'test -x /etc/init.d/shorewall6 && /etc/init.d/shorewall6 status',
+ subscribe => File['/etc/network/ipv6firewall_up.rules'],
+ require => File['/etc/network/ipv6firewall_up.rules'];
+ }
+}
diff --git a/puppet/modules/site_config/manifests/packages.pp b/puppet/modules/site_config/manifests/packages.pp
new file mode 100644
index 00000000..140189a4
--- /dev/null
+++ b/puppet/modules/site_config/manifests/packages.pp
@@ -0,0 +1,32 @@
+# install default packages and remove unwanted packages
+class site_config::packages {
+
+
+ # base set of packages that we want to have installed everywhere
+ package { [ 'etckeeper', 'screen', 'less', 'ntp' ]:
+ ensure => installed,
+ }
+
+ # base set of packages that we want to remove everywhere
+ package { [
+ 'acpi', 'build-essential',
+ 'cpp', 'cpp-4.6', 'cpp-4.7', 'cpp-4.8', 'cpp-4.9',
+ 'eject', 'ftp',
+ 'g++', 'g++-4.6', 'g++-4.7', 'g++-4.8', 'g++-4.9',
+ 'gcc', 'gcc-4.6', 'gcc-4.7', 'gcc-4.8', 'gcc-4.9',
+ 'laptop-detect', 'libc6-dev', 'libssl-dev', 'lpr', 'make',
+ 'pppconfig', 'pppoe', 'pump', 'qstat',
+ 'samba-common', 'samba-common-bin', 'smbclient',
+ 'tcl8.5', 'tk8.5', 'os-prober', 'unzip', 'xauth', 'x11-common',
+ 'x11-utils', 'xterm' ]:
+ ensure => purged;
+ }
+
+ # leave a few packages installed on local environments
+ # vagrant i.e. needs them for mounting shared folders
+ if $::site_config::params::environment != 'local' {
+ package { [ 'nfs-common', 'nfs-kernel-server', 'rpcbind', 'portmap' ]:
+ ensure => purged;
+ }
+ }
+}
diff --git a/puppet/modules/site_config/manifests/packages/build_essential.pp b/puppet/modules/site_config/manifests/packages/build_essential.pp
new file mode 100644
index 00000000..2b3e13b9
--- /dev/null
+++ b/puppet/modules/site_config/manifests/packages/build_essential.pp
@@ -0,0 +1,28 @@
+#
+# include this whenever you want to ensure build-essential package and related compilers are installed.
+#
+class site_config::packages::build_essential inherits ::site_config::packages {
+
+ # NICKSERVER CODE NOTE: in order to support TLS, libssl-dev must be installed
+ # before EventMachine gem is built/installed.
+ Package[ 'gcc', 'make', 'g++', 'cpp', 'libssl-dev', 'libc6-dev' ] {
+ ensure => present
+ }
+
+ case $::operatingsystemrelease {
+ /^8.*/: {
+ Package[ 'gcc-4.9','g++-4.9', 'cpp-4.9' ] {
+ ensure => present
+ }
+ }
+
+ /^7.*/: {
+ Package[ 'gcc-4.7','g++-4.7', 'cpp-4.7' ] {
+ ensure => present
+ }
+ }
+
+ default: { }
+ }
+
+}
diff --git a/puppet/modules/site_config/manifests/packages/gnutls.pp b/puppet/modules/site_config/manifests/packages/gnutls.pp
new file mode 100644
index 00000000..b1f17480
--- /dev/null
+++ b/puppet/modules/site_config/manifests/packages/gnutls.pp
@@ -0,0 +1,5 @@
+class site_config::packages::gnutls {
+
+ package { 'gnutls-bin': ensure => installed }
+
+}
diff --git a/puppet/modules/site_config/manifests/params.pp b/puppet/modules/site_config/manifests/params.pp
new file mode 100644
index 00000000..012b3ce0
--- /dev/null
+++ b/puppet/modules/site_config/manifests/params.pp
@@ -0,0 +1,35 @@
+class site_config::params {
+
+ $ip_address = hiera('ip_address')
+ $ip_address_interface = getvar("interface_${ip_address}")
+ $ec2_local_ipv4_interface = getvar("interface_${::ec2_local_ipv4}")
+ $environment = hiera('environment', undef)
+
+
+ if $environment == 'local' {
+ $interface = 'eth1'
+ include site_config::packages::build_essential
+ }
+ elsif hiera('interface','') != '' {
+ $interface = hiera('interface')
+ }
+ elsif $ip_address_interface != '' {
+ $interface = $ip_address_interface
+ }
+ elsif $ec2_local_ipv4_interface != '' {
+ $interface = $ec2_local_ipv4_interface
+ }
+ elsif $::interfaces =~ /eth0/ {
+ $interface = 'eth0'
+ }
+ else {
+ fail("unable to determine a valid interface, please set a valid interface for this node in nodes/${::hostname}.json")
+ }
+
+ $ca_name = 'leap_ca'
+ $client_ca_name = 'leap_client_ca'
+ $ca_bundle_name = 'leap_ca_bundle'
+ $cert_name = 'leap'
+ $commercial_ca_name = 'leap_commercial_ca'
+ $commercial_cert_name = 'leap_commercial'
+}
diff --git a/puppet/modules/site_config/manifests/remove.pp b/puppet/modules/site_config/manifests/remove.pp
new file mode 100644
index 00000000..443df9c2
--- /dev/null
+++ b/puppet/modules/site_config/manifests/remove.pp
@@ -0,0 +1,11 @@
+# remove leftovers from previous deploys
+class site_config::remove {
+ include site_config::remove::files
+
+ case $::operatingsystemrelease {
+ /^8.*/: {
+ include site_config::remove::jessie
+ }
+ default: { }
+ }
+}
diff --git a/puppet/modules/site_config/manifests/remove/bigcouch.pp b/puppet/modules/site_config/manifests/remove/bigcouch.pp
new file mode 100644
index 00000000..3535c3c1
--- /dev/null
+++ b/puppet/modules/site_config/manifests/remove/bigcouch.pp
@@ -0,0 +1,42 @@
+# remove bigcouch leftovers from previous installations
+class site_config::remove::bigcouch {
+
+ # Don't use check_mk logwatch to watch bigcouch logs anymore
+ # see https://leap.se/code/issues/7375 for more details
+ file { '/etc/check_mk/logwatch.d/bigcouch.cfg':
+ ensure => absent,
+ notify => [
+ Exec['remove_bigcouch_logwatch_stateline']
+ ]
+ }
+
+ exec { 'remove_bigcouch_logwatch_stateline':
+ command => "sed -i '/bigcouch.log/d' /etc/check_mk/logwatch.state",
+ refreshonly => true,
+ }
+
+ cron { 'compact_all_shards':
+ ensure => absent
+ }
+
+
+ exec { 'kill_bigcouch_stunnel_procs':
+ refreshonly => true,
+ command => '/usr/bin/pkill -f "/usr/bin/stunnel4 /etc/stunnel/(ednp|epmd)_server.conf"'
+ }
+
+ # 'tidy' doesn't notify other resources, so we need to use file here instead
+ # see https://tickets.puppetlabs.com/browse/PUP-6021
+ file {
+ [ '/etc/stunnel/ednp_server.conf', '/etc/stunnel/epmd_server.conf']:
+ ensure => absent,
+ # notifying Service[stunnel] doesn't work here because the config
+ # files contain the pid of the procs to stop/start.
+ # If we remove the config, and restart stunnel then it will only
+ # stop/start the procs for which config files are found and the stale
+ # service will continue to run.
+ # So we simply kill them.
+ notify => Exec['kill_bigcouch_stunnel_procs']
+ }
+
+}
diff --git a/puppet/modules/site_config/manifests/remove/files.pp b/puppet/modules/site_config/manifests/remove/files.pp
new file mode 100644
index 00000000..41d6462e
--- /dev/null
+++ b/puppet/modules/site_config/manifests/remove/files.pp
@@ -0,0 +1,56 @@
+#
+# Sometimes when we upgrade the platform, we need to ensure that files that
+# the platform previously created will get removed.
+#
+# These file removals don't need to be kept forever: we only need to remove
+# files that are present in the prior platform release.
+#
+# We can assume that the every node is upgraded from the previous platform
+# release.
+#
+
+class site_config::remove::files {
+
+ # Platform 0.8 removals
+ tidy {
+ '/etc/default/leap_mx':;
+ '/etc/logrotate.d/mx':;
+ '/etc/rsyslog.d/50-mx.conf':;
+ '/etc/apt/preferences.d/openvpn':;
+ '/etc/apt/sources.list.d/secondary.list.disabled.list':;
+ }
+
+ #
+ # Platform 0.7 removals
+ #
+
+ tidy {
+ '/etc/rsyslog.d/99-tapicero.conf':;
+ '/etc/rsyslog.d/01-webapp.conf':;
+ '/etc/rsyslog.d/50-stunnel.conf':;
+ '/etc/logrotate.d/stunnel':;
+ '/var/log/stunnel4/stunnel.log':;
+ 'leap_mx':
+ path => '/var/log/',
+ recurse => true,
+ matches => ['leap_mx*', 'mx.log.[1-5]', 'mx.log.[6-9](.gz)?',
+ 'mx.log.[0-9][0-9](.gz)?'];
+ '/srv/leap/webapp/public/provider.json':;
+ '/srv/leap/couchdb/designs/tmp_users':
+ recurse => true,
+ rmdirs => true;
+ '/etc/leap/soledad-server.conf':;
+ '/var/log/leap/openvpn.log':;
+ '/etc/rsyslog.d/50-openvpn.conf':;
+ }
+
+ # leax-mx logged to /var/log/leap_mx.log in the past
+ # we need to use a dumb exec here because file_line doesn't
+ # allow removing lines that match a regex in the current version
+ # of stdlib, see https://tickets.puppetlabs.com/browse/MODULES-1903
+ exec { 'rm_old_leap_mx_log_destination':
+ command => "/bin/sed -i '/leap_mx.log/d' /etc/check_mk/logwatch.state",
+ onlyif => "/bin/grep -qe 'leap_mx.log' /etc/check_mk/logwatch.state"
+ }
+
+}
diff --git a/puppet/modules/site_config/manifests/remove/jessie.pp b/puppet/modules/site_config/manifests/remove/jessie.pp
new file mode 100644
index 00000000..e9497baf
--- /dev/null
+++ b/puppet/modules/site_config/manifests/remove/jessie.pp
@@ -0,0 +1,14 @@
+# remove possible leftovers after upgrading from wheezy to jessie
+class site_config::remove::jessie {
+
+ tidy {
+ '/etc/apt/preferences.d/rsyslog_anon_depends':
+ notify => Exec['apt_updated'];
+ }
+
+ apt::preferences_snippet {
+ [ 'facter', 'obfsproxy', 'python-twisted', 'unbound' ]:
+ ensure => absent;
+ }
+
+}
diff --git a/puppet/modules/site_config/manifests/remove/monitoring.pp b/puppet/modules/site_config/manifests/remove/monitoring.pp
new file mode 100644
index 00000000..18e2949b
--- /dev/null
+++ b/puppet/modules/site_config/manifests/remove/monitoring.pp
@@ -0,0 +1,13 @@
+# remove leftovers on monitoring nodes
+class site_config::remove::monitoring {
+
+ # Remove check_mk loggwatch spoolfiles for
+ # tapicero and bigcouch
+ tidy {
+ 'remove_logwatch_spoolfiles':
+ path => '/var/lib/check_mk/logwatch',
+ recurse => true,
+ matches => [ '*tapicero.log', '*bigcouch.log'];
+ }
+
+}
diff --git a/puppet/modules/site_config/manifests/remove/tapicero.pp b/puppet/modules/site_config/manifests/remove/tapicero.pp
new file mode 100644
index 00000000..07c3c6c6
--- /dev/null
+++ b/puppet/modules/site_config/manifests/remove/tapicero.pp
@@ -0,0 +1,72 @@
+# remove tapicero leftovers from previous deploys on couchdb nodes
+class site_config::remove::tapicero {
+
+ ensure_packages('curl')
+
+ # remove tapicero couchdb user
+ $couchdb_config = hiera('couch')
+ $couchdb_mode = $couchdb_config['mode']
+
+ if $couchdb_mode == 'multimaster'
+ {
+ $port = 5986
+ } else {
+ $port = 5984
+ }
+
+ exec { 'remove_couchdb_user':
+ onlyif => "/usr/bin/curl -s 127.0.0.1:${port}/_users/org.couchdb.user:tapicero | grep -qv 'not_found'",
+ command => "/usr/local/bin/couch-doc-update --host 127.0.0.1:${port} --db _users --id org.couchdb.user:tapicero --delete",
+ require => Package['curl']
+ }
+
+
+ exec { 'kill_tapicero':
+ onlyif => '/usr/bin/test -s /var/run/tapicero.pid',
+ command => '/usr/bin/pkill --pidfile /var/run/tapicero.pid'
+ }
+
+ user { 'tapicero':
+ ensure => absent;
+ }
+
+ group { 'tapicero':
+ ensure => absent,
+ require => User['tapicero'];
+ }
+
+ tidy {
+ '/srv/leap/tapicero':
+ recurse => true,
+ require => [ Exec['kill_tapicero'] ];
+ '/var/lib/leap/tapicero':
+ require => [ Exec['kill_tapicero'] ];
+ '/var/run/tapicero':
+ require => [ Exec['kill_tapicero'] ];
+ '/etc/leap/tapicero.yaml':
+ require => [ Exec['kill_tapicero'] ];
+ '/etc/init.d/tapicero':
+ require => [ Exec['kill_tapicero'] ];
+ 'tapicero_logs':
+ path => '/var/log/leap',
+ recurse => true,
+ matches => 'tapicero*',
+ require => [ Exec['kill_tapicero'] ];
+ '/etc/check_mk/logwatch.d/tapicero.cfg':;
+ }
+
+ # remove local nagios plugin checks via mrpe
+ augeas {
+ 'Tapicero_Procs':
+ incl => '/etc/check_mk/mrpe.cfg',
+ lens => 'Spacevars.lns',
+ changes => 'rm /files/etc/check_mk/mrpe.cfg/Tapicero_Procs',
+ require => File['/etc/check_mk/mrpe.cfg'];
+ 'Tapicero_Heartbeat':
+ incl => '/etc/check_mk/mrpe.cfg',
+ lens => 'Spacevars.lns',
+ changes => 'rm Tapicero_Heartbeat',
+ require => File['/etc/check_mk/mrpe.cfg'];
+ }
+
+}
diff --git a/puppet/modules/site_config/manifests/remove/webapp.pp b/puppet/modules/site_config/manifests/remove/webapp.pp
new file mode 100644
index 00000000..58f59815
--- /dev/null
+++ b/puppet/modules/site_config/manifests/remove/webapp.pp
@@ -0,0 +1,7 @@
+# remove leftovers on webapp nodes
+class site_config::remove::webapp {
+ tidy {
+ '/etc/apache/sites-enabled/leap_webapp.conf':
+ notify => Service['apache'];
+ }
+}
diff --git a/puppet/modules/site_config/manifests/resolvconf.pp b/puppet/modules/site_config/manifests/resolvconf.pp
new file mode 100644
index 00000000..09f0b405
--- /dev/null
+++ b/puppet/modules/site_config/manifests/resolvconf.pp
@@ -0,0 +1,14 @@
+class site_config::resolvconf {
+
+ $domain_public = $site_config::default::domain_hash['full_suffix']
+
+ class { '::resolvconf':
+ domain => $domain_public,
+ search => $domain_public,
+ nameservers => [
+ '127.0.0.1 # local caching-only, unbound',
+ '85.214.20.141 # Digitalcourage, a german privacy organisation: (https://en.wikipedia.org/wiki/Digitalcourage)',
+ '172.81.176.146 # OpenNIC (https://servers.opennicproject.org/edit.php?srv=ns1.tor.ca.dns.opennic.glue)'
+ ]
+ }
+}
diff --git a/puppet/modules/site_config/manifests/ruby.pp b/puppet/modules/site_config/manifests/ruby.pp
new file mode 100644
index 00000000..5c13233d
--- /dev/null
+++ b/puppet/modules/site_config/manifests/ruby.pp
@@ -0,0 +1,8 @@
+# install ruby, rubygems and bundler
+# configure ruby settings common to all servers
+class site_config::ruby {
+ Class[Ruby] -> Class[rubygems] -> Class[bundler::install]
+ class { '::ruby': }
+ class { 'bundler::install': install_method => 'package' }
+ include rubygems
+}
diff --git a/puppet/modules/site_config/manifests/ruby/dev.pp b/puppet/modules/site_config/manifests/ruby/dev.pp
new file mode 100644
index 00000000..2b0b106d
--- /dev/null
+++ b/puppet/modules/site_config/manifests/ruby/dev.pp
@@ -0,0 +1,8 @@
+# install ruby dev packages needed for building some gems
+class site_config::ruby::dev {
+ include site_config::ruby
+ include ::ruby::devel
+
+ # building gems locally probably requires build-essential and gcc:
+ include site_config::packages::build_essential
+}
diff --git a/puppet/modules/site_config/manifests/setup.pp b/puppet/modules/site_config/manifests/setup.pp
new file mode 100644
index 00000000..82dfe76d
--- /dev/null
+++ b/puppet/modules/site_config/manifests/setup.pp
@@ -0,0 +1,50 @@
+# common things to set up on every node
+# leftover from the past, where we did two puppetruns
+# after another. We should consolidate this into site_config::default
+# in the future.
+class site_config::setup {
+ tag 'leap_base'
+
+ #
+ # this is applied before each run of site.pp
+ #
+
+ Exec { path => '/usr/bin:/usr/sbin/:/bin:/sbin:/usr/local/bin:/usr/local/sbin' }
+
+ include site_config::params
+
+ include concat::setup
+ include stdlib
+
+ # configure /etc/hosts
+ class { 'site_config::hosts': }
+
+ include site_config::initial_firewall
+
+ include site_apt
+
+ package { 'facter':
+ ensure => latest
+ }
+
+ # if squid_deb_proxy_client is set to true, install and configure
+ # squid_deb_proxy_client for apt caching
+ if hiera('squid_deb_proxy_client', false) {
+ include site_squid_deb_proxy::client
+ }
+
+ # shorewall is installed/half-configured during setup.pp (Bug #3871)
+ # we need to include shorewall::interface{eth0} in setup.pp so
+ # packages can be installed during main puppetrun, even before shorewall
+ # is configured completly
+ if ( $::site_config::params::environment == 'local' ) {
+ include site_config::vagrant
+ }
+
+ # if class site_custom::setup exists, include it.
+ # possibility for users to define custom puppet recipes
+ if defined( '::site_custom::setup') {
+ include ::site_custom::setup
+ }
+
+}
diff --git a/puppet/modules/site_config/manifests/shell.pp b/puppet/modules/site_config/manifests/shell.pp
new file mode 100644
index 00000000..5b8c025d
--- /dev/null
+++ b/puppet/modules/site_config/manifests/shell.pp
@@ -0,0 +1,22 @@
+class site_config::shell {
+
+ file {
+ '/etc/profile.d/leap_path.sh':
+ content => 'PATH=$PATH:/srv/leap/bin',
+ mode => '0644',
+ owner => root,
+ group => root;
+ }
+
+ ##
+ ## XTERM TITLE
+ ##
+
+ file { '/etc/profile.d/xterm-title.sh':
+ source => 'puppet:///modules/site_config/xterm-title.sh',
+ owner => root,
+ group => 0,
+ mode => '0644';
+ }
+
+}
diff --git a/puppet/modules/site_config/manifests/slow.pp b/puppet/modules/site_config/manifests/slow.pp
new file mode 100644
index 00000000..8e9b7035
--- /dev/null
+++ b/puppet/modules/site_config/manifests/slow.pp
@@ -0,0 +1,10 @@
+# this class is run by default, but can be excluded
+# for testing purposes by calling "leap deploy" with
+# the "--fast" parameter
+class site_config::slow {
+ tag 'leap_slow'
+
+ include site_config::default
+ include apt::update
+ class { 'site_apt::dist_upgrade': }
+}
diff --git a/puppet/modules/site_config/manifests/sysctl.pp b/puppet/modules/site_config/manifests/sysctl.pp
new file mode 100644
index 00000000..99f75123
--- /dev/null
+++ b/puppet/modules/site_config/manifests/sysctl.pp
@@ -0,0 +1,8 @@
+class site_config::sysctl {
+
+ sysctl::config {
+ 'net.ipv4.ip_nonlocal_bind':
+ value => 1,
+ comment => 'Allow applications to bind to an address when link is down (see https://leap.se/code/issues/4506)'
+ }
+}
diff --git a/puppet/modules/site_config/manifests/syslog.pp b/puppet/modules/site_config/manifests/syslog.pp
new file mode 100644
index 00000000..591e0601
--- /dev/null
+++ b/puppet/modules/site_config/manifests/syslog.pp
@@ -0,0 +1,62 @@
+# configure rsyslog on all nodes
+class site_config::syslog {
+
+ # only pin rsyslog packages to backports on wheezy
+ case $::operatingsystemrelease {
+ /^7.*/: {
+ include ::site_apt::preferences::rsyslog
+ }
+ # on jessie+ systems, systemd and journald are enabled,
+ # and journald logs IP addresses, so we need to disable
+ # it until a solution is found, (#7863):
+ # https://github.com/systemd/systemd/issues/2447
+ default: {
+ include ::journald
+ augeas {
+ 'disable_journald':
+ incl => '/etc/systemd/journald.conf',
+ lens => 'Puppet.lns',
+ changes => 'set /files/etc/systemd/journald.conf/Journal/Storage \'none\'',
+ notify => Service['systemd-journald'];
+ }
+ }
+ }
+
+ class { '::rsyslog::client':
+ log_remote => false,
+ log_local => true,
+ custom_config => 'site_rsyslog/client.conf.erb'
+ }
+
+ rsyslog::snippet { '00-anonymize_logs':
+ content => '$ModLoad mmanon
+action(type="mmanon" ipv4.bits="32" mode="rewrite")'
+ }
+
+ augeas {
+ 'logrotate_leap_deploy':
+ context => '/files/etc/logrotate.d/leap_deploy/rule',
+ changes => [
+ 'set file /var/log/leap/deploy.log',
+ 'set rotate 5',
+ 'set size 1M',
+ 'set compress compress',
+ 'set missingok missingok',
+ 'set copytruncate copytruncate' ];
+
+ # NOTE:
+ # the puppet_command script requires the option delaycompress
+ # be set on the summary log file.
+
+ 'logrotate_leap_deploy_summary':
+ context => '/files/etc/logrotate.d/leap_deploy_summary/rule',
+ changes => [
+ 'set file /var/log/leap/deploy-summary.log',
+ 'set rotate 5',
+ 'set size 100k',
+ 'set delaycompress delaycompress',
+ 'set compress compress',
+ 'set missingok missingok',
+ 'set copytruncate copytruncate' ]
+ }
+}
diff --git a/puppet/modules/site_config/manifests/vagrant.pp b/puppet/modules/site_config/manifests/vagrant.pp
new file mode 100644
index 00000000..8f50b305
--- /dev/null
+++ b/puppet/modules/site_config/manifests/vagrant.pp
@@ -0,0 +1,11 @@
+class site_config::vagrant {
+ # class for vagrant nodes
+
+ include site_shorewall::defaults
+ # eth0 on vagrant nodes is the uplink if
+ shorewall::interface { 'eth0':
+ zone => 'net',
+ options => 'tcpflags,blacklist,nosmurfs';
+ }
+
+}
diff --git a/puppet/modules/site_config/manifests/x509/ca.pp b/puppet/modules/site_config/manifests/x509/ca.pp
new file mode 100644
index 00000000..2880ecaf
--- /dev/null
+++ b/puppet/modules/site_config/manifests/x509/ca.pp
@@ -0,0 +1,11 @@
+class site_config::x509::ca {
+
+ include ::site_config::params
+
+ $x509 = hiera('x509')
+ $ca = $x509['ca_cert']
+
+ x509::ca { $site_config::params::ca_name:
+ content => $ca
+ }
+}
diff --git a/puppet/modules/site_config/manifests/x509/ca_bundle.pp b/puppet/modules/site_config/manifests/x509/ca_bundle.pp
new file mode 100644
index 00000000..5808e29e
--- /dev/null
+++ b/puppet/modules/site_config/manifests/x509/ca_bundle.pp
@@ -0,0 +1,17 @@
+class site_config::x509::ca_bundle {
+
+ # CA bundle -- we want to have the possibility of allowing multiple CAs.
+ # For now, the reason is to transition to using client CA. In the future,
+ # we will want to be able to smoothly phase out one CA and phase in another.
+ # I tried "--capath" for this, but it did not work.
+
+ include ::site_config::params
+
+ $x509 = hiera('x509')
+ $ca = $x509['ca_cert']
+ $client_ca = $x509['client_ca_cert']
+
+ x509::ca { $site_config::params::ca_bundle_name:
+ content => "${ca}${client_ca}"
+ }
+}
diff --git a/puppet/modules/site_config/manifests/x509/cert.pp b/puppet/modules/site_config/manifests/x509/cert.pp
new file mode 100644
index 00000000..7e5a36b9
--- /dev/null
+++ b/puppet/modules/site_config/manifests/x509/cert.pp
@@ -0,0 +1,12 @@
+class site_config::x509::cert {
+
+ include ::site_config::params
+
+ $x509 = hiera('x509')
+ $cert = $x509['cert']
+
+ x509::cert { $site_config::params::cert_name:
+ content => $cert
+ }
+
+}
diff --git a/puppet/modules/site_config/manifests/x509/client_ca/ca.pp b/puppet/modules/site_config/manifests/x509/client_ca/ca.pp
new file mode 100644
index 00000000..3fbafa98
--- /dev/null
+++ b/puppet/modules/site_config/manifests/x509/client_ca/ca.pp
@@ -0,0 +1,16 @@
+class site_config::x509::client_ca::ca {
+
+ ##
+ ## This is for the special CA that is used exclusively for generating
+ ## client certificates by the webapp.
+ ##
+
+ include ::site_config::params
+
+ $x509 = hiera('x509')
+ $cert = $x509['client_ca_cert']
+
+ x509::ca { $site_config::params::client_ca_name:
+ content => $cert
+ }
+}
diff --git a/puppet/modules/site_config/manifests/x509/client_ca/key.pp b/puppet/modules/site_config/manifests/x509/client_ca/key.pp
new file mode 100644
index 00000000..0b537e76
--- /dev/null
+++ b/puppet/modules/site_config/manifests/x509/client_ca/key.pp
@@ -0,0 +1,16 @@
+class site_config::x509::client_ca::key {
+
+ ##
+ ## This is for the special CA that is used exclusively for generating
+ ## client certificates by the webapp.
+ ##
+
+ include ::site_config::params
+
+ $x509 = hiera('x509')
+ $key = $x509['client_ca_key']
+
+ x509::key { $site_config::params::client_ca_name:
+ content => $key
+ }
+}
diff --git a/puppet/modules/site_config/manifests/x509/commercial/ca.pp b/puppet/modules/site_config/manifests/x509/commercial/ca.pp
new file mode 100644
index 00000000..c76a9dbb
--- /dev/null
+++ b/puppet/modules/site_config/manifests/x509/commercial/ca.pp
@@ -0,0 +1,11 @@
+class site_config::x509::commercial::ca {
+
+ include ::site_config::params
+
+ $x509 = hiera('x509')
+ $ca = $x509['commercial_ca_cert']
+
+ x509::ca { $site_config::params::commercial_ca_name:
+ content => $ca
+ }
+}
diff --git a/puppet/modules/site_config/manifests/x509/commercial/cert.pp b/puppet/modules/site_config/manifests/x509/commercial/cert.pp
new file mode 100644
index 00000000..9dd6ffcd
--- /dev/null
+++ b/puppet/modules/site_config/manifests/x509/commercial/cert.pp
@@ -0,0 +1,15 @@
+class site_config::x509::commercial::cert {
+
+ include ::site_config::params
+
+ $x509 = hiera('x509')
+ $cert = $x509['commercial_cert']
+ $ca = $x509['commercial_ca_cert']
+
+ $cafile = "${cert}\n${ca}"
+
+ x509::cert { $site_config::params::commercial_cert_name:
+ content => $cafile
+ }
+
+}
diff --git a/puppet/modules/site_config/manifests/x509/commercial/key.pp b/puppet/modules/site_config/manifests/x509/commercial/key.pp
new file mode 100644
index 00000000..2be439fd
--- /dev/null
+++ b/puppet/modules/site_config/manifests/x509/commercial/key.pp
@@ -0,0 +1,11 @@
+class site_config::x509::commercial::key {
+
+ include ::site_config::params
+
+ $x509 = hiera('x509')
+ $key = $x509['commercial_key']
+
+ x509::key { $site_config::params::commercial_cert_name:
+ content => $key
+ }
+}
diff --git a/puppet/modules/site_config/manifests/x509/key.pp b/puppet/modules/site_config/manifests/x509/key.pp
new file mode 100644
index 00000000..448dc6a6
--- /dev/null
+++ b/puppet/modules/site_config/manifests/x509/key.pp
@@ -0,0 +1,11 @@
+class site_config::x509::key {
+
+ include ::site_config::params
+
+ $x509 = hiera('x509')
+ $key = $x509['key']
+
+ x509::key { $site_config::params::cert_name:
+ content => $key
+ }
+}
diff --git a/puppet/modules/site_config/templates/hosts b/puppet/modules/site_config/templates/hosts
new file mode 100644
index 00000000..d62cbc3f
--- /dev/null
+++ b/puppet/modules/site_config/templates/hosts
@@ -0,0 +1,19 @@
+# This file is managed by puppet, any changes will be overwritten!
+
+127.0.0.1 localhost
+127.0.1.1 <%= @my_hostnames.join(' ') %>
+
+<%- if @hosts then -%>
+<% @hosts.keys.sort.each do |name| -%>
+<%- props = @hosts[name] -%>
+<%- aliases = props["aliases"] ? props["aliases"].join(' ') : nil -%>
+<%= [props["ip_address"], props["domain_full"], props["domain_internal"], aliases, name].compact.uniq.join(' ') %>
+<% end -%>
+<% end -%>
+
+# The following lines are desirable for IPv6 capable hosts
+::1 ip6-localhost ip6-loopback
+fe00::0 ip6-localnet
+ff00::0 ip6-mcastprefix
+ff02::1 ip6-allnodes
+ff02::2 ip6-allrouters
diff --git a/puppet/modules/site_config/templates/ipv4firewall_up.rules.erb b/puppet/modules/site_config/templates/ipv4firewall_up.rules.erb
new file mode 100644
index 00000000..b0c2b7ad
--- /dev/null
+++ b/puppet/modules/site_config/templates/ipv4firewall_up.rules.erb
@@ -0,0 +1,14 @@
+# Generated by iptables-save v1.4.14 on Tue Aug 20 14:40:40 2013
+*filter
+:INPUT DROP [0:0]
+:FORWARD DROP [0:0]
+:OUTPUT ACCEPT [0:0]
+-A INPUT -i lo -j ACCEPT
+-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
+-A INPUT -p tcp -m state --state NEW,ESTABLISHED --dport 22 -j ACCEPT
+-A INPUT -p tcp -m state --state NEW,ESTABLISHED --dport <%= @ssh_port %> -j ACCEPT
+-A INPUT -p udp -m udp --sport 53 -j ACCEPT
+-A INPUT -p icmp -m icmp --icmp-type 8 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT
+-A INPUT -p icmp -m icmp --icmp-type 0 -m state --state RELATED,ESTABLISHED -j ACCEPT
+-A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables denied: " --log-level 7
+COMMIT
diff --git a/puppet/modules/site_config/templates/ipv6firewall_up.rules.erb b/puppet/modules/site_config/templates/ipv6firewall_up.rules.erb
new file mode 100644
index 00000000..e2c92524
--- /dev/null
+++ b/puppet/modules/site_config/templates/ipv6firewall_up.rules.erb
@@ -0,0 +1,8 @@
+# Generated by ip6tables-save v1.4.20 on Tue Aug 20 12:19:43 2013
+*filter
+:INPUT DROP [24:1980]
+:FORWARD DROP [0:0]
+:OUTPUT DROP [14:8030]
+-A OUTPUT -j REJECT --reject-with icmp6-port-unreachable
+COMMIT
+# Completed on Tue Aug 20 12:19:43 2013
diff --git a/puppet/modules/site_config/templates/reload_dhclient.erb b/puppet/modules/site_config/templates/reload_dhclient.erb
new file mode 100644
index 00000000..075828b7
--- /dev/null
+++ b/puppet/modules/site_config/templates/reload_dhclient.erb
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+# Get the PID
+PIDFILE='/var/run/dhclient.<%= scope.lookupvar('site_config::params::interface') %>.pid'
+
+# Capture how dhclient is currently running so we can relaunch it
+dhclient=`/bin/ps --no-headers --pid $(cat $PIDFILE) -f | /usr/bin/awk '{for(i=8;i<=NF;++i) printf("%s ", $i) }'`
+
+# Kill the current dhclient
+/usr/bin/pkill -F $PIDFILE
+
+# Restart dhclient with the arguments it had previously
+$dhclient
diff --git a/puppet/modules/site_couchdb/files/couchdb_scripts_defaults.conf b/puppet/modules/site_couchdb/files/couchdb_scripts_defaults.conf
new file mode 100644
index 00000000..1565e1a1
--- /dev/null
+++ b/puppet/modules/site_couchdb/files/couchdb_scripts_defaults.conf
@@ -0,0 +1,4 @@
+# space separated list of excluded DBs for dumping
+# sourced by couchdb_dumpall.sh
+EXCLUDE_DBS='sessions tokens'
+
diff --git a/puppet/modules/site_couchdb/files/designs/Readme.md b/puppet/modules/site_couchdb/files/designs/Readme.md
new file mode 100644
index 00000000..983f629f
--- /dev/null
+++ b/puppet/modules/site_couchdb/files/designs/Readme.md
@@ -0,0 +1,14 @@
+This directory contains design documents for the leap platform.
+
+They need to be uploaded to the couch database in order to query the
+database in certain ways.
+
+Each subdirectory corresponds to a couch database and contains the design
+documents that need to be added to that particular database.
+
+Here's an example of how to upload the users design document:
+```bash
+HOST="http://localhost:5984"
+curl -X PUT $HOST/users/_design/User --data @users/User.json
+
+```
diff --git a/puppet/modules/site_couchdb/files/designs/customers/Customer.json b/puppet/modules/site_couchdb/files/designs/customers/Customer.json
new file mode 100644
index 00000000..1b4bbddd
--- /dev/null
+++ b/puppet/modules/site_couchdb/files/designs/customers/Customer.json
@@ -0,0 +1,18 @@
+{
+ "_id": "_design/Customer",
+ "language": "javascript",
+ "views": {
+ "by_user_id": {
+ "map": " function(doc) {\n if ((doc['type'] == 'Customer') && (doc['user_id'] != null)) {\n emit(doc['user_id'], 1);\n }\n }\n",
+ "reduce": "_sum"
+ },
+ "by_braintree_customer_id": {
+ "map": " function(doc) {\n if ((doc['type'] == 'Customer') && (doc['braintree_customer_id'] != null)) {\n emit(doc['braintree_customer_id'], 1);\n }\n }\n",
+ "reduce": "_sum"
+ },
+ "all": {
+ "map": " function(doc) {\n if (doc['type'] == 'Customer') {\n emit(doc._id, null);\n }\n }\n"
+ }
+ },
+ "couchrest-hash": "688c401ec0230b75625c176a88fc4a02"
+} \ No newline at end of file
diff --git a/puppet/modules/site_couchdb/files/designs/identities/Identity.json b/puppet/modules/site_couchdb/files/designs/identities/Identity.json
new file mode 100644
index 00000000..b1c567c1
--- /dev/null
+++ b/puppet/modules/site_couchdb/files/designs/identities/Identity.json
@@ -0,0 +1,34 @@
+{
+ "_id": "_design/Identity",
+ "language": "javascript",
+ "views": {
+ "by_address_and_destination": {
+ "map": " function(doc) {\n if ((doc['type'] == 'Identity') && (doc['address'] != null) && (doc['destination'] != null)) {\n emit([doc['address'], doc['destination']], 1);\n }\n }\n",
+ "reduce": "_sum"
+ },
+ "all": {
+ "map": " function(doc) {\n if (doc['type'] == 'Identity') {\n emit(doc._id, null);\n }\n }\n"
+ },
+ "cert_fingerprints_by_expiry": {
+ "map": "function(doc) {\n if (doc.type != 'Identity') {\n return;\n }\n if (typeof doc.cert_fingerprints === \"object\") {\n for (fp in doc.cert_fingerprints) {\n if (doc.cert_fingerprints.hasOwnProperty(fp)) {\n emit(doc.cert_fingerprints[fp], fp);\n }\n }\n }\n}\n"
+ },
+ "cert_expiry_by_fingerprint": {
+ "map": "function(doc) {\n if (doc.type != 'Identity') {\n return;\n }\n if (typeof doc.cert_fingerprints === \"object\") {\n for (fp in doc.cert_fingerprints) {\n if (doc.cert_fingerprints.hasOwnProperty(fp)) {\n emit(fp, doc.cert_fingerprints[fp]);\n }\n }\n }\n}\n"
+ },
+ "disabled": {
+ "map": "function(doc) {\n if (doc.type != 'Identity') {\n return;\n }\n if (typeof doc.user_id === \"undefined\") {\n emit(doc._id, 1);\n }\n}\n"
+ },
+ "pgp_key_by_email": {
+ "map": "function(doc) {\n if (doc.type != 'Identity') {\n return;\n }\n if (typeof doc.keys === \"object\") {\n emit(doc.address, doc.keys[\"pgp\"]);\n }\n}\n"
+ },
+ "by_user_id": {
+ "map": " function(doc) {\n if ((doc['type'] == 'Identity') && (doc['user_id'] != null)) {\n emit(doc['user_id'], 1);\n }\n }\n",
+ "reduce": "_sum"
+ },
+ "by_address": {
+ "map": " function(doc) {\n if ((doc['type'] == 'Identity') && (doc['address'] != null)) {\n emit(doc['address'], 1);\n }\n }\n",
+ "reduce": "_sum"
+ }
+ },
+ "couchrest-hash": "4a774c3f56122b655a314670403b27e2"
+} \ No newline at end of file
diff --git a/puppet/modules/site_couchdb/files/designs/invite_codes/InviteCode.json b/puppet/modules/site_couchdb/files/designs/invite_codes/InviteCode.json
new file mode 100644
index 00000000..006c1ea1
--- /dev/null
+++ b/puppet/modules/site_couchdb/files/designs/invite_codes/InviteCode.json
@@ -0,0 +1,22 @@
+{
+ "_id": "_design/InviteCode",
+ "language": "javascript",
+ "views": {
+ "by__id": {
+ "map": " function(doc) {\n if ((doc['type'] == 'InviteCode') && (doc['_id'] != null)) {\n emit(doc['_id'], 1);\n }\n }\n",
+ "reduce": "_sum"
+ },
+ "by_invite_code": {
+ "map": " function(doc) {\n if ((doc['type'] == 'InviteCode') && (doc['invite_code'] != null)) {\n emit(doc['invite_code'], 1);\n }\n }\n",
+ "reduce": "_sum"
+ },
+ "by_invite_count": {
+ "map": " function(doc) {\n if ((doc['type'] == 'InviteCode') && (doc['invite_count'] != null)) {\n emit(doc['invite_count'], 1);\n }\n }\n",
+ "reduce": "_sum"
+ },
+ "all": {
+ "map": " function(doc) {\n if (doc['type'] == 'InviteCode') {\n emit(doc._id, null);\n }\n }\n"
+ }
+ },
+ "couchrest-hash": "83fb8f504520b4a9c7ddbb7928cd0ce3"
+} \ No newline at end of file
diff --git a/puppet/modules/site_couchdb/files/designs/messages/Message.json b/puppet/modules/site_couchdb/files/designs/messages/Message.json
new file mode 100644
index 00000000..6a48fc4d
--- /dev/null
+++ b/puppet/modules/site_couchdb/files/designs/messages/Message.json
@@ -0,0 +1,18 @@
+{
+ "_id": "_design/Message",
+ "language": "javascript",
+ "views": {
+ "by_user_ids_to_show": {
+ "map": "function (doc) {\n if (doc.type === 'Message' && doc.user_ids_to_show && Array.isArray(doc.user_ids_to_show)) {\n doc.user_ids_to_show.forEach(function (userId) {\n emit(userId, 1);\n });\n }\n}\n",
+ "reduce": " function(key, values, rereduce) {\n return sum(values);\n }\n"
+ },
+ "by_user_ids_to_show_and_created_at": {
+ "map": "// not using at moment\n// call with something like Message.by_user_ids_to_show_and_created_at.startkey([user_id, start_date]).endkey([user_id,end_date])\nfunction (doc) {\n if (doc.type === 'Message' && doc.user_ids_to_show && Array.isArray(doc.user_ids_to_show)) {\n doc.user_ids_to_show.forEach(function (userId) {\n emit([userId, doc.created_at], 1);\n });\n }\n}\n",
+ "reduce": " function(key, values, rereduce) {\n return sum(values);\n }\n"
+ },
+ "all": {
+ "map": " function(doc) {\n if (doc['type'] == 'Message') {\n emit(doc._id, null);\n }\n }\n"
+ }
+ },
+ "couchrest-hash": "ba80168e51015d2678cad88fc6c5b986"
+} \ No newline at end of file
diff --git a/puppet/modules/site_couchdb/files/designs/sessions/Session.json b/puppet/modules/site_couchdb/files/designs/sessions/Session.json
new file mode 100644
index 00000000..70202780
--- /dev/null
+++ b/puppet/modules/site_couchdb/files/designs/sessions/Session.json
@@ -0,0 +1,8 @@
+{
+ "views": {
+ "by_expires": {
+ "reduce": "_sum",
+ "map": "function(doc) {\n if(typeof doc.expires !== \"undefined\") {\n emit(doc.expires, 1);\n }\n}\n"
+ }
+ }
+}
diff --git a/puppet/modules/site_couchdb/files/designs/shared/docs.json b/puppet/modules/site_couchdb/files/designs/shared/docs.json
new file mode 100644
index 00000000..004180cd
--- /dev/null
+++ b/puppet/modules/site_couchdb/files/designs/shared/docs.json
@@ -0,0 +1,8 @@
+{
+ "_id": "_design/docs",
+ "views": {
+ "get": {
+ "map": "function(doc) {\n if (doc.u1db_rev) {\n var is_tombstone = true;\n var has_conflicts = false;\n if (doc._attachments) {\n if (doc._attachments.u1db_content)\n is_tombstone = false;\n if (doc._attachments.u1db_conflicts)\n has_conflicts = true;\n }\n emit(doc._id,\n {\n \"couch_rev\": doc._rev,\n \"u1db_rev\": doc.u1db_rev,\n \"is_tombstone\": is_tombstone,\n \"has_conflicts\": has_conflicts,\n }\n );\n }\n}\n"
+ }
+ }
+} \ No newline at end of file
diff --git a/puppet/modules/site_couchdb/files/designs/shared/syncs.json b/puppet/modules/site_couchdb/files/designs/shared/syncs.json
new file mode 100644
index 00000000..bab5622f
--- /dev/null
+++ b/puppet/modules/site_couchdb/files/designs/shared/syncs.json
@@ -0,0 +1,11 @@
+{
+ "_id": "_design/syncs",
+ "updates": {
+ "put": "function(doc, req){\n if (!doc) {\n doc = {}\n doc['_id'] = 'u1db_sync_log';\n doc['syncs'] = [];\n }\n body = JSON.parse(req.body);\n // remove outdated info\n doc['syncs'] = doc['syncs'].filter(\n function (entry) {\n return entry[0] != body['other_replica_uid'];\n }\n );\n // store u1db rev\n doc['syncs'].push([\n body['other_replica_uid'],\n body['other_generation'],\n body['other_transaction_id']\n ]);\n return [doc, 'ok'];\n}\n\n"
+ },
+ "views": {
+ "log": {
+ "map": "function(doc) {\n if (doc._id == 'u1db_sync_log') {\n if (doc.syncs)\n doc.syncs.forEach(function (entry) {\n emit(entry[0],\n {\n 'known_generation': entry[1],\n 'known_transaction_id': entry[2]\n });\n });\n }\n}\n"
+ }
+ }
+} \ No newline at end of file
diff --git a/puppet/modules/site_couchdb/files/designs/shared/transactions.json b/puppet/modules/site_couchdb/files/designs/shared/transactions.json
new file mode 100644
index 00000000..106ad46c
--- /dev/null
+++ b/puppet/modules/site_couchdb/files/designs/shared/transactions.json
@@ -0,0 +1,13 @@
+{
+ "_id": "_design/transactions",
+ "lists": {
+ "generation": "function(head, req) {\n var row;\n var rows=[];\n // fetch all rows\n while(row = getRow()) {\n rows.push(row);\n }\n if (rows.length > 0)\n send(JSON.stringify({\n \"generation\": rows.length,\n \"doc_id\": rows[rows.length-1]['id'],\n \"transaction_id\": rows[rows.length-1]['value']\n }));\n else\n send(JSON.stringify({\n \"generation\": 0,\n \"doc_id\": \"\",\n \"transaction_id\": \"\",\n }));\n}\n",
+ "trans_id_for_gen": "function(head, req) {\n var row;\n var rows=[];\n var i = 1;\n var gen = 1;\n if (req.query.gen)\n gen = parseInt(req.query['gen']);\n // fetch all rows\n while(row = getRow())\n rows.push(row);\n if (gen <= rows.length)\n send(JSON.stringify({\n \"generation\": gen,\n \"doc_id\": rows[gen-1]['id'],\n \"transaction_id\": rows[gen-1]['value'],\n }));\n else\n send('{}');\n}\n",
+ "whats_changed": "function(head, req) {\n var row;\n var gen = 1;\n var old_gen = 0;\n if (req.query.old_gen)\n old_gen = parseInt(req.query['old_gen']);\n send('{\"transactions\":[\\n');\n // fetch all rows\n while(row = getRow()) {\n if (gen > old_gen) {\n if (gen > old_gen+1)\n send(',\\n');\n send(JSON.stringify({\n \"generation\": gen,\n \"doc_id\": row[\"id\"],\n \"transaction_id\": row[\"value\"]\n }));\n }\n gen++;\n }\n send('\\n]}');\n}\n"
+ },
+ "views": {
+ "log": {
+ "map": "function(doc) {\n if (doc.u1db_transactions)\n doc.u1db_transactions.forEach(function(t) {\n emit(t[0], // use timestamp as key so the results are ordered\n t[1]); // value is the transaction_id\n });\n}\n"
+ }
+ }
+} \ No newline at end of file
diff --git a/puppet/modules/site_couchdb/files/designs/tickets/Ticket.json b/puppet/modules/site_couchdb/files/designs/tickets/Ticket.json
new file mode 100644
index 00000000..578f632b
--- /dev/null
+++ b/puppet/modules/site_couchdb/files/designs/tickets/Ticket.json
@@ -0,0 +1,50 @@
+{
+ "_id": "_design/Ticket",
+ "language": "javascript",
+ "views": {
+ "by_updated_at": {
+ "map": " function(doc) {\n if ((doc['type'] == 'Ticket') && (doc['updated_at'] != null)) {\n emit(doc['updated_at'], 1);\n }\n }\n",
+ "reduce": "_sum"
+ },
+ "by_created_at": {
+ "map": " function(doc) {\n if ((doc['type'] == 'Ticket') && (doc['created_at'] != null)) {\n emit(doc['created_at'], 1);\n }\n }\n",
+ "reduce": "_sum"
+ },
+ "by_created_by": {
+ "map": " function(doc) {\n if ((doc['type'] == 'Ticket') && (doc['created_by'] != null)) {\n emit(doc['created_by'], 1);\n }\n }\n",
+ "reduce": "_sum"
+ },
+ "by_is_open_and_created_at": {
+ "map": " function(doc) {\n if ((doc['type'] == 'Ticket') && (doc['is_open'] != null) && (doc['created_at'] != null)) {\n emit([doc['is_open'], doc['created_at']], 1);\n }\n }\n",
+ "reduce": "_sum"
+ },
+ "by_is_open_and_updated_at": {
+ "map": " function(doc) {\n if ((doc['type'] == 'Ticket') && (doc['is_open'] != null) && (doc['updated_at'] != null)) {\n emit([doc['is_open'], doc['updated_at']], 1);\n }\n }\n",
+ "reduce": "_sum"
+ },
+ "by_includes_post_by_and_is_open_and_created_at": {
+ "map": "function(doc) {\n var arr = {}\n if (doc['type'] == 'Ticket' && doc.comments) {\n doc.comments.forEach(function(comment){\n if (comment.posted_by && !arr[comment.posted_by]) {\n //don't add duplicates\n arr[comment.posted_by] = true;\n emit([comment.posted_by, doc.is_open, doc.created_at], 1);\n }\n });\n }\n}\n",
+ "reduce": " function(key, values, rereduce) {\n return sum(values);\n }\n"
+ },
+ "by_includes_post_by": {
+ "map": "// TODO: This view is only used in tests--should we keep it?\nfunction(doc) {\n var arr = {}\n if (doc['type'] == 'Ticket' && doc.comments) {\n doc.comments.forEach(function(comment){\n if (comment.posted_by && !arr[comment.posted_by]) {\n //don't add duplicates\n arr[comment.posted_by] = true;\n emit(comment.posted_by, 1);\n }\n });\n }\n}\n",
+ "reduce": " function(key, values, rereduce) {\n return sum(values);\n }\n"
+ },
+ "by_includes_post_by_and_is_open_and_updated_at": {
+ "map": "function(doc) {\n var arr = {}\n if (doc['type'] == 'Ticket' && doc.comments) {\n doc.comments.forEach(function(comment){\n if (comment.posted_by && !arr[comment.posted_by]) {\n //don't add duplicates\n arr[comment.posted_by] = true;\n emit([comment.posted_by, doc.is_open, doc.updated_at], 1);\n }\n });\n }\n}\n",
+ "reduce": " function(key, values, rereduce) {\n return sum(values);\n }\n"
+ },
+ "by_includes_post_by_and_created_at": {
+ "map": "function(doc) {\n var arr = {}\n if (doc['type'] == 'Ticket' && doc.comments) {\n doc.comments.forEach(function(comment){\n if (comment.posted_by && !arr[comment.posted_by]) {\n //don't add duplicates\n arr[comment.posted_by] = true;\n emit([comment.posted_by, doc.created_at], 1);\n }\n });\n }\n}\n",
+ "reduce": " function(key, values, rereduce) {\n return sum(values);\n }\n"
+ },
+ "by_includes_post_by_and_updated_at": {
+ "map": "function(doc) {\n var arr = {}\n if (doc['type'] == 'Ticket' && doc.comments) {\n doc.comments.forEach(function(comment){\n if (comment.posted_by && !arr[comment.posted_by]) {\n //don't add duplicates\n arr[comment.posted_by] = true;\n emit([comment.posted_by, doc.updated_at], 1);\n }\n });\n }\n}\n",
+ "reduce": " function(key, values, rereduce) {\n return sum(values);\n }\n"
+ },
+ "all": {
+ "map": " function(doc) {\n if (doc['type'] == 'Ticket') {\n emit(doc._id, null);\n }\n }\n"
+ }
+ },
+ "couchrest-hash": "b21eaeea8ea66bfda65581b1b7ce06af"
+} \ No newline at end of file
diff --git a/puppet/modules/site_couchdb/files/designs/tokens/Token.json b/puppet/modules/site_couchdb/files/designs/tokens/Token.json
new file mode 100644
index 00000000..b9025f15
--- /dev/null
+++ b/puppet/modules/site_couchdb/files/designs/tokens/Token.json
@@ -0,0 +1,14 @@
+{
+ "_id": "_design/Token",
+ "language": "javascript",
+ "views": {
+ "by_last_seen_at": {
+ "map": " function(doc) {\n if ((doc['type'] == 'Token') && (doc['last_seen_at'] != null)) {\n emit(doc['last_seen_at'], 1);\n }\n }\n",
+ "reduce": "_sum"
+ },
+ "all": {
+ "map": " function(doc) {\n if (doc['type'] == 'Token') {\n emit(doc._id, null);\n }\n }\n"
+ }
+ },
+ "couchrest-hash": "541dd924551c42a2317b345effbe65cc"
+} \ No newline at end of file
diff --git a/puppet/modules/site_couchdb/files/designs/users/User.json b/puppet/modules/site_couchdb/files/designs/users/User.json
new file mode 100644
index 00000000..8a82cf4a
--- /dev/null
+++ b/puppet/modules/site_couchdb/files/designs/users/User.json
@@ -0,0 +1,22 @@
+{
+ "_id": "_design/User",
+ "language": "javascript",
+ "views": {
+ "by_login": {
+ "map": " function(doc) {\n if ((doc['type'] == 'User') && (doc['login'] != null)) {\n emit(doc['login'], 1);\n }\n }\n",
+ "reduce": "_sum"
+ },
+ "all": {
+ "map": " function(doc) {\n if (doc['type'] == 'User') {\n emit(doc._id, null);\n }\n }\n"
+ },
+ "by_created_at_and_one_month_warning_not_sent": {
+ "map": "function (doc) {\n if ((doc['type'] == 'User') && (doc['created_at'] != null) && (doc['one_month_warning_sent'] == null)) {\n emit(doc['created_at'], 1);\n } \n}\n",
+ "reduce": " function(key, values, rereduce) {\n return sum(values);\n }\n"
+ },
+ "by_created_at": {
+ "map": " function(doc) {\n if ((doc['type'] == 'User') && (doc['created_at'] != null)) {\n emit(doc['created_at'], 1);\n }\n }\n",
+ "reduce": "_sum"
+ }
+ },
+ "couchrest-hash": "d854607d299887a347e554176cb79e20"
+} \ No newline at end of file
diff --git a/puppet/modules/site_couchdb/files/leap_ca_daemon b/puppet/modules/site_couchdb/files/leap_ca_daemon
new file mode 100755
index 00000000..9a1a0bc7
--- /dev/null
+++ b/puppet/modules/site_couchdb/files/leap_ca_daemon
@@ -0,0 +1,157 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides: leap_ca_daemon
+# Required-Start: $remote_fs $syslog
+# Required-Stop: $remote_fs $syslog
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: leap_ca_daemon initscript
+# Description: Controls leap_ca_daemon (see https://github.com/leapcode/leap_ca
+# for more information.
+### END INIT INFO
+
+# Author: varac <varac@leap.se>
+#
+
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="leap_ca_daemon initscript"
+NAME=leap_ca_daemon
+DAEMON=/usr/local/bin/$NAME
+DAEMON_ARGS="run "
+PIDFILE=/var/run/$NAME.pid
+SCRIPTNAME=/etc/init.d/$NAME
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
+# and status_of_proc is working.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+ # Return
+ # 0 if daemon has been started
+ # 1 if daemon was already running
+ # 2 if daemon could not be started
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
+ || return 1
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
+ $DAEMON_ARGS \
+ || return 2
+ # Add code here, if necessary, that waits for the process to be ready
+ # to handle requests from services started subsequently which depend
+ # on this one. As a last resort, sleep for some time.
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+ # Return
+ # 0 if daemon has been stopped
+ # 1 if daemon was already stopped
+ # 2 if daemon could not be stopped
+ # other if a failure occurred
+ start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
+ RETVAL="$?"
+ [ "$RETVAL" = 2 ] && return 2
+ # Wait for children to finish too if this is a daemon that forks
+ # and if the daemon is only ever run from this initscript.
+ # If the above conditions are not satisfied then add some other code
+ # that waits for the process to drop all resources that could be
+ # needed by services started subsequently. A last resort is to
+ # sleep for some time.
+ start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
+ [ "$?" = 2 ] && return 2
+ # Many daemons don't delete their pidfiles when they exit.
+ rm -f $PIDFILE
+ return "$RETVAL"
+}
+
+#
+# Function that sends a SIGHUP to the daemon/service
+#
+do_reload() {
+ #
+ # If the daemon can reload its configuration without
+ # restarting (for example, when it is sent a SIGHUP),
+ # then implement that here.
+ #
+ start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
+ return 0
+}
+
+case "$1" in
+ start)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
+ do_start
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ stop)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ status)
+ status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
+ ;;
+ #reload|force-reload)
+ #
+ # If do_reload() is not implemented then leave this commented out
+ # and leave 'force-reload' as an alias for 'restart'.
+ #
+ #log_daemon_msg "Reloading $DESC" "$NAME"
+ #do_reload
+ #log_end_msg $?
+ #;;
+ restart|force-reload)
+ #
+ # If the "reload" option is implemented then remove the
+ # 'force-reload' alias
+ #
+ log_daemon_msg "Restarting $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1)
+ do_start
+ case "$?" in
+ 0) log_end_msg 0 ;;
+ 1) log_end_msg 1 ;; # Old process is still running
+ *) log_end_msg 1 ;; # Failed to start
+ esac
+ ;;
+ *)
+ # Failed to stop
+ log_end_msg 1
+ ;;
+ esac
+ ;;
+ *)
+ #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
+ echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
+ exit 3
+ ;;
+esac
+
+:
diff --git a/puppet/modules/site_couchdb/files/local.ini b/puppet/modules/site_couchdb/files/local.ini
new file mode 100644
index 00000000..b921a927
--- /dev/null
+++ b/puppet/modules/site_couchdb/files/local.ini
@@ -0,0 +1,8 @@
+; Puppet modified file !!
+
+; Custom settings should be made in this file. They will override settings
+; in default.ini, but unlike changes made to default.ini, this file won't be
+; overwritten on server upgrade.
+
+[compactions]
+_default = [{db_fragmentation, "70%"}, {view_fragmentation, "60%"}, {from, "03:00"}, {to, "05:00"}]
diff --git a/puppet/modules/site_couchdb/files/runit_config b/puppet/modules/site_couchdb/files/runit_config
new file mode 100644
index 00000000..169b4832
--- /dev/null
+++ b/puppet/modules/site_couchdb/files/runit_config
@@ -0,0 +1,6 @@
+#!/bin/bash
+exec 2>&1
+export HOME=/home/bigcouch
+ulimit -H -n 32768
+ulimit -S -n 32768
+exec chpst -u bigcouch /opt/bigcouch/bin/bigcouch
diff --git a/puppet/modules/site_couchdb/lib/puppet/parser/functions/rotated_db_name.rb b/puppet/modules/site_couchdb/lib/puppet/parser/functions/rotated_db_name.rb
new file mode 100644
index 00000000..6458ae81
--- /dev/null
+++ b/puppet/modules/site_couchdb/lib/puppet/parser/functions/rotated_db_name.rb
@@ -0,0 +1,24 @@
+module Puppet::Parser::Functions
+ newfunction(:rotated_db_name, :type => :rvalue, :doc => <<-EOS
+This function takes a database name string and returns a database name with the current rotation stamp appended.
+The first argument is the base name of the database. Subsequent arguments may contain these options:
+ * 'next' -- return the db name for the next rotation, not the current one.
+ * 'monthly' -- rotate monthly (default)
+ * 'weekly' -- rotate weekly
+*Examples:*
+ rotated_db_name('tokens') => 'tokens_551'
+ EOS
+ ) do |arguments|
+ if arguments.include?('weekly')
+ rotation_period = 604800 # 1 week
+ else
+ rotation_period = 2592000 # 1 month
+ end
+ suffix = Time.now.utc.to_i / rotation_period
+ if arguments.include?('next')
+ suffix += 1
+ end
+ "#{arguments.first}_#{suffix}"
+ end
+end
+
diff --git a/puppet/modules/site_couchdb/manifests/add_users.pp b/puppet/modules/site_couchdb/manifests/add_users.pp
new file mode 100644
index 00000000..c905316b
--- /dev/null
+++ b/puppet/modules/site_couchdb/manifests/add_users.pp
@@ -0,0 +1,57 @@
+# add couchdb users for all services
+class site_couchdb::add_users {
+
+ Class['site_couchdb::create_dbs']
+ -> Class['site_couchdb::add_users']
+
+ # Couchdb users
+
+ ## leap_mx couchdb user
+ ## read: identities
+ ## write access to user-<uuid>
+ couchdb::add_user { $site_couchdb::couchdb_leap_mx_user:
+ roles => '["identities"]',
+ pw => $site_couchdb::couchdb_leap_mx_pw,
+ salt => $site_couchdb::couchdb_leap_mx_salt,
+ require => Couchdb::Query::Setup['localhost']
+ }
+
+ ## nickserver couchdb user
+ ## r: identities
+ ## r/w: keycache
+ couchdb::add_user { $site_couchdb::couchdb_nickserver_user:
+ roles => '["identities","keycache"]',
+ pw => $site_couchdb::couchdb_nickserver_pw,
+ salt => $site_couchdb::couchdb_nickserver_salt,
+ require => Couchdb::Query::Setup['localhost']
+ }
+
+ ## soledad couchdb user
+ ## r/w: user-<uuid>, shared
+ ## read: tokens
+ couchdb::add_user { $site_couchdb::couchdb_soledad_user:
+ roles => '["tokens"]',
+ pw => $site_couchdb::couchdb_soledad_pw,
+ salt => $site_couchdb::couchdb_soledad_salt,
+ require => Couchdb::Query::Setup['localhost']
+ }
+
+ ## webapp couchdb user
+ ## read/write: users, tokens, sessions, tickets, identities, customer
+ couchdb::add_user { $site_couchdb::couchdb_webapp_user:
+ roles => '["tokens","identities","users"]',
+ pw => $site_couchdb::couchdb_webapp_pw,
+ salt => $site_couchdb::couchdb_webapp_salt,
+ require => Couchdb::Query::Setup['localhost']
+ }
+
+ ## replication couchdb user
+ ## read/write: all databases for replication
+ couchdb::add_user { $site_couchdb::couchdb_replication_user:
+ roles => '["replication"]',
+ pw => $site_couchdb::couchdb_replication_pw,
+ salt => $site_couchdb::couchdb_replication_salt,
+ require => Couchdb::Query::Setup['localhost']
+ }
+
+}
diff --git a/puppet/modules/site_couchdb/manifests/backup.pp b/puppet/modules/site_couchdb/manifests/backup.pp
new file mode 100644
index 00000000..8b5aa6ea
--- /dev/null
+++ b/puppet/modules/site_couchdb/manifests/backup.pp
@@ -0,0 +1,23 @@
+class site_couchdb::backup {
+
+ # general backupninja config
+ backupninja::config { 'backupninja_config':
+ usecolors => false,
+ }
+
+ # dump all DBs locally to /var/backups/couchdb once a day
+ backupninja::sh { 'couchdb_backup':
+ command_string => "cd /srv/leap/couchdb/scripts \n./couchdb_dumpall.sh"
+ }
+
+ # Deploy /etc/leap/couchdb_scripts_defaults.conf so we can exclude
+ # some databases
+
+ file { '/etc/leap/couchdb_scripts_defaults.conf':
+ source => 'puppet:///modules/site_couchdb/couchdb_scripts_defaults.conf',
+ mode => '0644',
+ owner => 'root',
+ group => 'root',
+ }
+
+}
diff --git a/puppet/modules/site_couchdb/manifests/bigcouch.pp b/puppet/modules/site_couchdb/manifests/bigcouch.pp
new file mode 100644
index 00000000..2de3d4d0
--- /dev/null
+++ b/puppet/modules/site_couchdb/manifests/bigcouch.pp
@@ -0,0 +1,50 @@
+# sets up bigcouch on couchdb node
+class site_couchdb::bigcouch {
+
+ $config = $::site_couchdb::couchdb_config['bigcouch']
+ $cookie = $config['cookie']
+ $ednp_port = $config['ednp_port']
+
+ class { 'couchdb':
+ admin_pw => $::site_couchdb::couchdb_admin_pw,
+ admin_salt => $::site_couchdb::couchdb_admin_salt,
+ bigcouch => true,
+ bigcouch_cookie => $cookie,
+ ednp_port => $ednp_port,
+ chttpd_bind_address => '127.0.0.1'
+ }
+
+ #
+ # stunnel must running correctly before bigcouch dbs can be set up.
+ #
+ Class['site_config::default']
+ -> Class['site_config::resolvconf']
+ -> Class['couchdb::bigcouch::package::cloudant']
+ -> Service['shorewall']
+ -> Exec['refresh_stunnel']
+ -> Class['site_couchdb::setup']
+ -> Class['site_couchdb::bigcouch::add_nodes']
+ -> Class['site_couchdb::bigcouch::settle_cluster']
+ -> Class['site_couchdb::create_dbs']
+
+ include site_couchdb::bigcouch::add_nodes
+ include site_couchdb::bigcouch::settle_cluster
+ include site_couchdb::bigcouch::compaction
+
+ file { '/var/log/bigcouch':
+ ensure => directory
+ }
+
+ file { '/etc/sv/bigcouch/run':
+ ensure => present,
+ source => 'puppet:///modules/site_couchdb/runit_config',
+ owner => root,
+ group => root,
+ mode => '0755',
+ require => Package['couchdb'],
+ notify => Service['couchdb']
+ }
+
+ include site_check_mk::agent::couchdb::bigcouch
+
+}
diff --git a/puppet/modules/site_couchdb/manifests/bigcouch/add_nodes.pp b/puppet/modules/site_couchdb/manifests/bigcouch/add_nodes.pp
new file mode 100644
index 00000000..c8c43275
--- /dev/null
+++ b/puppet/modules/site_couchdb/manifests/bigcouch/add_nodes.pp
@@ -0,0 +1,8 @@
+class site_couchdb::bigcouch::add_nodes {
+ # loop through neighbors array and add nodes
+ $nodes = $::site_couchdb::bigcouch::config['neighbors']
+
+ couchdb::bigcouch::add_node { $nodes:
+ require => Couchdb::Query::Setup['localhost']
+ }
+}
diff --git a/puppet/modules/site_couchdb/manifests/bigcouch/compaction.pp b/puppet/modules/site_couchdb/manifests/bigcouch/compaction.pp
new file mode 100644
index 00000000..84aab4ef
--- /dev/null
+++ b/puppet/modules/site_couchdb/manifests/bigcouch/compaction.pp
@@ -0,0 +1,8 @@
+class site_couchdb::bigcouch::compaction {
+ cron {
+ 'compact_all_shards':
+ command => '/srv/leap/couchdb/scripts/bigcouch_compact_all_shards.sh >> /var/log/bigcouch/compaction.log',
+ hour => 3,
+ minute => 17;
+ }
+}
diff --git a/puppet/modules/site_couchdb/manifests/bigcouch/settle_cluster.pp b/puppet/modules/site_couchdb/manifests/bigcouch/settle_cluster.pp
new file mode 100644
index 00000000..820b5be2
--- /dev/null
+++ b/puppet/modules/site_couchdb/manifests/bigcouch/settle_cluster.pp
@@ -0,0 +1,11 @@
+class site_couchdb::bigcouch::settle_cluster {
+
+ exec { 'wait_for_couch_nodes':
+ command => '/srv/leap/bin/run_tests --test CouchDB/Are_configured_nodes_online? --retry 12 --wait 10'
+ }
+
+ exec { 'settle_cluster_membership':
+ command => '/srv/leap/bin/run_tests --test CouchDB/Is_cluster_membership_ok? --retry 12 --wait 10',
+ require => Exec['wait_for_couch_nodes']
+ }
+}
diff --git a/puppet/modules/site_couchdb/manifests/create_dbs.pp b/puppet/modules/site_couchdb/manifests/create_dbs.pp
new file mode 100644
index 00000000..a2d1c655
--- /dev/null
+++ b/puppet/modules/site_couchdb/manifests/create_dbs.pp
@@ -0,0 +1,102 @@
+# creates neccesary databases
+class site_couchdb::create_dbs {
+
+ Class['site_couchdb::setup']
+ -> Class['site_couchdb::create_dbs']
+
+ ### customer database
+ ### r/w: webapp,
+ couchdb::create_db { 'customers':
+ members => "{ \"names\": [\"${site_couchdb::couchdb_webapp_user}\"], \"roles\": [\"replication\"] }",
+ require => Couchdb::Query::Setup['localhost']
+ }
+
+ ## identities database
+ ## r: nickserver, leap_mx - needs to be restrict with design document
+ ## r/w: webapp
+ couchdb::create_db { 'identities':
+ members => "{ \"names\": [], \"roles\": [\"replication\", \"identities\"] }",
+ require => Couchdb::Query::Setup['localhost']
+ }
+
+ ## keycache database
+ ## r/w: nickserver
+ couchdb::create_db { 'keycache':
+ members => "{ \"names\": [], \"roles\": [\"replication\", \"keycache\"] }",
+ require => Couchdb::Query::Setup['localhost']
+ }
+
+ ## sessions database
+ ## r/w: webapp
+ $sessions_db = rotated_db_name('sessions', 'monthly')
+ couchdb::create_db { $sessions_db:
+ members => "{ \"names\": [\"${site_couchdb::couchdb_webapp_user}\"], \"roles\": [\"replication\"] }",
+ require => Couchdb::Query::Setup['localhost']
+ }
+
+ $sessions_next_db = rotated_db_name('sessions', 'monthly', 'next')
+ couchdb::create_db { $sessions_next_db:
+ members => "{ \"names\": [\"${site_couchdb::couchdb_webapp_user}\"], \"roles\": [\"replication\"] }",
+ require => Couchdb::Query::Setup['localhost']
+ }
+
+ ## shared database
+ ## r/w: soledad
+ couchdb::create_db { 'shared':
+ members => "{ \"names\": [\"${site_couchdb::couchdb_soledad_user}\"], \"roles\": [\"replication\"] }",
+ require => Couchdb::Query::Setup['localhost']
+ }
+
+ ## tickets database
+ ## r/w: webapp
+ couchdb::create_db { 'tickets':
+ members => "{ \"names\": [\"${site_couchdb::couchdb_webapp_user}\"], \"roles\": [\"replication\"] }",
+ require => Couchdb::Query::Setup['localhost']
+ }
+
+ ## tokens database
+ ## r: soledad - needs to be restricted with a design document
+ ## r/w: webapp
+ $tokens_db = rotated_db_name('tokens', 'monthly')
+ couchdb::create_db { $tokens_db:
+ members => "{ \"names\": [], \"roles\": [\"replication\", \"tokens\"] }",
+ require => Couchdb::Query::Setup['localhost']
+ }
+
+ $tokens_next_db = rotated_db_name('tokens', 'monthly', 'next')
+ couchdb::create_db { $tokens_next_db:
+ members => "{ \"names\": [], \"roles\": [\"replication\", \"tokens\"] }",
+ require => Couchdb::Query::Setup['localhost']
+ }
+
+ ## users database
+ ## r/w: webapp
+ couchdb::create_db { 'users':
+ members => "{ \"names\": [], \"roles\": [\"replication\", \"users\"] }",
+ require => Couchdb::Query::Setup['localhost']
+ }
+
+ ## tmp_users database
+ ## r/w: webapp
+ couchdb::create_db { 'tmp_users':
+ members => "{ \"names\": [], \"roles\": [\"replication\", \"users\"] }",
+ require => Couchdb::Query::Setup['localhost']
+ }
+
+ ## messages db
+ ## store messages to the clients such as payment reminders
+ ## r/w: webapp
+ couchdb::create_db { 'messages':
+ members => "{ \"names\": [\"${site_couchdb::couchdb_webapp_user}\"], \"roles\": [\"replication\"] }",
+ require => Couchdb::Query::Setup['localhost']
+ }
+
+ ## invite_codes db
+ ## store invite codes for new signups
+ ## r/w: webapp
+ couchdb::create_db { 'invite_codes':
+ members => "{ \"names\": [\"${site_couchdb::couchdb_webapp_user}\"], \"roles\": [\"replication\"] }",
+ require => Couchdb::Query::Setup['localhost']
+ }
+
+}
diff --git a/puppet/modules/site_couchdb/manifests/designs.pp b/puppet/modules/site_couchdb/manifests/designs.pp
new file mode 100644
index 00000000..e5fd94c6
--- /dev/null
+++ b/puppet/modules/site_couchdb/manifests/designs.pp
@@ -0,0 +1,46 @@
+class site_couchdb::designs {
+
+ Class['site_couchdb::create_dbs']
+ -> Class['site_couchdb::designs']
+
+ file { '/srv/leap/couchdb/designs':
+ ensure => directory,
+ source => 'puppet:///modules/site_couchdb/designs',
+ recurse => true,
+ purge => true,
+ mode => '0755'
+ }
+
+ site_couchdb::upload_design {
+ 'customers': design => 'customers/Customer.json';
+ 'identities': design => 'identities/Identity.json';
+ 'tickets': design => 'tickets/Ticket.json';
+ 'messages': design => 'messages/Message.json';
+ 'users': design => 'users/User.json';
+ 'tmp_users': design => 'users/User.json';
+ 'invite_codes': design => 'invite_codes/InviteCode.json';
+ 'shared_docs':
+ db => 'shared',
+ design => 'shared/docs.json';
+ 'shared_syncs':
+ db => 'shared',
+ design => 'shared/syncs.json';
+ 'shared_transactions':
+ db => 'shared',
+ design => 'shared/transactions.json';
+ }
+
+ $sessions_db = rotated_db_name('sessions', 'monthly')
+ $sessions_next_db = rotated_db_name('sessions', 'monthly', 'next')
+ site_couchdb::upload_design {
+ $sessions_db: design => 'sessions/Session.json';
+ $sessions_next_db: design => 'sessions/Session.json';
+ }
+
+ $tokens_db = rotated_db_name('tokens', 'monthly')
+ $tokens_next_db = rotated_db_name('tokens', 'monthly', 'next')
+ site_couchdb::upload_design {
+ $tokens_db: design => 'tokens/Token.json';
+ $tokens_next_db: design => 'tokens/Token.json';
+ }
+}
diff --git a/puppet/modules/site_couchdb/manifests/init.pp b/puppet/modules/site_couchdb/manifests/init.pp
new file mode 100644
index 00000000..c4fe6277
--- /dev/null
+++ b/puppet/modules/site_couchdb/manifests/init.pp
@@ -0,0 +1,81 @@
+# entry class for configuring couchdb/bigcouch node
+# couchdb node
+class site_couchdb {
+ tag 'leap_service'
+
+ $couchdb_config = hiera('couch')
+ $couchdb_users = $couchdb_config['users']
+
+ $couchdb_admin = $couchdb_users['admin']
+ $couchdb_admin_user = $couchdb_admin['username']
+ $couchdb_admin_pw = $couchdb_admin['password']
+ $couchdb_admin_salt = $couchdb_admin['salt']
+
+ $couchdb_leap_mx = $couchdb_users['leap_mx']
+ $couchdb_leap_mx_user = $couchdb_leap_mx['username']
+ $couchdb_leap_mx_pw = $couchdb_leap_mx['password']
+ $couchdb_leap_mx_salt = $couchdb_leap_mx['salt']
+
+ $couchdb_nickserver = $couchdb_users['nickserver']
+ $couchdb_nickserver_user = $couchdb_nickserver['username']
+ $couchdb_nickserver_pw = $couchdb_nickserver['password']
+ $couchdb_nickserver_salt = $couchdb_nickserver['salt']
+
+ $couchdb_soledad = $couchdb_users['soledad']
+ $couchdb_soledad_user = $couchdb_soledad['username']
+ $couchdb_soledad_pw = $couchdb_soledad['password']
+ $couchdb_soledad_salt = $couchdb_soledad['salt']
+
+ $couchdb_webapp = $couchdb_users['webapp']
+ $couchdb_webapp_user = $couchdb_webapp['username']
+ $couchdb_webapp_pw = $couchdb_webapp['password']
+ $couchdb_webapp_salt = $couchdb_webapp['salt']
+
+ $couchdb_replication = $couchdb_users['replication']
+ $couchdb_replication_user = $couchdb_replication['username']
+ $couchdb_replication_pw = $couchdb_replication['password']
+ $couchdb_replication_salt = $couchdb_replication['salt']
+
+ $couchdb_backup = $couchdb_config['backup']
+ $couchdb_mode = $couchdb_config['mode']
+
+ # ensure bigcouch has been purged from the system:
+ # TODO: remove this check in 0.9 release
+ if file('/opt/bigcouch/bin/bigcouch', '/dev/null') != '' {
+ fail 'ERROR: BigCouch appears to be installed. Make sure you have migrated to CouchDB before proceeding. See https://leap.se/upgrade-0-8'
+ }
+
+ include site_couchdb::plain
+
+ Class['site_config::default']
+ -> Service['shorewall']
+ -> Exec['refresh_stunnel']
+ -> Class['couchdb']
+ -> Class['site_couchdb::setup']
+
+ include ::site_config::default
+ include site_stunnel
+
+ include site_couchdb::setup
+ include site_couchdb::create_dbs
+ include site_couchdb::add_users
+ include site_couchdb::designs
+ include site_couchdb::logrotate
+
+ if $couchdb_backup { include site_couchdb::backup }
+
+ include site_check_mk::agent::couchdb
+
+ # remove tapicero leftovers on couchdb nodes
+ include site_config::remove::tapicero
+
+ # Destroy every per-user storage database
+ # where the corresponding user record does not exist.
+ cron { 'cleanup_stale_userdbs':
+ command => '(/bin/date; /srv/leap/couchdb/scripts/cleanup-user-dbs) >> /var/log/leap/couchdb-cleanup.log',
+ user => 'root',
+ hour => 4,
+ minute => 7;
+ }
+
+}
diff --git a/puppet/modules/site_couchdb/manifests/logrotate.pp b/puppet/modules/site_couchdb/manifests/logrotate.pp
new file mode 100644
index 00000000..bb8843bb
--- /dev/null
+++ b/puppet/modules/site_couchdb/manifests/logrotate.pp
@@ -0,0 +1,14 @@
+# configure couchdb logrotation
+class site_couchdb::logrotate {
+
+ augeas {
+ 'logrotate_bigcouch':
+ context => '/files/etc/logrotate.d/bigcouch/rule',
+ changes => [
+ 'set file /opt/bigcouch/var/log/*.log', 'set rotate 7',
+ 'set schedule daily', 'set compress compress',
+ 'set missingok missingok', 'set ifempty notifempty',
+ 'set copytruncate copytruncate' ]
+ }
+
+}
diff --git a/puppet/modules/site_couchdb/manifests/mirror.pp b/puppet/modules/site_couchdb/manifests/mirror.pp
new file mode 100644
index 00000000..fb82b897
--- /dev/null
+++ b/puppet/modules/site_couchdb/manifests/mirror.pp
@@ -0,0 +1,78 @@
+# configure mirroring of couch nodes
+class site_couchdb::mirror {
+
+ Class['site_couchdb::add_users']
+ -> Class['site_couchdb::mirror']
+
+ class { 'couchdb':
+ admin_pw => $site_couchdb::couchdb_admin_pw,
+ admin_salt => $site_couchdb::couchdb_admin_salt,
+ chttpd_bind_address => '127.0.0.1'
+ }
+
+ $masters = $site_couchdb::couchdb_config['replication']['masters']
+ $master_node_names = keys($site_couchdb::couchdb_config['replication']['masters'])
+ $master_node = $masters[$master_node_names[0]]
+ $user = $site_couchdb::couchdb_replication_user
+ $password = $site_couchdb::couchdb_replication_pw
+ $from_host = $master_node['domain_internal']
+ $from_port = $master_node['couch_port']
+ $from = "http://${user}:${password}@${from_host}:${from_port}"
+
+ notice("mirror from: ${from}")
+
+ ### customer database
+ couchdb::mirror_db { 'customers':
+ from => $from,
+ require => Couchdb::Query::Setup['localhost']
+ }
+
+ ## identities database
+ couchdb::mirror_db { 'identities':
+ from => $from,
+ require => Couchdb::Query::Setup['localhost']
+ }
+
+ ## keycache database
+ couchdb::mirror_db { 'keycache':
+ from => $from,
+ require => Couchdb::Query::Setup['localhost']
+ }
+
+ ## sessions database
+ couchdb::mirror_db { 'sessions':
+ from => $from,
+ require => Couchdb::Query::Setup['localhost']
+ }
+
+ ## shared database
+ couchdb::mirror_db { 'shared':
+ from => $from,
+ require => Couchdb::Query::Setup['localhost']
+ }
+
+ ## tickets database
+ couchdb::mirror_db { 'tickets':
+ from => $from,
+ require => Couchdb::Query::Setup['localhost']
+ }
+
+ ## tokens database
+ couchdb::mirror_db { 'tokens':
+ from => $from,
+ require => Couchdb::Query::Setup['localhost']
+ }
+
+ ## users database
+ couchdb::mirror_db { 'users':
+ from => $from,
+ require => Couchdb::Query::Setup['localhost']
+ }
+
+ ## messages db
+ couchdb::mirror_db { 'messages':
+ from => $from,
+ require => Couchdb::Query::Setup['localhost']
+ }
+
+}
diff --git a/puppet/modules/site_couchdb/manifests/plain.pp b/puppet/modules/site_couchdb/manifests/plain.pp
new file mode 100644
index 00000000..b40fc100
--- /dev/null
+++ b/puppet/modules/site_couchdb/manifests/plain.pp
@@ -0,0 +1,14 @@
+# this class sets up a single, plain couchdb node
+class site_couchdb::plain {
+ class { 'couchdb':
+ admin_pw => $site_couchdb::couchdb_admin_pw,
+ admin_salt => $site_couchdb::couchdb_admin_salt,
+ chttpd_bind_address => '127.0.0.1'
+ }
+
+ include site_check_mk::agent::couchdb::plain
+
+ # remove bigcouch leftovers from previous installations
+ include ::site_config::remove::bigcouch
+
+}
diff --git a/puppet/modules/site_couchdb/manifests/setup.pp b/puppet/modules/site_couchdb/manifests/setup.pp
new file mode 100644
index 00000000..710d3c1c
--- /dev/null
+++ b/puppet/modules/site_couchdb/manifests/setup.pp
@@ -0,0 +1,61 @@
+#
+# An initial setup class. All the other classes depend on this
+#
+class site_couchdb::setup {
+
+ # ensure that we don't have leftovers from previous installations
+ # where we installed the cloudant bigcouch package
+ # https://leap.se/code/issues/4971
+ class { 'couchdb::bigcouch::package::cloudant':
+ ensure => absent
+ }
+
+ $user = $site_couchdb::couchdb_admin_user
+
+ # setup /etc/couchdb/couchdb-admin.netrc for couchdb admin access
+ couchdb::query::setup { 'localhost':
+ user => $user,
+ pw => $site_couchdb::couchdb_admin_pw
+ }
+
+ # We symlink /etc/couchdb/couchdb-admin.netrc to /etc/couchdb/couchdb.netrc
+ # for puppet commands, and to to /root/.netrc for couchdb_scripts
+ # (eg. backup) and to makes life easier for the admin on the command line
+ # (i.e. using curl/wget without passing credentials)
+ file {
+ '/etc/couchdb/couchdb.netrc':
+ ensure => link,
+ target => "/etc/couchdb/couchdb-${user}.netrc";
+ '/root/.netrc':
+ ensure => link,
+ target => '/etc/couchdb/couchdb.netrc';
+ }
+
+ # setup /etc/couchdb/couchdb-soledad-admin.netrc file for couchdb admin
+ # access, accessible only for the soledad-admin user to create soledad
+ # userdbs
+ if member(hiera('services', []), 'soledad') {
+ file { '/etc/couchdb/couchdb-soledad-admin.netrc':
+ content => "machine localhost login ${user} password ${site_couchdb::couchdb_admin_pw}",
+ mode => '0400',
+ owner => 'soledad-admin',
+ group => 'root',
+ require => [ Package['couchdb'], User['soledad-admin'] ];
+ }
+ }
+
+ # Checkout couchdb_scripts repo
+ file {
+ '/srv/leap/couchdb':
+ ensure => directory
+ }
+
+ vcsrepo { '/srv/leap/couchdb/scripts':
+ ensure => present,
+ provider => git,
+ source => 'https://leap.se/git/couchdb_scripts',
+ revision => 'origin/master',
+ require => File['/srv/leap/couchdb']
+ }
+
+}
diff --git a/puppet/modules/site_couchdb/manifests/upload_design.pp b/puppet/modules/site_couchdb/manifests/upload_design.pp
new file mode 100644
index 00000000..bd73ebf2
--- /dev/null
+++ b/puppet/modules/site_couchdb/manifests/upload_design.pp
@@ -0,0 +1,14 @@
+# upload a design doc to a db
+define site_couchdb::upload_design($design, $db = $title) {
+ $design_name = regsubst($design, '^.*\/(.*)\.json$', '\1')
+ $id = "_design/${design_name}"
+ $file = "/srv/leap/couchdb/designs/${design}"
+ exec {
+ "upload_design_${name}":
+ command => "/usr/local/bin/couch-doc-update --host 127.0.0.1:5984 --db '${db}' --id '${id}' --data '{}' --file '${file}'",
+ refreshonly => false,
+ loglevel => debug,
+ logoutput => on_failure,
+ require => File['/srv/leap/couchdb/designs'];
+ }
+}
diff --git a/puppet/modules/site_haproxy/files/haproxy-stats.cfg b/puppet/modules/site_haproxy/files/haproxy-stats.cfg
new file mode 100644
index 00000000..e6335ba2
--- /dev/null
+++ b/puppet/modules/site_haproxy/files/haproxy-stats.cfg
@@ -0,0 +1,6 @@
+# provide access to stats for the nagios plugin
+listen stats 127.0.0.1:8000
+ mode http
+ stats enable
+ stats uri /haproxy
+
diff --git a/puppet/modules/site_haproxy/manifests/init.pp b/puppet/modules/site_haproxy/manifests/init.pp
new file mode 100644
index 00000000..b28ce80e
--- /dev/null
+++ b/puppet/modules/site_haproxy/manifests/init.pp
@@ -0,0 +1,41 @@
+class site_haproxy {
+ $haproxy = hiera('haproxy')
+
+ class { 'haproxy':
+ enable => true,
+ manage_service => true,
+ global_options => {
+ 'log' => '127.0.0.1 local0',
+ 'maxconn' => '4096',
+ 'stats' => 'socket /var/run/haproxy.sock user haproxy group haproxy',
+ 'chroot' => '/usr/share/haproxy',
+ 'user' => 'haproxy',
+ 'group' => 'haproxy',
+ 'daemon' => ''
+ },
+ defaults_options => {
+ 'log' => 'global',
+ 'retries' => '3',
+ 'option' => 'redispatch',
+ 'timeout connect' => '4000',
+ 'timeout client' => '20000',
+ 'timeout server' => '20000'
+ }
+ }
+
+ # monitor haproxy
+ concat::fragment { 'stats':
+ target => '/etc/haproxy/haproxy.cfg',
+ order => '90',
+ source => 'puppet:///modules/site_haproxy/haproxy-stats.cfg';
+ }
+
+ # Template uses $haproxy
+ concat::fragment { 'leap_haproxy_webapp_couchdb':
+ target => '/etc/haproxy/haproxy.cfg',
+ order => '20',
+ content => template('site_haproxy/haproxy.cfg.erb'),
+ }
+
+ include site_check_mk::agent::haproxy
+}
diff --git a/puppet/modules/site_haproxy/templates/couch.erb b/puppet/modules/site_haproxy/templates/couch.erb
new file mode 100644
index 00000000..f42e8368
--- /dev/null
+++ b/puppet/modules/site_haproxy/templates/couch.erb
@@ -0,0 +1,32 @@
+frontend couch
+ bind localhost:<%= @listen_port %>
+ mode http
+ option httplog
+ option dontlognull
+ option http-server-close # use client keep-alive, but close server connection.
+ use_backend couch_read if METH_GET
+ default_backend couch_write
+
+backend couch_write
+ mode http
+ balance roundrobin
+ option httpchk GET / # health check using simple get to root
+ option allbackups # balance among all backups, not just one.
+ default-server inter 3000 fastinter 1000 downinter 1000 rise 2 fall 1
+<%- @servers.sort.each do |name,server| -%>
+<%- next unless server['writable'] -%>
+ # <%=name%>
+ server couchdb_<%=server['port']%> <%=server['host']%>:<%=server['port']%> <%='backup' if server['backup']%> weight <%=server['weight']%> check
+<%- end -%>
+
+backend couch_read
+ mode http
+ balance roundrobin
+ option httpchk GET / # health check using simple get to root
+ option allbackups # balance among all backups, not just one.
+ default-server inter 3000 fastinter 1000 downinter 1000 rise 2 fall 1
+<%- @servers.sort.each do |name,server| -%>
+ # <%=name%>
+ server couchdb_<%=server['port']%> <%=server['host']%>:<%=server['port']%> <%='backup' if server['backup']%> weight <%=server['weight']%> check
+<%- end -%>
+
diff --git a/puppet/modules/site_haproxy/templates/haproxy.cfg.erb b/puppet/modules/site_haproxy/templates/haproxy.cfg.erb
new file mode 100644
index 00000000..8311b1a5
--- /dev/null
+++ b/puppet/modules/site_haproxy/templates/haproxy.cfg.erb
@@ -0,0 +1,11 @@
+<%- @haproxy.each do |frontend, options| -%>
+<%- if options['servers'] -%>
+
+##
+## <%= frontend %>
+##
+
+<%= scope.function_templatewlv(["site_haproxy/#{frontend}.erb", options]) %>
+<%- end -%>
+<%- end -%>
+
diff --git a/puppet/modules/site_mx/manifests/init.pp b/puppet/modules/site_mx/manifests/init.pp
new file mode 100644
index 00000000..a9b0198b
--- /dev/null
+++ b/puppet/modules/site_mx/manifests/init.pp
@@ -0,0 +1,20 @@
+class site_mx {
+ tag 'leap_service'
+ Class['site_config::default'] -> Class['site_mx']
+
+ include site_config::default
+ include site_config::x509::cert
+ include site_config::x509::key
+ include site_config::x509::ca
+ include site_config::x509::client_ca::ca
+ include site_config::x509::client_ca::key
+
+ include site_stunnel
+
+ include site_postfix::mx
+ include site_haproxy
+ include site_shorewall::mx
+ include site_shorewall::service::smtp
+ include leap_mx
+ include site_check_mk::agent::mx
+}
diff --git a/puppet/modules/site_nagios/files/configs/Debian/nagios.cfg b/puppet/modules/site_nagios/files/configs/Debian/nagios.cfg
new file mode 100644
index 00000000..62f26f2c
--- /dev/null
+++ b/puppet/modules/site_nagios/files/configs/Debian/nagios.cfg
@@ -0,0 +1,1302 @@
+##############################################################################
+#
+# NAGIOS.CFG - Sample Main Config File for Nagios
+#
+#
+##############################################################################
+
+
+# LOG FILE
+# This is the main log file where service and host events are logged
+# for historical purposes. This should be the first option specified
+# in the config file!!!
+
+log_file=/var/log/nagios3/nagios.log
+
+
+
+# OBJECT CONFIGURATION FILE(S)
+# These are the object configuration files in which you define hosts,
+# host groups, contacts, contact groups, services, etc.
+# You can split your object definitions across several config files
+# if you wish (as shown below), or keep them all in a single config file.
+#cfg_file=/etc/nagios3/commands.cfg
+
+# Check_mk configuration files
+cfg_dir=/etc/nagios3/conf.d/check_mk
+cfg_dir=/etc/nagios3/local
+
+# Puppet-managed configuration files
+cfg_file=/etc/nagios3/nagios_templates.cfg
+cfg_file=/etc/nagios3/nagios_command.cfg
+cfg_file=/etc/nagios3/nagios_contact.cfg
+cfg_file=/etc/nagios3/nagios_contactgroup.cfg
+cfg_file=/etc/nagios3/nagios_host.cfg
+cfg_file=/etc/nagios3/nagios_hostdependency.cfg
+cfg_file=/etc/nagios3/nagios_hostescalation.cfg
+cfg_file=/etc/nagios3/nagios_hostextinfo.cfg
+cfg_file=/etc/nagios3/nagios_hostgroup.cfg
+cfg_file=/etc/nagios3/nagios_hostgroupescalation.cfg
+cfg_file=/etc/nagios3/nagios_service.cfg
+cfg_file=/etc/nagios3/nagios_servicedependency.cfg
+cfg_file=/etc/nagios3/nagios_serviceescalation.cfg
+cfg_file=/etc/nagios3/nagios_serviceextinfo.cfg
+cfg_file=/etc/nagios3/nagios_servicegroup.cfg
+cfg_file=/etc/nagios3/nagios_timeperiod.cfg
+
+# Debian also defaults to using the check commands defined by the debian
+# nagios-plugins package
+cfg_dir=/etc/nagios-plugins/config
+
+
+# OBJECT CACHE FILE
+# This option determines where object definitions are cached when
+# Nagios starts/restarts. The CGIs read object definitions from
+# this cache file (rather than looking at the object config files
+# directly) in order to prevent inconsistencies that can occur
+# when the config files are modified after Nagios starts.
+
+object_cache_file=/var/cache/nagios3/objects.cache
+
+
+
+# PRE-CACHED OBJECT FILE
+# This options determines the location of the precached object file.
+# If you run Nagios with the -p command line option, it will preprocess
+# your object configuration file(s) and write the cached config to this
+# file. You can then start Nagios with the -u option to have it read
+# object definitions from this precached file, rather than the standard
+# object configuration files (see the cfg_file and cfg_dir options above).
+# Using a precached object file can speed up the time needed to (re)start
+# the Nagios process if you've got a large and/or complex configuration.
+# Read the documentation section on optimizing Nagios to find our more
+# about how this feature works.
+
+precached_object_file=/var/lib/nagios3/objects.precache
+
+
+
+# RESOURCE FILE
+# This is an optional resource file that contains $USERx$ macro
+# definitions. Multiple resource files can be specified by using
+# multiple resource_file definitions. The CGIs will not attempt to
+# read the contents of resource files, so information that is
+# considered to be sensitive (usernames, passwords, etc) can be
+# defined as macros in this file and restrictive permissions (600)
+# can be placed on this file.
+
+resource_file=/etc/nagios3/resource.cfg
+
+
+
+# STATUS FILE
+# This is where the current status of all monitored services and
+# hosts is stored. Its contents are read and processed by the CGIs.
+# The contents of the status file are deleted every time Nagios
+# restarts.
+
+status_file=/var/cache/nagios3/status.dat
+
+
+
+# STATUS FILE UPDATE INTERVAL
+# This option determines the frequency (in seconds) that
+# Nagios will periodically dump program, host, and
+# service status data.
+
+status_update_interval=10
+
+
+
+# NAGIOS USER
+# This determines the effective user that Nagios should run as.
+# You can either supply a username or a UID.
+
+nagios_user=nagios
+
+
+
+# NAGIOS GROUP
+# This determines the effective group that Nagios should run as.
+# You can either supply a group name or a GID.
+
+nagios_group=nagios
+
+
+
+# EXTERNAL COMMAND OPTION
+# This option allows you to specify whether or not Nagios should check
+# for external commands (in the command file defined below). By default
+# Nagios will *not* check for external commands, just to be on the
+# cautious side. If you want to be able to use the CGI command interface
+# you will have to enable this.
+# Values: 0 = disable commands, 1 = enable commands
+
+check_external_commands=1
+
+
+
+# EXTERNAL COMMAND CHECK INTERVAL
+# This is the interval at which Nagios should check for external commands.
+# This value works of the interval_length you specify later. If you leave
+# that at its default value of 60 (seconds), a value of 1 here will cause
+# Nagios to check for external commands every minute. If you specify a
+# number followed by an "s" (i.e. 15s), this will be interpreted to mean
+# actual seconds rather than a multiple of the interval_length variable.
+# Note: In addition to reading the external command file at regularly
+# scheduled intervals, Nagios will also check for external commands after
+# event handlers are executed.
+# NOTE: Setting this value to -1 causes Nagios to check the external
+# command file as often as possible.
+
+#command_check_interval=15s
+command_check_interval=-1
+
+
+
+# EXTERNAL COMMAND FILE
+# This is the file that Nagios checks for external command requests.
+# It is also where the command CGI will write commands that are submitted
+# by users, so it must be writeable by the user that the web server
+# is running as (usually 'nobody'). Permissions should be set at the
+# directory level instead of on the file, as the file is deleted every
+# time its contents are processed.
+# Debian Users: In case you didn't read README.Debian yet, _NOW_ is the
+# time to do it.
+
+command_file=/var/lib/nagios3/rw/nagios.cmd
+
+
+
+# EXTERNAL COMMAND BUFFER SLOTS
+# This settings is used to tweak the number of items or "slots" that
+# the Nagios daemon should allocate to the buffer that holds incoming
+# external commands before they are processed. As external commands
+# are processed by the daemon, they are removed from the buffer.
+
+external_command_buffer_slots=4096
+
+
+
+# LOCK FILE
+# This is the lockfile that Nagios will use to store its PID number
+# in when it is running in daemon mode.
+
+lock_file=/var/run/nagios3/nagios3.pid
+
+
+
+# TEMP FILE
+# This is a temporary file that is used as scratch space when Nagios
+# updates the status log, cleans the comment file, etc. This file
+# is created, used, and deleted throughout the time that Nagios is
+# running.
+
+temp_file=/var/cache/nagios3/nagios.tmp
+
+
+
+# TEMP PATH
+# This is path where Nagios can create temp files for service and
+# host check results, etc.
+
+temp_path=/tmp
+
+
+
+# EVENT BROKER OPTIONS
+# Controls what (if any) data gets sent to the event broker.
+# Values: 0 = Broker nothing
+# -1 = Broker everything
+# <other> = See documentation
+
+event_broker_options=-1
+
+
+
+# EVENT BROKER MODULE(S)
+# This directive is used to specify an event broker module that should
+# by loaded by Nagios at startup. Use multiple directives if you want
+# to load more than one module. Arguments that should be passed to
+# the module at startup are seperated from the module path by a space.
+#
+#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+# WARNING !!! WARNING !!! WARNING !!! WARNING !!! WARNING !!! WARNING
+#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+#
+# Do NOT overwrite modules while they are being used by Nagios or Nagios
+# will crash in a fiery display of SEGFAULT glory. This is a bug/limitation
+# either in dlopen(), the kernel, and/or the filesystem. And maybe Nagios...
+#
+# The correct/safe way of updating a module is by using one of these methods:
+# 1. Shutdown Nagios, replace the module file, restart Nagios
+# 2. Delete the original module file, move the new module file into place, restart Nagios
+#
+# Example:
+#
+# broker_module=<modulepath> [moduleargs]
+
+#broker_module=/somewhere/module1.o
+#broker_module=/somewhere/module2.o arg1 arg2=3 debug=0
+
+
+
+# LOG ROTATION METHOD
+# This is the log rotation method that Nagios should use to rotate
+# the main log file. Values are as follows..
+# n = None - don't rotate the log
+# h = Hourly rotation (top of the hour)
+# d = Daily rotation (midnight every day)
+# w = Weekly rotation (midnight on Saturday evening)
+# m = Monthly rotation (midnight last day of month)
+
+log_rotation_method=n
+
+
+
+# LOG ARCHIVE PATH
+# This is the directory where archived (rotated) log files should be
+# placed (assuming you've chosen to do log rotation).
+
+log_archive_path=/var/log/nagios3/archives
+
+
+
+# LOGGING OPTIONS
+# If you want messages logged to the syslog facility, as well as the
+# Nagios log file set this option to 1. If not, set it to 0.
+
+use_syslog=0
+
+
+
+# NOTIFICATION LOGGING OPTION
+# If you don't want notifications to be logged, set this value to 0.
+# If notifications should be logged, set the value to 1.
+
+log_notifications=1
+
+
+
+# SERVICE RETRY LOGGING OPTION
+# If you don't want service check retries to be logged, set this value
+# to 0. If retries should be logged, set the value to 1.
+
+log_service_retries=1
+
+
+
+# HOST RETRY LOGGING OPTION
+# If you don't want host check retries to be logged, set this value to
+# 0. If retries should be logged, set the value to 1.
+
+log_host_retries=1
+
+
+
+# EVENT HANDLER LOGGING OPTION
+# If you don't want host and service event handlers to be logged, set
+# this value to 0. If event handlers should be logged, set the value
+# to 1.
+
+log_event_handlers=1
+
+
+
+# INITIAL STATES LOGGING OPTION
+# If you want Nagios to log all initial host and service states to
+# the main log file (the first time the service or host is checked)
+# you can enable this option by setting this value to 1. If you
+# are not using an external application that does long term state
+# statistics reporting, you do not need to enable this option. In
+# this case, set the value to 0.
+
+log_initial_states=0
+
+
+
+# EXTERNAL COMMANDS LOGGING OPTION
+# If you don't want Nagios to log external commands, set this value
+# to 0. If external commands should be logged, set this value to 1.
+# Note: This option does not include logging of passive service
+# checks - see the option below for controlling whether or not
+# passive checks are logged.
+
+log_external_commands=1
+
+
+
+# PASSIVE CHECKS LOGGING OPTION
+# If you don't want Nagios to log passive host and service checks, set
+# this value to 0. If passive checks should be logged, set
+# this value to 1.
+
+log_passive_checks=1
+
+
+
+# GLOBAL HOST AND SERVICE EVENT HANDLERS
+# These options allow you to specify a host and service event handler
+# command that is to be run for every host or service state change.
+# The global event handler is executed immediately prior to the event
+# handler that you have optionally specified in each host or
+# service definition. The command argument is the short name of a
+# command definition that you define in your host configuration file.
+# Read the HTML docs for more information.
+
+#global_host_event_handler=somecommand
+#global_service_event_handler=somecommand
+
+
+
+# SERVICE INTER-CHECK DELAY METHOD
+# This is the method that Nagios should use when initially
+# "spreading out" service checks when it starts monitoring. The
+# default is to use smart delay calculation, which will try to
+# space all service checks out evenly to minimize CPU load.
+# Using the dumb setting will cause all checks to be scheduled
+# at the same time (with no delay between them)! This is not a
+# good thing for production, but is useful when testing the
+# parallelization functionality.
+# n = None - don't use any delay between checks
+# d = Use a "dumb" delay of 1 second between checks
+# s = Use "smart" inter-check delay calculation
+# x.xx = Use an inter-check delay of x.xx seconds
+
+service_inter_check_delay_method=s
+
+
+
+# MAXIMUM SERVICE CHECK SPREAD
+# This variable determines the timeframe (in minutes) from the
+# program start time that an initial check of all services should
+# be completed. Default is 30 minutes.
+
+max_service_check_spread=30
+
+
+
+# SERVICE CHECK INTERLEAVE FACTOR
+# This variable determines how service checks are interleaved.
+# Interleaving the service checks allows for a more even
+# distribution of service checks and reduced load on remote
+# hosts. Setting this value to 1 is equivalent to how versions
+# of Nagios previous to 0.0.5 did service checks. Set this
+# value to s (smart) for automatic calculation of the interleave
+# factor unless you have a specific reason to change it.
+# s = Use "smart" interleave factor calculation
+# x = Use an interleave factor of x, where x is a
+# number greater than or equal to 1.
+
+service_interleave_factor=s
+
+
+
+# HOST INTER-CHECK DELAY METHOD
+# This is the method that Nagios should use when initially
+# "spreading out" host checks when it starts monitoring. The
+# default is to use smart delay calculation, which will try to
+# space all host checks out evenly to minimize CPU load.
+# Using the dumb setting will cause all checks to be scheduled
+# at the same time (with no delay between them)!
+# n = None - don't use any delay between checks
+# d = Use a "dumb" delay of 1 second between checks
+# s = Use "smart" inter-check delay calculation
+# x.xx = Use an inter-check delay of x.xx seconds
+
+host_inter_check_delay_method=s
+
+
+
+# MAXIMUM HOST CHECK SPREAD
+# This variable determines the timeframe (in minutes) from the
+# program start time that an initial check of all hosts should
+# be completed. Default is 30 minutes.
+
+max_host_check_spread=30
+
+
+
+# MAXIMUM CONCURRENT SERVICE CHECKS
+# This option allows you to specify the maximum number of
+# service checks that can be run in parallel at any given time.
+# Specifying a value of 1 for this variable essentially prevents
+# any service checks from being parallelized. A value of 0
+# will not restrict the number of concurrent checks that are
+# being executed.
+
+max_concurrent_checks=0
+
+
+
+# HOST AND SERVICE CHECK REAPER FREQUENCY
+# This is the frequency (in seconds!) that Nagios will process
+# the results of host and service checks.
+
+check_result_reaper_frequency=10
+
+
+
+
+# MAX CHECK RESULT REAPER TIME
+# This is the max amount of time (in seconds) that a single
+# check result reaper event will be allowed to run before
+# returning control back to Nagios so it can perform other
+# duties.
+
+max_check_result_reaper_time=30
+
+
+
+
+# CHECK RESULT PATH
+# This is directory where Nagios stores the results of host and
+# service checks that have not yet been processed.
+#
+# Note: Make sure that only one instance of Nagios has access
+# to this directory!
+
+check_result_path=/var/lib/nagios3/spool/checkresults
+
+
+
+
+# MAX CHECK RESULT FILE AGE
+# This option determines the maximum age (in seconds) which check
+# result files are considered to be valid. Files older than this
+# threshold will be mercilessly deleted without further processing.
+
+max_check_result_file_age=3600
+
+
+
+
+# CACHED HOST CHECK HORIZON
+# This option determines the maximum amount of time (in seconds)
+# that the state of a previous host check is considered current.
+# Cached host states (from host checks that were performed more
+# recently that the timeframe specified by this value) can immensely
+# improve performance in regards to the host check logic.
+# Too high of a value for this option may result in inaccurate host
+# states being used by Nagios, while a lower value may result in a
+# performance hit for host checks. Use a value of 0 to disable host
+# check caching.
+
+cached_host_check_horizon=15
+
+
+
+# CACHED SERVICE CHECK HORIZON
+# This option determines the maximum amount of time (in seconds)
+# that the state of a previous service check is considered current.
+# Cached service states (from service checks that were performed more
+# recently that the timeframe specified by this value) can immensely
+# improve performance in regards to predictive dependency checks.
+# Use a value of 0 to disable service check caching.
+
+cached_service_check_horizon=15
+
+
+
+# ENABLE PREDICTIVE HOST DEPENDENCY CHECKS
+# This option determines whether or not Nagios will attempt to execute
+# checks of hosts when it predicts that future dependency logic test
+# may be needed. These predictive checks can help ensure that your
+# host dependency logic works well.
+# Values:
+# 0 = Disable predictive checks
+# 1 = Enable predictive checks (default)
+
+enable_predictive_host_dependency_checks=1
+
+
+
+# ENABLE PREDICTIVE SERVICE DEPENDENCY CHECKS
+# This option determines whether or not Nagios will attempt to execute
+# checks of service when it predicts that future dependency logic test
+# may be needed. These predictive checks can help ensure that your
+# service dependency logic works well.
+# Values:
+# 0 = Disable predictive checks
+# 1 = Enable predictive checks (default)
+
+enable_predictive_service_dependency_checks=1
+
+
+
+# SOFT STATE DEPENDENCIES
+# This option determines whether or not Nagios will use soft state
+# information when checking host and service dependencies. Normally
+# Nagios will only use the latest hard host or service state when
+# checking dependencies. If you want it to use the latest state (regardless
+# of whether its a soft or hard state type), enable this option.
+# Values:
+# 0 = Don't use soft state dependencies (default)
+# 1 = Use soft state dependencies
+
+soft_state_dependencies=0
+
+
+
+# TIME CHANGE ADJUSTMENT THRESHOLDS
+# These options determine when Nagios will react to detected changes
+# in system time (either forward or backwards).
+
+#time_change_threshold=900
+
+
+
+# AUTO-RESCHEDULING OPTION
+# This option determines whether or not Nagios will attempt to
+# automatically reschedule active host and service checks to
+# "smooth" them out over time. This can help balance the load on
+# the monitoring server.
+# WARNING: THIS IS AN EXPERIMENTAL FEATURE - IT CAN DEGRADE
+# PERFORMANCE, RATHER THAN INCREASE IT, IF USED IMPROPERLY
+
+auto_reschedule_checks=0
+
+
+
+# AUTO-RESCHEDULING INTERVAL
+# This option determines how often (in seconds) Nagios will
+# attempt to automatically reschedule checks. This option only
+# has an effect if the auto_reschedule_checks option is enabled.
+# Default is 30 seconds.
+# WARNING: THIS IS AN EXPERIMENTAL FEATURE - IT CAN DEGRADE
+# PERFORMANCE, RATHER THAN INCREASE IT, IF USED IMPROPERLY
+
+auto_rescheduling_interval=30
+
+
+
+# AUTO-RESCHEDULING WINDOW
+# This option determines the "window" of time (in seconds) that
+# Nagios will look at when automatically rescheduling checks.
+# Only host and service checks that occur in the next X seconds
+# (determined by this variable) will be rescheduled. This option
+# only has an effect if the auto_reschedule_checks option is
+# enabled. Default is 180 seconds (3 minutes).
+# WARNING: THIS IS AN EXPERIMENTAL FEATURE - IT CAN DEGRADE
+# PERFORMANCE, RATHER THAN INCREASE IT, IF USED IMPROPERLY
+
+auto_rescheduling_window=180
+
+
+
+# SLEEP TIME
+# This is the number of seconds to sleep between checking for system
+# events and service checks that need to be run.
+
+sleep_time=0.25
+
+
+
+# TIMEOUT VALUES
+# These options control how much time Nagios will allow various
+# types of commands to execute before killing them off. Options
+# are available for controlling maximum time allotted for
+# service checks, host checks, event handlers, notifications, the
+# ocsp command, and performance data commands. All values are in
+# seconds.
+
+service_check_timeout=60
+host_check_timeout=30
+event_handler_timeout=30
+notification_timeout=30
+ocsp_timeout=5
+perfdata_timeout=5
+
+
+
+# RETAIN STATE INFORMATION
+# This setting determines whether or not Nagios will save state
+# information for services and hosts before it shuts down. Upon
+# startup Nagios will reload all saved service and host state
+# information before starting to monitor. This is useful for
+# maintaining long-term data on state statistics, etc, but will
+# slow Nagios down a bit when it (re)starts. Since its only
+# a one-time penalty, I think its well worth the additional
+# startup delay.
+
+retain_state_information=1
+
+
+
+# STATE RETENTION FILE
+# This is the file that Nagios should use to store host and
+# service state information before it shuts down. The state
+# information in this file is also read immediately prior to
+# starting to monitor the network when Nagios is restarted.
+# This file is used only if the preserve_state_information
+# variable is set to 1.
+
+state_retention_file=/var/lib/nagios3/retention.dat
+
+
+
+# RETENTION DATA UPDATE INTERVAL
+# This setting determines how often (in minutes) that Nagios
+# will automatically save retention data during normal operation.
+# If you set this value to 0, Nagios will not save retention
+# data at regular interval, but it will still save retention
+# data before shutting down or restarting. If you have disabled
+# state retention, this option has no effect.
+
+retention_update_interval=60
+
+
+
+# USE RETAINED PROGRAM STATE
+# This setting determines whether or not Nagios will set
+# program status variables based on the values saved in the
+# retention file. If you want to use retained program status
+# information, set this value to 1. If not, set this value
+# to 0.
+
+use_retained_program_state=1
+
+
+
+# USE RETAINED SCHEDULING INFO
+# This setting determines whether or not Nagios will retain
+# the scheduling info (next check time) for hosts and services
+# based on the values saved in the retention file. If you
+# If you want to use retained scheduling info, set this
+# value to 1. If not, set this value to 0.
+
+use_retained_scheduling_info=1
+
+
+
+# RETAINED ATTRIBUTE MASKS (ADVANCED FEATURE)
+# The following variables are used to specify specific host and
+# service attributes that should *not* be retained by Nagios during
+# program restarts.
+#
+# The values of the masks are bitwise ANDs of values specified
+# by the "MODATTR_" definitions found in include/common.h.
+# For example, if you do not want the current enabled/disabled state
+# of flap detection and event handlers for hosts to be retained, you
+# would use a value of 24 for the host attribute mask...
+# MODATTR_EVENT_HANDLER_ENABLED (8) + MODATTR_FLAP_DETECTION_ENABLED (16) = 24
+
+# This mask determines what host attributes are not retained
+retained_host_attribute_mask=0
+
+# This mask determines what service attributes are not retained
+retained_service_attribute_mask=0
+
+# These two masks determine what process attributes are not retained.
+# There are two masks, because some process attributes have host and service
+# options. For example, you can disable active host checks, but leave active
+# service checks enabled.
+retained_process_host_attribute_mask=0
+retained_process_service_attribute_mask=0
+
+# These two masks determine what contact attributes are not retained.
+# There are two masks, because some contact attributes have host and
+# service options. For example, you can disable host notifications for
+# a contact, but leave service notifications enabled for them.
+retained_contact_host_attribute_mask=0
+retained_contact_service_attribute_mask=0
+
+
+
+# INTERVAL LENGTH
+# This is the seconds per unit interval as used in the
+# host/contact/service configuration files. Setting this to 60 means
+# that each interval is one minute long (60 seconds). Other settings
+# have not been tested much, so your mileage is likely to vary...
+
+interval_length=60
+
+
+
+# AGGRESSIVE HOST CHECKING OPTION
+# If you don't want to turn on aggressive host checking features, set
+# this value to 0 (the default). Otherwise set this value to 1 to
+# enable the aggressive check option. Read the docs for more info
+# on what aggressive host check is or check out the source code in
+# base/checks.c
+
+use_aggressive_host_checking=0
+
+
+
+# SERVICE CHECK EXECUTION OPTION
+# This determines whether or not Nagios will actively execute
+# service checks when it initially starts. If this option is
+# disabled, checks are not actively made, but Nagios can still
+# receive and process passive check results that come in. Unless
+# you're implementing redundant hosts or have a special need for
+# disabling the execution of service checks, leave this enabled!
+# Values: 1 = enable checks, 0 = disable checks
+
+execute_service_checks=1
+
+
+
+# PASSIVE SERVICE CHECK ACCEPTANCE OPTION
+# This determines whether or not Nagios will accept passive
+# service checks results when it initially (re)starts.
+# Values: 1 = accept passive checks, 0 = reject passive checks
+
+accept_passive_service_checks=1
+
+
+
+# HOST CHECK EXECUTION OPTION
+# This determines whether or not Nagios will actively execute
+# host checks when it initially starts. If this option is
+# disabled, checks are not actively made, but Nagios can still
+# receive and process passive check results that come in. Unless
+# you're implementing redundant hosts or have a special need for
+# disabling the execution of host checks, leave this enabled!
+# Values: 1 = enable checks, 0 = disable checks
+
+execute_host_checks=1
+
+
+
+# PASSIVE HOST CHECK ACCEPTANCE OPTION
+# This determines whether or not Nagios will accept passive
+# host checks results when it initially (re)starts.
+# Values: 1 = accept passive checks, 0 = reject passive checks
+
+accept_passive_host_checks=1
+
+
+
+# NOTIFICATIONS OPTION
+# This determines whether or not Nagios will sent out any host or
+# service notifications when it is initially (re)started.
+# Values: 1 = enable notifications, 0 = disable notifications
+
+enable_notifications=1
+
+
+
+# EVENT HANDLER USE OPTION
+# This determines whether or not Nagios will run any host or
+# service event handlers when it is initially (re)started. Unless
+# you're implementing redundant hosts, leave this option enabled.
+# Values: 1 = enable event handlers, 0 = disable event handlers
+
+enable_event_handlers=1
+
+
+
+# PROCESS PERFORMANCE DATA OPTION
+# This determines whether or not Nagios will process performance
+# data returned from service and host checks. If this option is
+# enabled, host performance data will be processed using the
+# host_perfdata_command (defined below) and service performance
+# data will be processed using the service_perfdata_command (also
+# defined below). Read the HTML docs for more information on
+# performance data.
+# Values: 1 = process performance data, 0 = do not process performance data
+
+process_performance_data=0
+
+
+
+# HOST AND SERVICE PERFORMANCE DATA PROCESSING COMMANDS
+# These commands are run after every host and service check is
+# performed. These commands are executed only if the
+# enable_performance_data option (above) is set to 1. The command
+# argument is the short name of a command definition that you
+# define in your host configuration file. Read the HTML docs for
+# more information on performance data.
+
+#host_perfdata_command=process-host-perfdata
+#service_perfdata_command=process-service-perfdata
+
+
+
+# HOST AND SERVICE PERFORMANCE DATA FILES
+# These files are used to store host and service performance data.
+# Performance data is only written to these files if the
+# enable_performance_data option (above) is set to 1.
+
+#host_perfdata_file=/tmp/host-perfdata
+#service_perfdata_file=/tmp/service-perfdata
+
+
+
+# HOST AND SERVICE PERFORMANCE DATA FILE TEMPLATES
+# These options determine what data is written (and how) to the
+# performance data files. The templates may contain macros, special
+# characters (\t for tab, \r for carriage return, \n for newline)
+# and plain text. A newline is automatically added after each write
+# to the performance data file. Some examples of what you can do are
+# shown below.
+
+#host_perfdata_file_template=[HOSTPERFDATA]\t$TIMET$\t$HOSTNAME$\t$HOSTEXECUTIONTIME$\t$HOSTOUTPUT$\t$HOSTPERFDATA$
+#service_perfdata_file_template=[SERVICEPERFDATA]\t$TIMET$\t$HOSTNAME$\t$SERVICEDESC$\t$SERVICEEXECUTIONTIME$\t$SERVICELATENCY$\t$SERVICEOUTPUT$\t$SERVICEPERFDATA$
+
+
+
+# HOST AND SERVICE PERFORMANCE DATA FILE MODES
+# This option determines whether or not the host and service
+# performance data files are opened in write ("w") or append ("a")
+# mode. If you want to use named pipes, you should use the special
+# pipe ("p") mode which avoid blocking at startup, otherwise you will
+# likely want the defult append ("a") mode.
+
+#host_perfdata_file_mode=a
+#service_perfdata_file_mode=a
+
+
+
+# HOST AND SERVICE PERFORMANCE DATA FILE PROCESSING INTERVAL
+# These options determine how often (in seconds) the host and service
+# performance data files are processed using the commands defined
+# below. A value of 0 indicates the files should not be periodically
+# processed.
+
+#host_perfdata_file_processing_interval=0
+#service_perfdata_file_processing_interval=0
+
+
+
+# HOST AND SERVICE PERFORMANCE DATA FILE PROCESSING COMMANDS
+# These commands are used to periodically process the host and
+# service performance data files. The interval at which the
+# processing occurs is determined by the options above.
+
+#host_perfdata_file_processing_command=process-host-perfdata-file
+#service_perfdata_file_processing_command=process-service-perfdata-file
+
+
+
+# OBSESS OVER SERVICE CHECKS OPTION
+# This determines whether or not Nagios will obsess over service
+# checks and run the ocsp_command defined below. Unless you're
+# planning on implementing distributed monitoring, do not enable
+# this option. Read the HTML docs for more information on
+# implementing distributed monitoring.
+# Values: 1 = obsess over services, 0 = do not obsess (default)
+
+obsess_over_services=0
+
+
+
+# OBSESSIVE COMPULSIVE SERVICE PROCESSOR COMMAND
+# This is the command that is run for every service check that is
+# processed by Nagios. This command is executed only if the
+# obsess_over_services option (above) is set to 1. The command
+# argument is the short name of a command definition that you
+# define in your host configuration file. Read the HTML docs for
+# more information on implementing distributed monitoring.
+
+#ocsp_command=somecommand
+
+
+
+# OBSESS OVER HOST CHECKS OPTION
+# This determines whether or not Nagios will obsess over host
+# checks and run the ochp_command defined below. Unless you're
+# planning on implementing distributed monitoring, do not enable
+# this option. Read the HTML docs for more information on
+# implementing distributed monitoring.
+# Values: 1 = obsess over hosts, 0 = do not obsess (default)
+
+obsess_over_hosts=0
+
+
+
+# OBSESSIVE COMPULSIVE HOST PROCESSOR COMMAND
+# This is the command that is run for every host check that is
+# processed by Nagios. This command is executed only if the
+# obsess_over_hosts option (above) is set to 1. The command
+# argument is the short name of a command definition that you
+# define in your host configuration file. Read the HTML docs for
+# more information on implementing distributed monitoring.
+
+#ochp_command=somecommand
+
+
+
+# TRANSLATE PASSIVE HOST CHECKS OPTION
+# This determines whether or not Nagios will translate
+# DOWN/UNREACHABLE passive host check results into their proper
+# state for this instance of Nagios. This option is useful
+# if you have distributed or failover monitoring setup. In
+# these cases your other Nagios servers probably have a different
+# "view" of the network, with regards to the parent/child relationship
+# of hosts. If a distributed monitoring server thinks a host
+# is DOWN, it may actually be UNREACHABLE from the point of
+# this Nagios instance. Enabling this option will tell Nagios
+# to translate any DOWN or UNREACHABLE host states it receives
+# passively into the correct state from the view of this server.
+# Values: 1 = perform translation, 0 = do not translate (default)
+
+translate_passive_host_checks=0
+
+
+
+# PASSIVE HOST CHECKS ARE SOFT OPTION
+# This determines whether or not Nagios will treat passive host
+# checks as being HARD or SOFT. By default, a passive host check
+# result will put a host into a HARD state type. This can be changed
+# by enabling this option.
+# Values: 0 = passive checks are HARD, 1 = passive checks are SOFT
+
+passive_host_checks_are_soft=0
+
+
+
+# ORPHANED HOST/SERVICE CHECK OPTIONS
+# These options determine whether or not Nagios will periodically
+# check for orphaned host service checks. Since service checks are
+# not rescheduled until the results of their previous execution
+# instance are processed, there exists a possibility that some
+# checks may never get rescheduled. A similar situation exists for
+# host checks, although the exact scheduling details differ a bit
+# from service checks. Orphaned checks seem to be a rare
+# problem and should not happen under normal circumstances.
+# If you have problems with service checks never getting
+# rescheduled, make sure you have orphaned service checks enabled.
+# Values: 1 = enable checks, 0 = disable checks
+
+check_for_orphaned_services=1
+check_for_orphaned_hosts=1
+
+
+
+# SERVICE FRESHNESS CHECK OPTION
+# This option determines whether or not Nagios will periodically
+# check the "freshness" of service results. Enabling this option
+# is useful for ensuring passive checks are received in a timely
+# manner.
+# Values: 1 = enabled freshness checking, 0 = disable freshness checking
+
+check_service_freshness=1
+
+
+
+# SERVICE FRESHNESS CHECK INTERVAL
+# This setting determines how often (in seconds) Nagios will
+# check the "freshness" of service check results. If you have
+# disabled service freshness checking, this option has no effect.
+
+service_freshness_check_interval=60
+
+
+
+# HOST FRESHNESS CHECK OPTION
+# This option determines whether or not Nagios will periodically
+# check the "freshness" of host results. Enabling this option
+# is useful for ensuring passive checks are received in a timely
+# manner.
+# Values: 1 = enabled freshness checking, 0 = disable freshness checking
+
+check_host_freshness=0
+
+
+
+# HOST FRESHNESS CHECK INTERVAL
+# This setting determines how often (in seconds) Nagios will
+# check the "freshness" of host check results. If you have
+# disabled host freshness checking, this option has no effect.
+
+host_freshness_check_interval=60
+
+
+
+
+# ADDITIONAL FRESHNESS THRESHOLD LATENCY
+# This setting determines the number of seconds that Nagios
+# will add to any host and service freshness thresholds that
+# it calculates (those not explicitly specified by the user).
+
+additional_freshness_latency=15
+
+
+
+
+# FLAP DETECTION OPTION
+# This option determines whether or not Nagios will try
+# and detect hosts and services that are "flapping".
+# Flapping occurs when a host or service changes between
+# states too frequently. When Nagios detects that a
+# host or service is flapping, it will temporarily suppress
+# notifications for that host/service until it stops
+# flapping. Flap detection is very experimental, so read
+# the HTML documentation before enabling this feature!
+# Values: 1 = enable flap detection
+# 0 = disable flap detection (default)
+
+enable_flap_detection=1
+
+
+
+# FLAP DETECTION THRESHOLDS FOR HOSTS AND SERVICES
+# Read the HTML documentation on flap detection for
+# an explanation of what this option does. This option
+# has no effect if flap detection is disabled.
+
+low_service_flap_threshold=5.0
+high_service_flap_threshold=20.0
+low_host_flap_threshold=5.0
+high_host_flap_threshold=20.0
+
+
+
+# DATE FORMAT OPTION
+# This option determines how short dates are displayed. Valid options
+# include:
+# us (MM-DD-YYYY HH:MM:SS)
+# euro (DD-MM-YYYY HH:MM:SS)
+# iso8601 (YYYY-MM-DD HH:MM:SS)
+# strict-iso8601 (YYYY-MM-DDTHH:MM:SS)
+#
+
+date_format=iso8601
+
+
+
+
+# TIMEZONE OFFSET
+# This option is used to override the default timezone that this
+# instance of Nagios runs in. If not specified, Nagios will use
+# the system configured timezone.
+#
+# NOTE: In order to display the correct timezone in the CGIs, you
+# will also need to alter the Apache directives for the CGI path
+# to include your timezone. Example:
+#
+# <Directory "/usr/local/nagios/sbin/">
+# SetEnv TZ "Australia/Brisbane"
+# ...
+# </Directory>
+
+#use_timezone=US/Mountain
+#use_timezone=Australia/Brisbane
+
+
+
+
+# P1.PL FILE LOCATION
+# This value determines where the p1.pl perl script (used by the
+# embedded Perl interpreter) is located. If you didn't compile
+# Nagios with embedded Perl support, this option has no effect.
+
+p1_file=/usr/lib/nagios3/p1.pl
+
+
+
+# EMBEDDED PERL INTERPRETER OPTION
+# This option determines whether or not the embedded Perl interpreter
+# will be enabled during runtime. This option has no effect if Nagios
+# has not been compiled with support for embedded Perl.
+# Values: 0 = disable interpreter, 1 = enable interpreter
+
+enable_embedded_perl=1
+
+
+
+# EMBEDDED PERL USAGE OPTION
+# This option determines whether or not Nagios will process Perl plugins
+# and scripts with the embedded Perl interpreter if the plugins/scripts
+# do not explicitly indicate whether or not it is okay to do so. Read
+# the HTML documentation on the embedded Perl interpreter for more
+# information on how this option works.
+
+use_embedded_perl_implicitly=1
+
+
+
+# ILLEGAL OBJECT NAME CHARACTERS
+# This option allows you to specify illegal characters that cannot
+# be used in host names, service descriptions, or names of other
+# object types.
+
+illegal_object_name_chars=`~!$%^&*|'"<>?,()=
+
+
+
+# ILLEGAL MACRO OUTPUT CHARACTERS
+# This option allows you to specify illegal characters that are
+# stripped from macros before being used in notifications, event
+# handlers, etc. This DOES NOT affect macros used in service or
+# host check commands.
+# The following macros are stripped of the characters you specify:
+# $HOSTOUTPUT$
+# $HOSTPERFDATA$
+# $HOSTACKAUTHOR$
+# $HOSTACKCOMMENT$
+# $SERVICEOUTPUT$
+# $SERVICEPERFDATA$
+# $SERVICEACKAUTHOR$
+# $SERVICEACKCOMMENT$
+
+illegal_macro_output_chars=`~$&|'"<>
+
+
+
+# REGULAR EXPRESSION MATCHING
+# This option controls whether or not regular expression matching
+# takes place in the object config files. Regular expression
+# matching is used to match host, hostgroup, service, and service
+# group names/descriptions in some fields of various object types.
+# Values: 1 = enable regexp matching, 0 = disable regexp matching
+
+use_regexp_matching=0
+
+
+
+# "TRUE" REGULAR EXPRESSION MATCHING
+# This option controls whether or not "true" regular expression
+# matching takes place in the object config files. This option
+# only has an effect if regular expression matching is enabled
+# (see above). If this option is DISABLED, regular expression
+# matching only occurs if a string contains wildcard characters
+# (* and ?). If the option is ENABLED, regexp matching occurs
+# all the time (which can be annoying).
+# Values: 1 = enable true matching, 0 = disable true matching
+
+use_true_regexp_matching=0
+
+
+
+# ADMINISTRATOR EMAIL/PAGER ADDRESSES
+# The email and pager address of a global administrator (likely you).
+# Nagios never uses these values itself, but you can access them by
+# using the $ADMINEMAIL$ and $ADMINPAGER$ macros in your notification
+# commands.
+
+admin_email=root@localhost
+admin_pager=pageroot@localhost
+
+
+
+# DAEMON CORE DUMP OPTION
+# This option determines whether or not Nagios is allowed to create
+# a core dump when it runs as a daemon. Note that it is generally
+# considered bad form to allow this, but it may be useful for
+# debugging purposes. Enabling this option doesn't guarantee that
+# a core file will be produced, but that's just life...
+# Values: 1 - Allow core dumps
+# 0 - Do not allow core dumps (default)
+
+daemon_dumps_core=0
+
+
+
+# LARGE INSTALLATION TWEAKS OPTION
+# This option determines whether or not Nagios will take some shortcuts
+# which can save on memory and CPU usage in large Nagios installations.
+# Read the documentation for more information on the benefits/tradeoffs
+# of enabling this option.
+# Values: 1 - Enabled tweaks
+# 0 - Disable tweaks (default)
+
+use_large_installation_tweaks=0
+
+
+
+# ENABLE ENVIRONMENT MACROS
+# This option determines whether or not Nagios will make all standard
+# macros available as environment variables when host/service checks
+# and system commands (event handlers, notifications, etc.) are
+# executed. Enabling this option can cause performance issues in
+# large installations, as it will consume a bit more memory and (more
+# importantly) consume more CPU.
+# Values: 1 - Enable environment variable macros (default)
+# 0 - Disable environment variable macros
+
+enable_environment_macros=1
+
+
+
+# CHILD PROCESS MEMORY OPTION
+# This option determines whether or not Nagios will free memory in
+# child processes (processed used to execute system commands and host/
+# service checks). If you specify a value here, it will override
+# program defaults.
+# Value: 1 - Free memory in child processes
+# 0 - Do not free memory in child processes
+
+#free_child_process_memory=1
+
+
+
+# CHILD PROCESS FORKING BEHAVIOR
+# This option determines how Nagios will fork child processes
+# (used to execute system commands and host/service checks). Normally
+# child processes are fork()ed twice, which provides a very high level
+# of isolation from problems. Fork()ing once is probably enough and will
+# save a great deal on CPU usage (in large installs), so you might
+# want to consider using this. If you specify a value here, it will
+# program defaults.
+# Value: 1 - Child processes fork() twice
+# 0 - Child processes fork() just once
+
+#child_processes_fork_twice=1
+
+
+
+# DEBUG LEVEL
+# This option determines how much (if any) debugging information will
+# be written to the debug file. OR values together to log multiple
+# types of information.
+# Values:
+# -1 = Everything
+# 0 = Nothing
+# 1 = Functions
+# 2 = Configuration
+# 4 = Process information
+# 8 = Scheduled events
+# 16 = Host/service checks
+# 32 = Notifications
+# 64 = Event broker
+# 128 = External commands
+# 256 = Commands
+# 512 = Scheduled downtime
+# 1024 = Comments
+# 2048 = Macros
+
+debug_level=0
+
+
+
+# DEBUG VERBOSITY
+# This option determines how verbose the debug log out will be.
+# Values: 0 = Brief output
+# 1 = More detailed
+# 2 = Very detailed
+
+debug_verbosity=1
+
+
+
+# DEBUG FILE
+# This option determines where Nagios should write debugging information.
+
+debug_file=/var/lib/nagios3/nagios.debug
+
+
+
+# MAX DEBUG FILE SIZE
+# This option determines the maximum size (in bytes) of the debug file. If
+# the file grows larger than this size, it will be renamed with a .old
+# extension. If a file already exists with a .old extension it will
+# automatically be deleted. This helps ensure your disk space usage doesn't
+# get out of control when debugging Nagios.
+
+max_debug_file_size=1000000
+
+process_performance_data=1
+service_perfdata_file=/var/lib/nagios3/service-perfdata
+service_perfdata_file_template=DATATYPE::SERVICEPERFDATA\tTIMET::$TIMET$\tHOSTNAME::$HOSTNAME$\tSERVICEDESC::$SERVICEDESC$\tSERVICEPERFDATA::$SERVICEPERFDATA$\tSERVICECHECKCOMMAND::$SERVICECHECKCOMMAND$\tHOSTSTATE::$HOSTSTATE$\tHOSTSTATETYPE::$HOSTSTATETYPE$\tSERVICESTATE::$SERVICESTATE$\tSERVICESTATETYPE::$SERVICESTATETYPE$
+service_perfdata_file_mode=a
+service_perfdata_file_processing_interval=15
+service_perfdata_file_processing_command=process-service-perfdata-file-pnp4nagios-bulk-npcd
+host_perfdata_file=/var/lib/nagios3/host-perfdata
+host_perfdata_file_template=DATATYPE::HOSTPERFDATA\tTIMET::$TIMET$\tHOSTNAME::$HOSTNAME$\tHOSTPERFDATA::$HOSTPERFDATA$\tHOSTCHECKCOMMAND::$HOSTCHECKCOMMAND$\tHOSTSTATE::$HOSTSTATE$\tHOSTSTATETYPE::$HOSTSTATETYPE$
+host_perfdata_file_mode=a
+host_perfdata_file_processing_interval=15
+host_perfdata_file_processing_command=process-host-perfdata-file-pnp4nagios-bulk-npcd
+
diff --git a/puppet/modules/site_nagios/files/plugins/check_last_regex_in_log b/puppet/modules/site_nagios/files/plugins/check_last_regex_in_log
new file mode 100755
index 00000000..47569388
--- /dev/null
+++ b/puppet/modules/site_nagios/files/plugins/check_last_regex_in_log
@@ -0,0 +1,85 @@
+#!/bin/sh
+#
+# depends on nagios-plugins-common for /usr/lib/nagios/plugins/utils.sh
+# this package is installed using leap_platform by the Site_check_mk::Agent::Mrpe
+# class
+
+set -e
+
+usage()
+{
+cat << EOF
+usage: $0 -w <sec> -c <sec> -r <regexp> -f <filename>
+
+OPTIONS:
+ -h Show this message
+ -r <regex> regex to grep for
+ -f <file> logfile to search in
+ -w <sec> warning state after X seconds
+ -c <sec> critical state after x seconds
+
+example: $0 -f /var/log/syslog -r 'tapicero' -w 300 -c 600
+EOF
+}
+
+
+. /usr/lib/nagios/plugins/utils.sh
+
+
+warn=0
+crit=0
+log=''
+regex=''
+
+set -- $(getopt hr:f:w:c: "$@")
+while [ $# -gt 0 ]
+do
+ case "$1" in
+ (-h) usage; exit 0 ;;
+ (-f) log="$2"; shift;;
+ (-r) regex="$2"; shift;;
+ (-w) warn="$2"; shift;;
+ (-c) crit="$2"; shift;;
+ (--) shift; break;;
+ (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
+ (*) break;;
+ esac
+ shift
+done
+
+[ $warn -eq 0 -o $crit -eq 0 -o -z "$regex" -o -z "$log" ] && ( usage; exit $STATE_UNKNOWN)
+[ -f "$log" ] || (echo "$log doesn't exist"; exit $STATE_UNKNOWN)
+
+lastmsg=$(tac $log | grep -i $regex | head -1 | sed 's/ / /g' | cut -d' ' -f 1-3)
+
+if [ -z "$lastmsg" ]
+then
+ summary="\"$regex\" in $log was not found"
+ state=$STATE_CRITICAL
+ state_text='CRITICAL'
+ diff_sec=0
+else
+ lastmsg_sec=$(date '+%s' -d "$lastmsg")
+ now_sec=$(date '+%s')
+
+ diff_sec=$(($now_sec - $lastmsg_sec))
+
+ if [ $diff_sec -lt $warn ]; then
+ state=$STATE_OK
+ state_text='OK'
+ elif [ $diff_sec -lt $crit ]; then
+ state=$STATE_WARNING
+ state_text='WARNING'
+ else
+ state=$STATE_CRITICAL
+ state_text='CRITICAL'
+ fi
+
+ summary="Last occurrence of \"$regex\" in $log was $diff_sec sec ago"
+fi
+
+# check_mk_agent output
+# echo "$state Tapicero_Heatbeat sec=$diff_sec;$warn;$crit;0; $state_text - $summary"
+
+echo "${state_text}: $summary | seconds=${diff_sec};$warn;$crit;0;"
+exit $state
diff --git a/puppet/modules/site_nagios/manifests/add_host_services.pp b/puppet/modules/site_nagios/manifests/add_host_services.pp
new file mode 100644
index 00000000..bd968e6f
--- /dev/null
+++ b/puppet/modules/site_nagios/manifests/add_host_services.pp
@@ -0,0 +1,32 @@
+define site_nagios::add_host_services (
+ $domain_full_suffix,
+ $domain_internal,
+ $domain_internal_suffix,
+ $ip_address,
+ $services,
+ $ssh_port,
+ $environment,
+ $openvpn_gateway_address='',
+ ) {
+
+ $nagios_hostname = $domain_internal
+
+ # Add Nagios service
+
+ # First, we need to turn the serice array into hash, using a "hash template"
+ # see https://github.com/ashak/puppet-resource-looping
+ $nagios_service_hashpart = {
+ 'hostname' => $nagios_hostname,
+ 'ip_address' => $ip_address,
+ 'openvpn_gw' => $openvpn_gateway_address,
+ 'environment' => $environment
+ }
+ $dynamic_parameters = {
+ 'service' => '%s'
+ }
+ $nagios_servicename = "${nagios_hostname}_%s"
+
+ $nagios_service_hash = create_resources_hash_from($nagios_servicename, $services, $nagios_service_hashpart, $dynamic_parameters)
+
+ create_resources ( site_nagios::add_service, $nagios_service_hash )
+}
diff --git a/puppet/modules/site_nagios/manifests/add_service.pp b/puppet/modules/site_nagios/manifests/add_service.pp
new file mode 100644
index 00000000..72cd038a
--- /dev/null
+++ b/puppet/modules/site_nagios/manifests/add_service.pp
@@ -0,0 +1,32 @@
+define site_nagios::add_service (
+ $hostname, $ip_address, $service, $environment, $openvpn_gw = '') {
+
+ $ssh = hiera_hash('ssh')
+ $ssh_port = $ssh['port']
+
+ case $service {
+ 'webapp': {
+ nagios_service {
+ "${name}_ssh":
+ use => 'generic-service',
+ check_command => "check_ssh_port!${ssh_port}",
+ service_description => 'SSH',
+ host_name => $hostname,
+ contact_groups => $environment;
+ "${name}_cert":
+ use => 'generic-service',
+ check_command => 'check_https_cert',
+ service_description => 'Website Certificate',
+ host_name => $hostname,
+ contact_groups => $environment;
+ "${name}_website":
+ use => 'generic-service',
+ check_command => 'check_https',
+ service_description => 'Website',
+ host_name => $hostname,
+ contact_groups => $environment;
+ }
+ }
+ default: {}
+ }
+}
diff --git a/puppet/modules/site_nagios/manifests/init.pp b/puppet/modules/site_nagios/manifests/init.pp
new file mode 100644
index 00000000..f91bfc26
--- /dev/null
+++ b/puppet/modules/site_nagios/manifests/init.pp
@@ -0,0 +1,13 @@
+# setup nagios on monitoring node
+class site_nagios {
+ tag 'leap_service'
+
+ include site_config::default
+
+ Class['site_config::default'] -> Class['site_nagios']
+
+ include site_nagios::server
+
+ # remove leftovers on monitoring nodes
+ include site_config::remove::monitoring
+}
diff --git a/puppet/modules/site_nagios/manifests/plugins.pp b/puppet/modules/site_nagios/manifests/plugins.pp
new file mode 100644
index 00000000..90a01cfb
--- /dev/null
+++ b/puppet/modules/site_nagios/manifests/plugins.pp
@@ -0,0 +1,16 @@
+# Deploy generic plugins useful to all nodes
+# nagios::plugin won't work to deploy a plugin
+# because it complains with:
+# Could not find dependency Package[nagios-plugins] …
+# at /srv/leap/puppet/modules/nagios/manifests/plugin.pp:18
+class site_nagios::plugins {
+
+ file { [
+ '/usr/local/lib', '/usr/local/lib/nagios',
+ '/usr/local/lib/nagios/plugins' ]:
+ ensure => directory;
+ '/usr/local/lib/nagios/plugins/check_last_regex_in_log':
+ source => 'puppet:///modules/site_nagios/plugins/check_last_regex_in_log',
+ mode => '0755';
+ }
+}
diff --git a/puppet/modules/site_nagios/manifests/server.pp b/puppet/modules/site_nagios/manifests/server.pp
new file mode 100644
index 00000000..6537124d
--- /dev/null
+++ b/puppet/modules/site_nagios/manifests/server.pp
@@ -0,0 +1,97 @@
+# configures nagios on monitoring node
+# lint:ignore:inherits_across_namespaces
+class site_nagios::server inherits nagios::base {
+# lint:endignore
+
+ $nagios_hiera = hiera('nagios')
+ $nagiosadmin_pw = htpasswd_sha1($nagios_hiera['nagiosadmin_pw'])
+ $nagios_hosts = $nagios_hiera['hosts']
+ $nagios_contacts = hiera('contacts')
+ $environment = $nagios_hiera['environments']
+
+ include nagios::base
+ include nagios::defaults::commands
+ include nagios::defaults::templates
+ include nagios::defaults::timeperiods
+ include nagios::pnp4nagios
+ include nagios::pnp4nagios::popup
+
+ class { 'nagios':
+ # don't manage apache class from nagios, cause we already include
+ # it in site_apache::common
+ httpd => 'absent',
+ allow_external_cmd => true,
+ storeconfigs => false,
+ }
+
+ # Delete nagios config files provided by packages
+ # These don't get parsed by nagios.conf, but are
+ # still irritating duplicates to the real config
+ # files deployed by puppet in /etc/nagios3/
+ file { [
+ '/etc/nagios3/conf.d/contacts_nagios2.cfg',
+ '/etc/nagios3/conf.d/extinfo_nagios2.cfg',
+ '/etc/nagios3/conf.d/generic-host_nagios2.cfg',
+ '/etc/nagios3/conf.d/generic-service_nagios2.cfg',
+ '/etc/nagios3/conf.d/hostgroups_nagios2.cfg',
+ '/etc/nagios3/conf.d/localhost_nagios2.cfg',
+ '/etc/nagios3/conf.d/pnp4nagios.cfg',
+ '/etc/nagios3/conf.d/services_nagios2.cfg',
+ '/etc/nagios3/conf.d/timeperiods_nagios2.cfg' ]:
+ ensure => absent;
+ }
+
+ # deploy apache nagios3 config
+ # until https://gitlab.com/shared-puppet-modules-group/apache/issues/11
+ # is not fixed, we need to manually deploy the config file
+ file {
+ '/etc/apache2/conf-available/nagios3.conf':
+ ensure => present,
+ source => 'puppet:///modules/nagios/configs/apache2.conf',
+ require => [ Package['nagios3'], Package['apache2'] ];
+ '/etc/apache2/conf-enabled/nagios3.conf':
+ ensure => link,
+ target => '/etc/apache2/conf-available/nagios3.conf',
+ require => [ Package['nagios3'], Package['apache2'] ];
+ }
+
+ include site_apache::common
+ include site_webapp::common_vhost
+ include apache::module::headers
+
+ File['nagios_htpasswd'] {
+ source => undef,
+ content => "nagiosadmin:${nagiosadmin_pw}",
+ mode => '0640',
+ }
+
+
+ # deploy serverside plugins
+ file { '/usr/lib/nagios/plugins/check_openvpn_server.pl':
+ source => 'puppet:///modules/nagios/plugins/check_openvpn_server.pl',
+ mode => '0755',
+ owner => 'nagios',
+ group => 'nagios',
+ require => Package['nagios-plugins'];
+ }
+
+ create_resources ( site_nagios::add_host_services, $nagios_hosts )
+
+ include site_nagios::server::apache
+ include site_check_mk::server
+ include site_shorewall::monitor
+ include site_nagios::server::icli
+
+ augeas {
+ 'logrotate_nagios':
+ context => '/files/etc/logrotate.d/nagios/rule',
+ changes => [ 'set file /var/log/nagios3/nagios.log', 'set rotate 7',
+ 'set schedule daily', 'set compress compress',
+ 'set missingok missingok', 'set ifempty notifempty',
+ 'set copytruncate copytruncate' ]
+ }
+
+ create_resources ( site_nagios::server::hostgroup, $environment )
+ create_resources ( site_nagios::server::contactgroup, $environment )
+ create_resources ( site_nagios::server::add_contacts, $environment )
+}
diff --git a/puppet/modules/site_nagios/manifests/server/add_contacts.pp b/puppet/modules/site_nagios/manifests/server/add_contacts.pp
new file mode 100644
index 00000000..b5c6f0a5
--- /dev/null
+++ b/puppet/modules/site_nagios/manifests/server/add_contacts.pp
@@ -0,0 +1,18 @@
+# configure a nagios_contact
+define site_nagios::server::add_contacts ($contact_emails) {
+
+ $environment = $name
+
+ nagios_contact {
+ $environment:
+ alias => $environment,
+ service_notification_period => '24x7',
+ host_notification_period => '24x7',
+ service_notification_options => 'w,u,c,r',
+ host_notification_options => 'd,r',
+ service_notification_commands => 'notify-service-by-email',
+ host_notification_commands => 'notify-host-by-email',
+ email => join($contact_emails, ', '),
+ require => Package['nagios']
+ }
+}
diff --git a/puppet/modules/site_nagios/manifests/server/apache.pp b/puppet/modules/site_nagios/manifests/server/apache.pp
new file mode 100644
index 00000000..82962e89
--- /dev/null
+++ b/puppet/modules/site_nagios/manifests/server/apache.pp
@@ -0,0 +1,25 @@
+# set up apache for nagios
+class site_nagios::server::apache {
+
+ include x509::variables
+
+ include site_config::x509::commercial::cert
+ include site_config::x509::commercial::key
+ include site_config::x509::commercial::ca
+
+ include apache::module::authn_file
+ # "AuthUserFile"
+ include apache::module::authz_user
+ # "AuthType Basic"
+ include apache::module::auth_basic
+ # "DirectoryIndex"
+ include apache::module::dir
+ include apache::module::php5
+ include apache::module::cgi
+
+ # apache >= 2.4, debian jessie
+ if ( $::lsbdistcodename == 'jessie' ) {
+ include apache::module::authn_core
+ }
+
+}
diff --git a/puppet/modules/site_nagios/manifests/server/contactgroup.pp b/puppet/modules/site_nagios/manifests/server/contactgroup.pp
new file mode 100644
index 00000000..5e60dd06
--- /dev/null
+++ b/puppet/modules/site_nagios/manifests/server/contactgroup.pp
@@ -0,0 +1,8 @@
+# configure a contactgroup
+define site_nagios::server::contactgroup ($contact_emails) {
+
+ nagios_contactgroup { $name:
+ members => $name,
+ require => Package['nagios']
+ }
+}
diff --git a/puppet/modules/site_nagios/manifests/server/hostgroup.pp b/puppet/modules/site_nagios/manifests/server/hostgroup.pp
new file mode 100644
index 00000000..0692fced
--- /dev/null
+++ b/puppet/modules/site_nagios/manifests/server/hostgroup.pp
@@ -0,0 +1,7 @@
+# create a nagios hostsgroup
+define site_nagios::server::hostgroup ($contact_emails) {
+ nagios_hostgroup { $name:
+ ensure => present,
+ require => Package['nagios']
+ }
+}
diff --git a/puppet/modules/site_nagios/manifests/server/icli.pp b/puppet/modules/site_nagios/manifests/server/icli.pp
new file mode 100644
index 00000000..26fba725
--- /dev/null
+++ b/puppet/modules/site_nagios/manifests/server/icli.pp
@@ -0,0 +1,26 @@
+# Install icli package and configure ncli aliases
+class site_nagios::server::icli {
+ $nagios_hiera = hiera('nagios')
+ $environments = $nagios_hiera['environments']
+
+ package { 'icli':
+ ensure => installed;
+ }
+
+ file { '/root/.bashrc':
+ ensure => present;
+ }
+
+ file_line { 'icli aliases':
+ path => '/root/.bashrc',
+ line => 'source /root/.icli_aliases';
+ }
+
+ file { '/root/.icli_aliases':
+ content => template("${module_name}/icli_aliases.erb"),
+ mode => '0644',
+ owner => root,
+ group => 0,
+ require => Package['icli'];
+ }
+} \ No newline at end of file
diff --git a/puppet/modules/site_nagios/templates/icli_aliases.erb b/puppet/modules/site_nagios/templates/icli_aliases.erb
new file mode 100644
index 00000000..bcb2abb0
--- /dev/null
+++ b/puppet/modules/site_nagios/templates/icli_aliases.erb
@@ -0,0 +1,7 @@
+alias ncli='icli -c /var/cache/nagios3/objects.cache -f /var/cache/nagios3/status.dat -F /var/lib/nagios3/rw/nagios.cmd'
+alias ncli_problems='ncli -z '!o,!A''
+
+<% @environments.keys.sort.each do |env_name| %>
+alias ncli_<%= env_name %>='ncli -z '!o,!A' -g <%= env_name %>'
+alias ncli_<%= env_name %>_recheck='ncli -s Check_MK -g <%= env_name %> -a R'
+<% end -%>
diff --git a/puppet/modules/site_nickserver/manifests/init.pp b/puppet/modules/site_nickserver/manifests/init.pp
new file mode 100644
index 00000000..eb4415e7
--- /dev/null
+++ b/puppet/modules/site_nickserver/manifests/init.pp
@@ -0,0 +1,178 @@
+#
+# TODO: currently, this is dependent on some things that are set up in
+# site_webapp
+#
+# (1) HAProxy -> couchdb
+# (2) Apache
+#
+# It would be good in the future to make nickserver installable independently of
+# site_webapp.
+#
+
+class site_nickserver {
+ tag 'leap_service'
+ Class['site_config::default'] -> Class['site_nickserver']
+
+ include site_config::ruby::dev
+
+ #
+ # VARIABLES
+ #
+
+ $nickserver = hiera('nickserver')
+ $nickserver_domain = $nickserver['domain']
+ $couchdb_user = $nickserver['couchdb_nickserver_user']['username']
+ $couchdb_password = $nickserver['couchdb_nickserver_user']['password']
+
+ # the port that public connects to (should be 6425)
+ $nickserver_port = $nickserver['port']
+ # the port that nickserver is actually running on
+ $nickserver_local_port = '64250'
+
+ # couchdb is available on localhost via haproxy, which is bound to 4096.
+ $couchdb_host = 'localhost'
+ # See site_webapp/templates/haproxy_couchdb.cfg.erg
+ $couchdb_port = '4096'
+
+ $sources = hiera('sources')
+
+ # temporarily for now:
+ $domain = hiera('domain')
+ $address_domain = $domain['full_suffix']
+
+ include site_config::x509::cert
+ include site_config::x509::key
+ include site_config::x509::ca
+
+ #
+ # USER AND GROUP
+ #
+
+ group { 'nickserver':
+ ensure => present,
+ allowdupe => false;
+ }
+
+ user { 'nickserver':
+ ensure => present,
+ allowdupe => false,
+ gid => 'nickserver',
+ home => '/srv/leap/nickserver',
+ require => Group['nickserver'];
+ }
+
+ vcsrepo { '/srv/leap/nickserver':
+ ensure => present,
+ revision => $sources['nickserver']['revision'],
+ provider => $sources['nickserver']['type'],
+ source => $sources['nickserver']['source'],
+ owner => 'nickserver',
+ group => 'nickserver',
+ require => [ User['nickserver'], Group['nickserver'] ],
+ notify => Exec['nickserver_bundler_update'];
+ }
+
+ exec { 'nickserver_bundler_update':
+ cwd => '/srv/leap/nickserver',
+ command => '/bin/bash -c "/usr/bin/bundle check || /usr/bin/bundle install --path vendor/bundle"',
+ unless => '/usr/bin/bundle check',
+ user => 'nickserver',
+ timeout => 600,
+ require => [
+ Class['bundler::install'], Vcsrepo['/srv/leap/nickserver'],
+ Package['libssl-dev'], Class['site_config::ruby::dev'] ],
+
+ notify => Service['nickserver'];
+ }
+
+ #
+ # NICKSERVER CONFIG
+ #
+
+ file { '/etc/nickserver.yml':
+ content => template('site_nickserver/nickserver.yml.erb'),
+ owner => nickserver,
+ group => nickserver,
+ mode => '0600',
+ notify => Service['nickserver'];
+ }
+
+ #
+ # NICKSERVER DAEMON
+ #
+
+ file {
+ '/usr/bin/nickserver':
+ ensure => link,
+ target => '/srv/leap/nickserver/bin/nickserver',
+ require => Vcsrepo['/srv/leap/nickserver'];
+
+ '/etc/init.d/nickserver':
+ owner => root,
+ group => 0,
+ mode => '0755',
+ source => '/srv/leap/nickserver/dist/debian-init-script',
+ require => Vcsrepo['/srv/leap/nickserver'];
+ }
+
+ # register initscript at systemd on nodes newer than wheezy
+ # see https://leap.se/code/issues/7614
+ case $::operatingsystemrelease {
+ /^7.*/: { }
+ default: {
+ exec { 'register_systemd_nickserver':
+ refreshonly => true,
+ command => '/bin/systemctl enable nickserver',
+ subscribe => File['/etc/init.d/nickserver'],
+ before => Service['nickserver'];
+ }
+ }
+ }
+
+ service { 'nickserver':
+ ensure => running,
+ enable => true,
+ hasrestart => true,
+ hasstatus => true,
+ require => [
+ File['/etc/init.d/nickserver'],
+ File['/usr/bin/nickserver'],
+ Class['Site_config::X509::Key'],
+ Class['Site_config::X509::Cert'],
+ Class['Site_config::X509::Ca'] ];
+ }
+
+ #
+ # FIREWALL
+ # poke a hole in the firewall to allow nickserver requests
+ #
+
+ file { '/etc/shorewall/macro.nickserver':
+ content => "PARAM - - tcp ${nickserver_port}",
+ notify => Service['shorewall'],
+ require => Package['shorewall'];
+ }
+
+ shorewall::rule { 'net2fw-nickserver':
+ source => 'net',
+ destination => '$FW',
+ action => 'nickserver(ACCEPT)',
+ order => 200;
+ }
+
+ #
+ # APACHE REVERSE PROXY
+ # nickserver doesn't speak TLS natively, let Apache handle that.
+ #
+
+ apache::module {
+ 'proxy': ensure => present;
+ 'proxy_http': ensure => present
+ }
+
+ apache::vhost::file {
+ 'nickserver':
+ content => template('site_nickserver/nickserver-proxy.conf.erb')
+ }
+
+}
diff --git a/puppet/modules/site_nickserver/templates/nickserver-proxy.conf.erb b/puppet/modules/site_nickserver/templates/nickserver-proxy.conf.erb
new file mode 100644
index 00000000..8f59fe38
--- /dev/null
+++ b/puppet/modules/site_nickserver/templates/nickserver-proxy.conf.erb
@@ -0,0 +1,19 @@
+#
+# Apache reverse proxy configuration for the Nickserver
+#
+
+Listen 0.0.0.0:<%= @nickserver_port -%>
+
+<VirtualHost *:<%= @nickserver_port -%>>
+ ServerName <%= @nickserver_domain %>
+ ServerAlias <%= @address_domain %>
+
+ SSLCACertificatePath /etc/ssl/certs
+ SSLCertificateKeyFile <%= scope.lookupvar('x509::variables::keys') %>/<%= scope.lookupvar('site_config::params::cert_name') %>.key
+ SSLCertificateFile <%= scope.lookupvar('x509::variables::certs') %>/<%= scope.lookupvar('site_config::params::cert_name') %>.crt
+
+ Include include.d/ssl_common.inc
+
+ ProxyPass / http://localhost:<%= @nickserver_local_port %>/
+ ProxyPreserveHost On # preserve Host header in HTTP request
+</VirtualHost>
diff --git a/puppet/modules/site_nickserver/templates/nickserver.yml.erb b/puppet/modules/site_nickserver/templates/nickserver.yml.erb
new file mode 100644
index 00000000..e717cbaa
--- /dev/null
+++ b/puppet/modules/site_nickserver/templates/nickserver.yml.erb
@@ -0,0 +1,19 @@
+#
+# configuration for nickserver.
+#
+
+domain: "<%= @address_domain %>"
+
+couch_host: "<%= @couchdb_host %>"
+couch_port: <%= @couchdb_port %>
+couch_database: "identities"
+couch_user: "<%= @couchdb_user %>"
+couch_password: "<%= @couchdb_password %>"
+
+hkp_url: "https://hkps.pool.sks-keyservers.net:/pks/lookup"
+
+user: "nickserver"
+port: <%= @nickserver_local_port %>
+pid_file: "/var/run/nickserver"
+log_file: "/var/log/nickserver.log"
+
diff --git a/puppet/modules/site_obfsproxy/README b/puppet/modules/site_obfsproxy/README
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/puppet/modules/site_obfsproxy/README
diff --git a/puppet/modules/site_obfsproxy/manifests/init.pp b/puppet/modules/site_obfsproxy/manifests/init.pp
new file mode 100644
index 00000000..2ed5ec9e
--- /dev/null
+++ b/puppet/modules/site_obfsproxy/manifests/init.pp
@@ -0,0 +1,38 @@
+class site_obfsproxy {
+ tag 'leap_service'
+ Class['site_config::default'] -> Class['site_obfsproxy']
+
+ $transport = 'scramblesuit'
+
+ $obfsproxy = hiera('obfsproxy')
+ $scramblesuit = $obfsproxy['scramblesuit']
+ $scram_pass = $scramblesuit['password']
+ $scram_port = $scramblesuit['port']
+ $dest_ip = $obfsproxy['gateway_address']
+ $dest_port = '443'
+
+ if member($::services, 'openvpn') {
+ $openvpn = hiera('openvpn')
+ $bind_address = $openvpn['gateway_address']
+ }
+ elsif member($::services, 'obfsproxy') {
+ $bind_address = hiera('ip_address')
+ }
+
+ include site_config::default
+
+ class { 'obfsproxy':
+ transport => $transport,
+ bind_address => $bind_address,
+ port => $scram_port,
+ param => $scram_pass,
+ dest_ip => $dest_ip,
+ dest_port => $dest_port,
+ }
+
+ include site_shorewall::obfsproxy
+
+}
+
+
+
diff --git a/puppet/modules/site_openvpn/README b/puppet/modules/site_openvpn/README
new file mode 100644
index 00000000..cef5be23
--- /dev/null
+++ b/puppet/modules/site_openvpn/README
@@ -0,0 +1,20 @@
+Place to look when debugging problems
+========================================
+
+Log files:
+
+ openvpn: /var/log/syslog
+ shorewall: /var/log/syslog
+ shorewall startup: /var/log/shorewall-init.log
+
+Check NAT masq:
+
+ iptables -t nat --list-rules
+
+Check interfaces:
+
+ ip addr ls
+
+Scripts:
+
+ /usr/local/bin/add_gateway_ips.sh \ No newline at end of file
diff --git a/puppet/modules/site_openvpn/manifests/dh_key.pp b/puppet/modules/site_openvpn/manifests/dh_key.pp
new file mode 100644
index 00000000..13cc0f5b
--- /dev/null
+++ b/puppet/modules/site_openvpn/manifests/dh_key.pp
@@ -0,0 +1,10 @@
+class site_openvpn::dh_key {
+
+ $x509_config = hiera('x509')
+
+ file { '/etc/openvpn/keys/dh.pem':
+ content => $x509_config['dh'],
+ mode => '0644',
+ }
+
+}
diff --git a/puppet/modules/site_openvpn/manifests/init.pp b/puppet/modules/site_openvpn/manifests/init.pp
new file mode 100644
index 00000000..f1ecefb9
--- /dev/null
+++ b/puppet/modules/site_openvpn/manifests/init.pp
@@ -0,0 +1,238 @@
+#
+# An openvpn gateway can support three modes:
+#
+# (1) limited and unlimited
+# (2) unlimited only
+# (3) limited only
+#
+# The difference is that 'unlimited' gateways only allow client certs that match
+# the 'unlimited_prefix', and 'limited' gateways only allow certs that match the
+# 'limited_prefix'.
+#
+# We potentially create four openvpn config files (thus four daemons):
+#
+# (1) unlimited + tcp => tcp_config.conf
+# (2) unlimited + udp => udp_config.conf
+# (3) limited + tcp => limited_tcp_config.conf
+# (4) limited + udp => limited_udp_config.conf
+#
+
+class site_openvpn {
+ tag 'leap_service'
+
+ include site_config::x509::cert
+ include site_config::x509::key
+ include site_config::x509::ca_bundle
+
+ include site_config::default
+ Class['site_config::default'] -> Class['site_openvpn']
+
+ include ::site_obfsproxy
+
+ $openvpn = hiera('openvpn')
+ $openvpn_ports = $openvpn['ports']
+ $openvpn_config = $openvpn['configuration']
+
+ if $::ec2_instance_id {
+ $openvpn_gateway_address = $::ipaddress
+ } else {
+ $openvpn_gateway_address = $openvpn['gateway_address']
+ if $openvpn['second_gateway_address'] {
+ $openvpn_second_gateway_address = $openvpn['second_gateway_address']
+ } else {
+ $openvpn_second_gateway_address = undef
+ }
+ }
+
+ $openvpn_allow_unlimited = $openvpn['allow_unlimited']
+ $openvpn_unlimited_prefix = $openvpn['unlimited_prefix']
+ $openvpn_unlimited_tcp_network_prefix = '10.41.0'
+ $openvpn_unlimited_tcp_netmask = '255.255.248.0'
+ $openvpn_unlimited_tcp_cidr = '21'
+ $openvpn_unlimited_udp_network_prefix = '10.42.0'
+ $openvpn_unlimited_udp_netmask = '255.255.248.0'
+ $openvpn_unlimited_udp_cidr = '21'
+
+ if !$::ec2_instance_id {
+ $openvpn_allow_limited = $openvpn['allow_limited']
+ $openvpn_limited_prefix = $openvpn['limited_prefix']
+ $openvpn_rate_limit = $openvpn['rate_limit']
+ $openvpn_limited_tcp_network_prefix = '10.43.0'
+ $openvpn_limited_tcp_netmask = '255.255.248.0'
+ $openvpn_limited_tcp_cidr = '21'
+ $openvpn_limited_udp_network_prefix = '10.44.0'
+ $openvpn_limited_udp_netmask = '255.255.248.0'
+ $openvpn_limited_udp_cidr = '21'
+ }
+
+ # find out the netmask in cidr format of the primary IF
+ # thx to https://blog.kumina.nl/tag/puppet-tips-and-tricks/
+ # we can do this using an inline_template:
+ $factname_primary_netmask = "netmask_cidr_${::site_config::params::interface}"
+ $primary_netmask = inline_template('<%= scope.lookupvar(@factname_primary_netmask) %>')
+
+ # deploy dh keys
+ include site_openvpn::dh_key
+
+ if $openvpn_allow_unlimited and $openvpn_allow_limited {
+ $unlimited_gateway_address = $openvpn_gateway_address
+ $limited_gateway_address = $openvpn_second_gateway_address
+ } elsif $openvpn_allow_unlimited {
+ $unlimited_gateway_address = $openvpn_gateway_address
+ $limited_gateway_address = undef
+ } elsif $openvpn_allow_limited {
+ $unlimited_gateway_address = undef
+ $limited_gateway_address = $openvpn_gateway_address
+ }
+
+ if $openvpn_allow_unlimited {
+ site_openvpn::server_config { 'tcp_config':
+ port => '1194',
+ proto => 'tcp',
+ local => $unlimited_gateway_address,
+ tls_remote => "\"${openvpn_unlimited_prefix}\"",
+ server => "${openvpn_unlimited_tcp_network_prefix}.0 ${openvpn_unlimited_tcp_netmask}",
+ push => "\"dhcp-option DNS ${openvpn_unlimited_tcp_network_prefix}.1\"",
+ management => '127.0.0.1 1000',
+ config => $openvpn_config
+ }
+ site_openvpn::server_config { 'udp_config':
+ port => '1194',
+ proto => 'udp',
+ local => $unlimited_gateway_address,
+ tls_remote => "\"${openvpn_unlimited_prefix}\"",
+ server => "${openvpn_unlimited_udp_network_prefix}.0 ${openvpn_unlimited_udp_netmask}",
+ push => "\"dhcp-option DNS ${openvpn_unlimited_udp_network_prefix}.1\"",
+ management => '127.0.0.1 1001',
+ config => $openvpn_config
+ }
+ } else {
+ tidy { '/etc/openvpn/tcp_config.conf': }
+ tidy { '/etc/openvpn/udp_config.conf': }
+ }
+
+ if $openvpn_allow_limited {
+ site_openvpn::server_config { 'limited_tcp_config':
+ port => '1194',
+ proto => 'tcp',
+ local => $limited_gateway_address,
+ tls_remote => "\"${openvpn_limited_prefix}\"",
+ server => "${openvpn_limited_tcp_network_prefix}.0 ${openvpn_limited_tcp_netmask}",
+ push => "\"dhcp-option DNS ${openvpn_limited_tcp_network_prefix}.1\"",
+ management => '127.0.0.1 1002',
+ config => $openvpn_config
+ }
+ site_openvpn::server_config { 'limited_udp_config':
+ port => '1194',
+ proto => 'udp',
+ local => $limited_gateway_address,
+ tls_remote => "\"${openvpn_limited_prefix}\"",
+ server => "${openvpn_limited_udp_network_prefix}.0 ${openvpn_limited_udp_netmask}",
+ push => "\"dhcp-option DNS ${openvpn_limited_udp_network_prefix}.1\"",
+ management => '127.0.0.1 1003',
+ config => $openvpn_config
+ }
+ } else {
+ tidy { '/etc/openvpn/limited_tcp_config.conf': }
+ tidy { '/etc/openvpn/limited_udp_config.conf': }
+ }
+
+ file {
+ '/usr/local/bin/add_gateway_ips.sh':
+ content => template('site_openvpn/add_gateway_ips.sh.erb'),
+ mode => '0755';
+ }
+
+ exec { '/usr/local/bin/add_gateway_ips.sh':
+ subscribe => File['/usr/local/bin/add_gateway_ips.sh'],
+ }
+
+ exec { 'restart_openvpn':
+ command => '/etc/init.d/openvpn restart',
+ refreshonly => true,
+ subscribe => [
+ File['/etc/openvpn'],
+ Class['Site_config::X509::Key'],
+ Class['Site_config::X509::Cert'],
+ Class['Site_config::X509::Ca_bundle'] ],
+ require => [
+ Package['openvpn'],
+ File['/etc/openvpn'],
+ Class['Site_config::X509::Key'],
+ Class['Site_config::X509::Cert'],
+ Class['Site_config::X509::Ca_bundle'] ];
+ }
+
+ cron { 'add_gateway_ips.sh':
+ command => '/usr/local/bin/add_gateway_ips.sh',
+ user => 'root',
+ special => 'reboot',
+ }
+
+ # setup the resolver to listen on the vpn IP
+ include site_openvpn::resolver
+
+ include site_shorewall::eip
+
+ package {
+ 'openvpn': ensure => latest
+ }
+
+ service {
+ 'openvpn':
+ ensure => running,
+ hasrestart => true,
+ hasstatus => true,
+ require => [
+ Package['openvpn'],
+ Exec['concat_/etc/default/openvpn'] ];
+ }
+
+ file {
+ '/etc/openvpn':
+ ensure => directory,
+ notify => Exec['restart_openvpn'],
+ require => Package['openvpn'];
+ }
+
+ file {
+ '/etc/openvpn/keys':
+ ensure => directory,
+ require => Package['openvpn'];
+ }
+
+ concat {
+ '/etc/default/openvpn':
+ owner => root,
+ group => root,
+ mode => 644,
+ warn => true,
+ notify => Service['openvpn'];
+ }
+
+ concat::fragment {
+ 'openvpn.default.header':
+ content => template('openvpn/etc-default-openvpn.erb'),
+ target => '/etc/default/openvpn',
+ order => 01;
+ }
+
+ concat::fragment {
+ "openvpn.default.autostart.${name}":
+ content => 'AUTOSTART=all',
+ target => '/etc/default/openvpn',
+ order => 10;
+ }
+
+ leap::logfile { 'openvpn_tcp': }
+ leap::logfile { 'openvpn_udp': }
+
+ # Because we currently do not support ipv6 and instead block it (so no leaks
+ # happen), we get a large number of these messages, so we ignore them (#6540)
+ rsyslog::snippet { '01-ignore_icmpv6_send':
+ content => ':msg, contains, "icmpv6_send: no reply to icmp error" ~'
+ }
+
+ include site_check_mk::agent::openvpn
+
+}
diff --git a/puppet/modules/site_openvpn/manifests/resolver.pp b/puppet/modules/site_openvpn/manifests/resolver.pp
new file mode 100644
index 00000000..cea0153a
--- /dev/null
+++ b/puppet/modules/site_openvpn/manifests/resolver.pp
@@ -0,0 +1,50 @@
+class site_openvpn::resolver {
+
+ if $site_openvpn::openvpn_allow_unlimited {
+ $ensure_unlimited = 'present'
+ file {
+ '/etc/unbound/unbound.conf.d/vpn_unlimited_udp_resolver.conf':
+ content => "server:\n\tinterface: ${site_openvpn::openvpn_unlimited_udp_network_prefix}.1\n\taccess-control: ${site_openvpn::openvpn_unlimited_udp_network_prefix}.0/${site_openvpn::openvpn_unlimited_udp_cidr} allow\n",
+ owner => root,
+ group => root,
+ mode => '0644',
+ require => [ Class['site_config::caching_resolver'], Service['openvpn'] ],
+ notify => Service['unbound'];
+ '/etc/unbound/unbound.conf.d/vpn_unlimited_tcp_resolver.conf':
+ content => "server:\n\tinterface: ${site_openvpn::openvpn_unlimited_tcp_network_prefix}.1\n\taccess-control: ${site_openvpn::openvpn_unlimited_tcp_network_prefix}.0/${site_openvpn::openvpn_unlimited_tcp_cidr} allow\n",
+ owner => root,
+ group => root,
+ mode => '0644',
+ require => [ Class['site_config::caching_resolver'], Service['openvpn'] ],
+ notify => Service['unbound'];
+ }
+ } else {
+ $ensure_unlimited = 'absent'
+ tidy { '/etc/unbound/unbound.conf.d/vpn_unlimited_udp_resolver.conf': }
+ tidy { '/etc/unbound/unbound.conf.d/vpn_unlimited_tcp_resolver.conf': }
+ }
+
+ if $site_openvpn::openvpn_allow_limited {
+ $ensure_limited = 'present'
+ file {
+ '/etc/unbound/unbound.conf.d/vpn_limited_udp_resolver.conf':
+ content => "server:\n\tinterface: ${site_openvpn::openvpn_limited_udp_network_prefix}.1\n\taccess-control: ${site_openvpn::openvpn_limited_udp_network_prefix}.0/${site_openvpn::openvpn_limited_udp_cidr} allow\n",
+ owner => root,
+ group => root,
+ mode => '0644',
+ require => [ Class['site_config::caching_resolver'], Service['openvpn'] ],
+ notify => Service['unbound'];
+ '/etc/unbound/unbound.conf.d/vpn_limited_tcp_resolver.conf':
+ content => "server\n\tinterface: ${site_openvpn::openvpn_limited_tcp_network_prefix}.1\n\taccess-control: ${site_openvpn::openvpn_limited_tcp_network_prefix}.0/${site_openvpn::openvpn_limited_tcp_cidr} allow\n",
+ owner => root,
+ group => root,
+ mode => '0644',
+ require => [ Class['site_config::caching_resolver'], Service['openvpn'] ],
+ notify => Service['unbound'];
+ }
+ } else {
+ $ensure_limited = 'absent'
+ tidy { '/etc/unbound/unbound.conf.d/vpn_limited_udp_resolver.conf': }
+ tidy { '/etc/unbound/unbound.conf.d/vpn_limited_tcp_resolver.conf': }
+ }
+}
diff --git a/puppet/modules/site_openvpn/manifests/server_config.pp b/puppet/modules/site_openvpn/manifests/server_config.pp
new file mode 100644
index 00000000..15e6fb38
--- /dev/null
+++ b/puppet/modules/site_openvpn/manifests/server_config.pp
@@ -0,0 +1,228 @@
+#
+# Cipher discussion
+# ================================
+#
+# We want to specify explicit values for the crypto options to prevent a MiTM from forcing
+# a weaker cipher. These should be set in both the server and the client ('auth' and 'cipher'
+# MUST be the same on both ends or no data will get transmitted).
+#
+# tls-cipher DHE-RSA-AES128-SHA
+#
+# dkg: For the TLS control channel, we want to make sure we choose a
+# key exchange mechanism that has PFS (meaning probably some form of ephemeral
+# Diffie-Hellman key exchange), and that uses a standard, well-tested cipher
+# (I recommend AES, and 128 bits is probably fine, since there are some known
+# weaknesses in the 192- and 256-bit key schedules). That leaves us with the
+# choice of public key algorithms: /usr/sbin/openvpn --show-tls | grep DHE |
+# grep AES128 | grep GCM.
+#
+# elijah:
+# I could not get any of these working:
+# * openvpn --show-tls | grep GCM
+# * openvpn --show-tls | grep DHE | grep AES128 | grep SHA256
+# so, i went with this:
+# * openvpn --show-tls | grep DHE | grep AES128 | grep -v SHA256 | grep -v GCM
+# Also, i couldn't get any of the elliptical curve algorithms to work. Not sure how
+# our cert generation interacts with the tls-cipher algorithms.
+#
+# note: in my tests, DHE-RSA-AES256-SHA is the one it negotiates if no value is set.
+#
+# auth SHA1
+#
+# dkg: For HMAC digest to authenticate packets, we just want SHA256. OpenVPN lists
+# a number of "digest" with names like "RSA-SHA256", but this are legacy and
+# should be avoided.
+#
+# elijah: i am not so sure that the digest algo matters for 'auth' option, because
+# i think an attacker would have to forge the digest in real time, which is still far from
+# a possibility for SHA1. So, i am leaving the default for now (SHA1).
+#
+# cipher AES-128-CBC
+#
+# dkg: For the choice of cipher, we need to select an algorithm and a
+# cipher mode. OpenVPN defaults to Blowfish, which is a fine algorithm - but
+# our control channel is already relying on AES not being broken; if the
+# control channel is cracked, then the key material for the tunnel is exposed,
+# and the choice of algorithm is moot. So it makes more sense to me to rely on
+# the same cipher here: AES128. As for the cipher mode, OFB seems cleaner to
+# me, but CBC is more well-tested, and the OpenVPN man page (at least as of
+# version 2.2.1) says "CBC is recommended and CFB and OFB should be considered
+# advanced modes."
+#
+# note: the default is BF-CBC (blowfish)
+#
+
+define site_openvpn::server_config(
+ $port, $proto, $local, $server, $push,
+ $management, $config, $tls_remote = undef) {
+
+ $openvpn_configname = $name
+ $shortname = regsubst(regsubst($name, '_config', ''), '_', '-')
+ $openvpn_status_filename = "/var/run/openvpn-status-${shortname}"
+
+ concat {
+ "/etc/openvpn/${openvpn_configname}.conf":
+ owner => root,
+ group => root,
+ mode => 644,
+ warn => true,
+ require => File['/etc/openvpn'],
+ before => Service['openvpn'],
+ notify => Exec['restart_openvpn'];
+ }
+
+ if $tls_remote != undef {
+ openvpn::option {
+ "tls-remote ${openvpn_configname}":
+ key => 'tls-remote',
+ value => $tls_remote,
+ server => $openvpn_configname;
+ }
+ }
+
+ # according to openvpn man page: tcp-nodelay is a "generally a good latency optimization".
+ if $proto == 'tcp' {
+ openvpn::option {
+ "tcp-nodelay ${openvpn_configname}":
+ key => 'tcp-nodelay',
+ server => $openvpn_configname;
+ }
+ } elsif $proto == 'udp' {
+ if $config['fragment'] != 1500 {
+ openvpn::option {
+ "fragment ${openvpn_configname}":
+ key => 'fragment',
+ value => $config['fragment'],
+ server => $openvpn_configname;
+ "mssfix ${openvpn_configname}":
+ key => 'mssfix',
+ server => $openvpn_configname;
+ }
+ }
+ }
+
+ openvpn::option {
+ "ca ${openvpn_configname}":
+ key => 'ca',
+ value => "${x509::variables::local_CAs}/${site_config::params::ca_bundle_name}.crt",
+ server => $openvpn_configname;
+ "cert ${openvpn_configname}":
+ key => 'cert',
+ value => "${x509::variables::certs}/${site_config::params::cert_name}.crt",
+ server => $openvpn_configname;
+ "key ${openvpn_configname}":
+ key => 'key',
+ value => "${x509::variables::keys}/${site_config::params::cert_name}.key",
+ server => $openvpn_configname;
+ "dh ${openvpn_configname}":
+ key => 'dh',
+ value => '/etc/openvpn/keys/dh.pem',
+ server => $openvpn_configname;
+ "tls-cipher ${openvpn_configname}":
+ key => 'tls-cipher',
+ value => $config['tls-cipher'],
+ server => $openvpn_configname;
+ "auth ${openvpn_configname}":
+ key => 'auth',
+ value => $config['auth'],
+ server => $openvpn_configname;
+ "cipher ${openvpn_configname}":
+ key => 'cipher',
+ value => $config['cipher'],
+ server => $openvpn_configname;
+ "dev ${openvpn_configname}":
+ key => 'dev',
+ value => 'tun',
+ server => $openvpn_configname;
+ "tun-ipv6 ${openvpn_configname}":
+ key => 'tun-ipv6',
+ server => $openvpn_configname;
+ "duplicate-cn ${openvpn_configname}":
+ key => 'duplicate-cn',
+ server => $openvpn_configname;
+ "keepalive ${openvpn_configname}":
+ key => 'keepalive',
+ value => $config['keepalive'],
+ server => $openvpn_configname;
+ "local ${openvpn_configname}":
+ key => 'local',
+ value => $local,
+ server => $openvpn_configname;
+ "mute ${openvpn_configname}":
+ key => 'mute',
+ value => '5',
+ server => $openvpn_configname;
+ "mute-replay-warnings ${openvpn_configname}":
+ key => 'mute-replay-warnings',
+ server => $openvpn_configname;
+ "management ${openvpn_configname}":
+ key => 'management',
+ value => $management,
+ server => $openvpn_configname;
+ "proto ${openvpn_configname}":
+ key => 'proto',
+ value => $proto,
+ server => $openvpn_configname;
+ "push1 ${openvpn_configname}":
+ key => 'push',
+ value => $push,
+ server => $openvpn_configname;
+ "push2 ${openvpn_configname}":
+ key => 'push',
+ value => '"redirect-gateway def1"',
+ server => $openvpn_configname;
+ "push-ipv6 ${openvpn_configname}":
+ key => 'push',
+ value => '"route-ipv6 2000::/3"',
+ server => $openvpn_configname;
+ "script-security ${openvpn_configname}":
+ key => 'script-security',
+ value => '1',
+ server => $openvpn_configname;
+ "server ${openvpn_configname}":
+ key => 'server',
+ value => $server,
+ server => $openvpn_configname;
+ "server-ipv6 ${openvpn_configname}":
+ key => 'server-ipv6',
+ value => '2001:db8:123::/64',
+ server => $openvpn_configname;
+ "status ${openvpn_configname}":
+ key => 'status',
+ value => "${openvpn_status_filename} 10",
+ server => $openvpn_configname;
+ "status-version ${openvpn_configname}":
+ key => 'status-version',
+ value => '3',
+ server => $openvpn_configname;
+ "topology ${openvpn_configname}":
+ key => 'topology',
+ value => 'subnet',
+ server => $openvpn_configname;
+ "verb ${openvpn_configname}":
+ key => 'verb',
+ value => '3',
+ server => $openvpn_configname;
+ "log-append /var/log/leap/openvpn_${proto}.log":
+ key => 'log-append',
+ value => "/var/log/leap/openvpn_${proto}.log",
+ server => $openvpn_configname;
+ }
+
+ # register openvpn services at systemd on nodes newer than wheezy
+ # see https://leap.se/code/issues/7798
+ case $::operatingsystemrelease {
+ /^7.*/: { }
+ default: {
+ exec { "enable_systemd_${openvpn_configname}":
+ refreshonly => true,
+ command => "/bin/systemctl enable openvpn@${openvpn_configname}",
+ subscribe => File["/etc/openvpn/${openvpn_configname}.conf"],
+ notify => Service["openvpn@${openvpn_configname}"];
+ }
+ service { "openvpn@${openvpn_configname}":
+ ensure => running
+ }
+ }
+ }
+}
diff --git a/puppet/modules/site_openvpn/templates/add_gateway_ips.sh.erb b/puppet/modules/site_openvpn/templates/add_gateway_ips.sh.erb
new file mode 100644
index 00000000..e76b756b
--- /dev/null
+++ b/puppet/modules/site_openvpn/templates/add_gateway_ips.sh.erb
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+ip addr show dev <%= scope.lookupvar('site_config::params::interface') %> | grep -q <%= @openvpn_gateway_address %>/<%= @primary_netmask %> ||
+ ip addr add <%= @openvpn_gateway_address %>/<%= @primary_netmask %> dev <%= scope.lookupvar('site_config::params::interface') %>
+
+<% if @openvpn_second_gateway_address %>
+ip addr show dev <%= scope.lookupvar('site_config::params::interface') %> | grep -q <%= @openvpn_second_gateway_address %>/<%= @primary_netmask %> ||
+ ip addr add <%= @openvpn_second_gateway_address %>/<%= @primary_netmask %> dev <%= scope.lookupvar('site_config::params::interface') %>
+<% end %>
+
+/bin/echo 1 > /proc/sys/net/ipv4/ip_forward
diff --git a/puppet/modules/site_postfix/files/checks/received_anon b/puppet/modules/site_postfix/files/checks/received_anon
new file mode 100644
index 00000000..9de25e63
--- /dev/null
+++ b/puppet/modules/site_postfix/files/checks/received_anon
@@ -0,0 +1,2 @@
+/^Received: from (.* \([-._[:alnum:]]+ \[[.[:digit:]]{7,15}\]\))([[:space:]]+).*(\(using [.[:alnum:]]+ with cipher [-A-Z0-9]+ \([0-9]+\/[0-9]+ bits\)\))[[:space:]]+\(Client CN "([-._@[:alnum:]]+)", Issuer "[[:print:]]+" \(verified OK\)\)[[:space:]]+by ([.[:alnum:]]+) \(([^)]+)\) with (E?SMTPS?A?) id ([A-F[:digit:]]+).*/
+ REPLACE Received: from [127.0.0.1] (localhost [127.0.0.1])${2}${3}${2}(Authenticated sender: $4)${2}with $7 id $8
diff --git a/puppet/modules/site_postfix/manifests/debug.pp b/puppet/modules/site_postfix/manifests/debug.pp
new file mode 100644
index 00000000..f370d166
--- /dev/null
+++ b/puppet/modules/site_postfix/manifests/debug.pp
@@ -0,0 +1,9 @@
+class site_postfix::debug {
+
+ postfix::config {
+ 'debug_peer_list': value => '127.0.0.1';
+ 'debug_peer_level': value => '1';
+ 'smtpd_tls_loglevel': value => '1';
+ }
+
+}
diff --git a/puppet/modules/site_postfix/manifests/mx.pp b/puppet/modules/site_postfix/manifests/mx.pp
new file mode 100644
index 00000000..c269946b
--- /dev/null
+++ b/puppet/modules/site_postfix/manifests/mx.pp
@@ -0,0 +1,152 @@
+#
+# configure mx node
+#
+class site_postfix::mx {
+
+ $domain_hash = hiera('domain')
+ $domain = $domain_hash['full_suffix']
+ $host_domain = $domain_hash['full']
+ $cert_name = hiera('name')
+ $mynetworks = join(hiera('mynetworks', ''), ' ')
+ $rbls = suffix(prefix(hiera('rbls', []), 'reject_rbl_client '), ',')
+
+ $root_mail_recipient = hiera('contacts')
+ $postfix_smtp_listen = 'all'
+ $postfix_use_postscreen = 'yes'
+
+ include site_config::x509::cert
+ include site_config::x509::key
+ include site_config::x509::client_ca::ca
+ include site_config::x509::client_ca::key
+
+ postfix::config {
+ 'mynetworks':
+ value => "127.0.0.0/8 [::1]/128 [fe80::]/64 ${mynetworks}";
+ # Note: mydestination should not include @domain, because this is
+ # used in virtual alias maps.
+ 'mydestination':
+ value => "\$myorigin, localhost, localhost.\$mydomain";
+ 'myhostname':
+ value => $host_domain;
+ 'mailbox_size_limit':
+ value => '0';
+ 'home_mailbox':
+ value => '';
+ 'virtual_mailbox_domains':
+ value => 'deliver.local';
+ 'virtual_mailbox_base':
+ value => '/var/mail/leap-mx';
+ 'virtual_mailbox_maps':
+ value => 'static:Maildir/';
+ # Note: virtual-aliases map will take precedence over leap-mx
+ # lookup (tcp:localhost)
+ 'virtual_alias_maps':
+ value => 'hash:/etc/postfix/virtual-aliases tcp:localhost:4242';
+ 'luser_relay':
+ value => '';
+ # uid and gid are set to an arbitrary hard-coded value here, this
+ # must match the 'leap-mx' user/group
+ 'virtual_uid_maps':
+ value => 'static:42424';
+ 'virtual_gid_maps':
+ value => 'static:42424';
+ # the two following configs are needed for matching user's client cert
+ # fingerprints to enable relaying (#3634). Satellites do not have
+ # these configured.
+ 'smtpd_tls_fingerprint_digest':
+ value => 'sha1';
+ 'relay_clientcerts':
+ value => 'tcp:localhost:2424';
+ # Note: we are setting this here, instead of in site_postfix::mx::smtp_tls
+ # because the satellites need to have a different value
+ 'smtp_tls_security_level':
+ value => 'may';
+ # reject inbound mail to system users
+ # see https://leap.se/code/issues/6829
+ # this blocks *only* mails to system users, that don't appear in the
+ # alias map
+ 'local_recipient_maps':
+ value => '$alias_maps';
+ # setup clamav and opendkim on smtpd
+ 'smtpd_milters':
+ value => 'unix:/run/clamav/milter.ctl,inet:localhost:8891';
+ # setup opendkim for smtp (non-smtpd) outgoing mail
+ 'non_smtpd_milters':
+ value => 'inet:localhost:8891';
+ 'milter_default_action':
+ value => 'accept';
+ # Make sure that the right values are set, these could be set to different
+ # things on install, depending on preseed or debconf options
+ # selected (see #7478)
+ 'relay_transport':
+ value => 'relay';
+ 'default_transport':
+ value => 'smtp';
+ 'mailbox_command':
+ value => '';
+ 'header_checks':
+ value => '';
+ 'postscreen_access_list':
+ value => 'permit_mynetworks';
+ 'postscreen_greet_action':
+ value => 'enforce';
+ }
+
+ # Make sure that the cleanup serivce is not chrooted, otherwise it cannot
+ # access the opendkim milter socket (#8020)
+ exec { 'unset_cleanup_chroot':
+ command => '/usr/sbin/postconf -F "cleanup/unix/chroot=n"',
+ onlyif => '/usr/sbin/postconf -h -F "cleanup/unix/chroot" | egrep -q ^n',
+ notify => Service['postfix'],
+ require => File['/etc/postfix/master.cf']
+ }
+
+ include ::site_postfix::mx::smtpd_checks
+ include ::site_postfix::mx::checks
+ include ::site_postfix::mx::smtp_tls
+ include ::site_postfix::mx::smtpd_tls
+ include ::site_postfix::mx::static_aliases
+ include ::site_postfix::mx::rewrite_openpgp_header
+ include ::site_postfix::mx::received_anon
+ include ::clamav
+ include ::opendkim
+ include ::postfwd
+
+ # greater verbosity for debugging, take out for production
+ #include site_postfix::debug
+
+ case $::operatingsystemrelease {
+ /^7.*/: {
+ $smtpd_relay_restrictions=''
+ }
+ default: {
+ $smtpd_relay_restrictions=" -o smtpd_relay_restrictions=\$smtps_relay_restrictions\n"
+ }
+ }
+
+ $mastercf_tail = "
+smtps inet n - - - - smtpd
+ -o smtpd_tls_wrappermode=yes
+ -o smtpd_tls_security_level=encrypt
+ -o tls_preempt_cipherlist=yes
+${smtpd_relay_restrictions} -o smtpd_recipient_restrictions=\$smtps_recipient_restrictions
+ -o smtpd_helo_restrictions=\$smtps_helo_restrictions
+ -o smtpd_client_restrictions=
+ -o cleanup_service_name=clean_smtps
+clean_smtps unix n - n - 0 cleanup
+ -o header_checks=pcre:/etc/postfix/checks/rewrite_openpgp_headers,pcre:/etc/postfix/checks/received_anon"
+
+ class { 'postfix':
+ preseed => true,
+ root_mail_recipient => $root_mail_recipient,
+ smtp_listen => 'all',
+ mastercf_tail => $mastercf_tail,
+ use_postscreen => 'yes',
+ require => [
+ Class['Site_config::X509::Key'],
+ Class['Site_config::X509::Cert'],
+ Class['Site_config::X509::Client_ca::Key'],
+ Class['Site_config::X509::Client_ca::Ca'],
+ User['leap-mx'] ]
+ }
+}
diff --git a/puppet/modules/site_postfix/manifests/mx/checks.pp b/puppet/modules/site_postfix/manifests/mx/checks.pp
new file mode 100644
index 00000000..f406ad34
--- /dev/null
+++ b/puppet/modules/site_postfix/manifests/mx/checks.pp
@@ -0,0 +1,23 @@
+class site_postfix::mx::checks {
+
+ file {
+ '/etc/postfix/checks':
+ ensure => directory,
+ mode => '0755',
+ owner => root,
+ group => postfix,
+ require => Package['postfix'];
+
+ '/etc/postfix/checks/helo_checks':
+ content => template('site_postfix/checks/helo_access.erb'),
+ mode => '0644',
+ owner => root,
+ group => root;
+ }
+
+ exec {
+ '/usr/sbin/postmap /etc/postfix/checks/helo_checks':
+ refreshonly => true,
+ subscribe => File['/etc/postfix/checks/helo_checks'];
+ }
+}
diff --git a/puppet/modules/site_postfix/manifests/mx/received_anon.pp b/puppet/modules/site_postfix/manifests/mx/received_anon.pp
new file mode 100644
index 00000000..51ba3faa
--- /dev/null
+++ b/puppet/modules/site_postfix/manifests/mx/received_anon.pp
@@ -0,0 +1,13 @@
+# Anonymize the user's home IP from the email headers (Feature #3866)
+class site_postfix::mx::received_anon {
+
+ package { 'postfix-pcre': ensure => installed, require => Package['postfix'] }
+
+ file { '/etc/postfix/checks/received_anon':
+ source => 'puppet:///modules/site_postfix/checks/received_anon',
+ mode => '0644',
+ owner => root,
+ group => root,
+ notify => Service['postfix']
+ }
+}
diff --git a/puppet/modules/site_postfix/manifests/mx/rewrite_openpgp_header.pp b/puppet/modules/site_postfix/manifests/mx/rewrite_openpgp_header.pp
new file mode 100644
index 00000000..71f945b8
--- /dev/null
+++ b/puppet/modules/site_postfix/manifests/mx/rewrite_openpgp_header.pp
@@ -0,0 +1,11 @@
+class site_postfix::mx::rewrite_openpgp_header {
+ $mx = hiera('mx')
+ $correct_domain = $mx['key_lookup_domain']
+
+ file { '/etc/postfix/checks/rewrite_openpgp_headers':
+ content => template('site_postfix/checks/rewrite_openpgp_headers.erb'),
+ mode => '0644',
+ owner => root,
+ group => root;
+ }
+}
diff --git a/puppet/modules/site_postfix/manifests/mx/smtp_auth.pp b/puppet/modules/site_postfix/manifests/mx/smtp_auth.pp
new file mode 100644
index 00000000..afa70527
--- /dev/null
+++ b/puppet/modules/site_postfix/manifests/mx/smtp_auth.pp
@@ -0,0 +1,6 @@
+class site_postfix::mx::smtp_auth {
+
+ postfix::config {
+ 'smtpd_tls_ask_ccert': value => 'yes';
+ }
+}
diff --git a/puppet/modules/site_postfix/manifests/mx/smtp_tls.pp b/puppet/modules/site_postfix/manifests/mx/smtp_tls.pp
new file mode 100644
index 00000000..c93c3ba2
--- /dev/null
+++ b/puppet/modules/site_postfix/manifests/mx/smtp_tls.pp
@@ -0,0 +1,43 @@
+# configure smtp tls
+class site_postfix::mx::smtp_tls {
+
+ include site_config::x509::ca
+ include x509::variables
+ $cert_name = hiera('name')
+ $ca_path = "${x509::variables::local_CAs}/${site_config::params::ca_name}.crt"
+ $cert_path = "${x509::variables::certs}/${site_config::params::cert_name}.crt"
+ $key_path = "${x509::variables::keys}/${site_config::params::cert_name}.key"
+
+ include site_config::x509::cert
+ include site_config::x509::key
+
+ # smtp TLS
+ postfix::config {
+ 'smtp_use_tls': value => 'yes';
+ 'smtp_tls_CApath': value => '/etc/ssl/certs/';
+ 'smtp_tls_CAfile': value => $ca_path;
+ 'smtp_tls_cert_file': value => $cert_path;
+ 'smtp_tls_key_file': value => $key_path;
+ 'smtp_tls_loglevel': value => '1';
+ 'smtp_tls_exclude_ciphers':
+ value => 'aNULL, MD5, DES';
+ # upstream default is md5 (since 2.5 and older used it), we force sha1
+ 'smtp_tls_fingerprint_digest':
+ value => 'sha1';
+ 'smtp_tls_session_cache_database':
+ value => "btree:\${data_directory}/smtp_cache";
+ # see issue #4011
+ 'smtp_tls_protocols':
+ value => '!SSLv2, !SSLv3';
+ 'smtp_tls_mandatory_protocols':
+ value => '!SSLv2, !SSLv3';
+ 'tls_ssl_options':
+ value => 'NO_COMPRESSION';
+ # We can switch between the different postfix internal list of ciphers by
+ # using smtpd_tls_ciphers. For server-to-server connections we leave this
+ # at its default because of opportunistic encryption combined with many mail
+ # servers only support outdated protocols and ciphers and if we are too
+ # strict with required ciphers, then connections *will* fall-back to
+ # plain-text. Bad ciphers are still better than plain text transmission.
+ }
+}
diff --git a/puppet/modules/site_postfix/manifests/mx/smtpd_checks.pp b/puppet/modules/site_postfix/manifests/mx/smtpd_checks.pp
new file mode 100644
index 00000000..291d7ee4
--- /dev/null
+++ b/puppet/modules/site_postfix/manifests/mx/smtpd_checks.pp
@@ -0,0 +1,36 @@
+# smtpd checks for incoming mail on smtp port 25 and
+# mail sent via the bitmask client using smtps port 465
+class site_postfix::mx::smtpd_checks {
+
+ postfix::config {
+ 'smtpd_helo_required':
+ value => 'yes';
+ 'checks_dir':
+ value => '$config_directory/checks';
+ 'smtpd_client_restrictions':
+ value => "permit_mynetworks,${site_postfix::mx::rbls},permit";
+ 'smtpd_data_restrictions':
+ value => 'permit_mynetworks, reject_unauth_pipelining, permit';
+ 'smtpd_delay_reject':
+ value => 'yes';
+ 'smtpd_helo_restrictions':
+ value => 'permit_mynetworks, reject_invalid_helo_hostname, reject_non_fqdn_helo_hostname, check_helo_access hash:$checks_dir/helo_checks, permit';
+ 'smtpd_recipient_restrictions':
+ value => 'reject_unknown_recipient_domain, permit_mynetworks, check_recipient_access tcp:localhost:2244, reject_unauth_destination, permit';
+
+ # permit_tls_clientcerts will lookup client cert fingerprints from the tcp
+ # lookup on port 2424 (based on what is configured in relay_clientcerts
+ # paramter, see site_postfix::mx postfix::config resource) to determine
+ # if a client is allowed to relay mail through us. This enables us to
+ # disable a user by removing their valid client cert (#3634)
+ 'smtps_recipient_restrictions':
+ value => 'permit_tls_clientcerts, check_recipient_access tcp:localhost:2244, reject_unauth_destination, permit';
+ 'smtps_relay_restrictions':
+ value => 'permit_mynetworks, permit_tls_clientcerts, defer_unauth_destination';
+ 'smtps_helo_restrictions':
+ value => 'permit_mynetworks, check_helo_access hash:$checks_dir/helo_checks, permit';
+ 'smtpd_sender_restrictions':
+ value => 'permit_mynetworks, reject_non_fqdn_sender, reject_unknown_sender_domain, permit';
+ }
+
+}
diff --git a/puppet/modules/site_postfix/manifests/mx/smtpd_tls.pp b/puppet/modules/site_postfix/manifests/mx/smtpd_tls.pp
new file mode 100644
index 00000000..66297f55
--- /dev/null
+++ b/puppet/modules/site_postfix/manifests/mx/smtpd_tls.pp
@@ -0,0 +1,69 @@
+# configure smtpd tls
+class site_postfix::mx::smtpd_tls {
+
+ include x509::variables
+ $ca_path = "${x509::variables::local_CAs}/${site_config::params::client_ca_name}.crt"
+ $cert_path = "${x509::variables::certs}/${site_config::params::cert_name}.crt"
+ $key_path = "${x509::variables::keys}/${site_config::params::cert_name}.key"
+
+
+ postfix::config {
+ 'smtpd_use_tls': value => 'yes';
+ 'smtpd_tls_CAfile': value => $ca_path;
+ 'smtpd_tls_cert_file': value => $cert_path;
+ 'smtpd_tls_key_file': value => $key_path;
+ 'smtpd_tls_ask_ccert': value => 'yes';
+ 'smtpd_tls_received_header':
+ value => 'yes';
+ 'smtpd_tls_security_level':
+ value => 'may';
+ 'smtpd_tls_eecdh_grade':
+ value => 'ultra';
+ 'smtpd_tls_session_cache_database':
+ value => "btree:\${data_directory}/smtpd_scache";
+ # see issue #4011
+ 'smtpd_tls_mandatory_protocols':
+ value => '!SSLv2, !SSLv3';
+ 'smtpd_tls_protocols':
+ value => '!SSLv2, !SSLv3';
+ # For connections to MUAs, TLS is mandatory and the ciphersuite is modified.
+ # MX and SMTP client configuration
+ 'smtpd_tls_mandatory_ciphers':
+ value => 'high';
+ 'tls_high_cipherlist':
+ value => 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!RC4:!MD5:!PSK!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
+ }
+
+ # Setup DH parameters
+ # Instead of using the dh parameters that are created by leap cli, it is more
+ # secure to generate new parameter files that will only be used for postfix,
+ # for each machine
+
+ include site_config::packages::gnutls
+
+ # Note, the file name is called dh_1024.pem, but we are generating 2048bit dh
+ # parameters Neither Postfix nor OpenSSL actually care about the size of the
+ # prime in "smtpd_tls_dh1024_param_file". You can make it 2048 bits
+
+ exec { 'certtool-postfix-gendh':
+ command => 'certtool --generate-dh-params --bits 2048 --outfile /etc/postfix/smtpd_tls_dh_param.pem',
+ user => root,
+ group => root,
+ creates => '/etc/postfix/smtpd_tls_dh_param.pem',
+ require => [ Package['gnutls-bin'], Package['postfix'] ]
+ }
+
+ # Make sure the dh params file has correct ownership and mode
+ file {
+ '/etc/postfix/smtpd_tls_dh_param.pem':
+ owner => root,
+ group => root,
+ mode => '0600',
+ require => Exec['certtool-postfix-gendh'];
+ }
+
+ postfix::config { 'smtpd_tls_dh1024_param_file':
+ value => '/etc/postfix/smtpd_tls_dh_param.pem',
+ require => File['/etc/postfix/smtpd_tls_dh_param.pem']
+ }
+}
diff --git a/puppet/modules/site_postfix/manifests/mx/static_aliases.pp b/puppet/modules/site_postfix/manifests/mx/static_aliases.pp
new file mode 100644
index 00000000..9cd7ca02
--- /dev/null
+++ b/puppet/modules/site_postfix/manifests/mx/static_aliases.pp
@@ -0,0 +1,88 @@
+#
+# Defines static, hard coded aliases that are not in the database.
+# These aliases take precedence over the database aliases.
+#
+# There are three classes of reserved names:
+#
+# (1) forbidden_usernames:
+# Some usernames are forbidden and cannot be registered.
+# this is defined in node property webapp.forbidden_usernames
+# This is enforced by the webapp.
+#
+# (2) public aliases:
+# Some aliases for root, and are publicly exposed so that anyone
+# can deliver mail to them. For example, postmaster.
+# These are implemented in the virtual alias map, which takes
+# precedence over the local alias map.
+#
+# (3) local aliases:
+# Some aliases are only available locally: mail can be delivered
+# to the alias if the mail originates from the local host, or is
+# hostname qualified, but otherwise it will be rejected.
+# These are implemented in the local alias map.
+#
+# The alias for local 'root' is defined elsewhere. In this file, we
+# define the virtual 'root@domain' (which can be overwritten by
+# defining an entry for root in node property mx.aliases).
+#
+
+class site_postfix::mx::static_aliases {
+
+ $mx = hiera('mx')
+ $root_recipients = hiera('contacts')
+
+ #
+ # LOCAL ALIASES
+ #
+
+ # NOTE: if you remove one of these, they will still appear in the
+ # /etc/aliases file
+ $local_aliases = [
+ 'admin', 'administrator', 'bin', 'cron', 'games', 'ftp', 'lp', 'maildrop',
+ 'mysql', 'news', 'nobody', 'noc', 'postgresql', 'ssladmin', 'sys',
+ 'usenet', 'uucp', 'www', 'www-data', 'leap-mx'
+ ]
+
+ postfix::mailalias {
+ $local_aliases:
+ ensure => present,
+ recipient => 'root'
+ }
+
+ #
+ # PUBLIC ALIASES
+ #
+
+ $public_aliases = $mx['aliases']
+
+ $default_public_aliases = {
+ 'root' => $root_recipients,
+ 'abuse' => 'postmaster',
+ 'arin-admin' => 'root',
+ 'certmaster' => 'hostmaster',
+ 'domainadmin' => 'hostmaster',
+ 'hostmaster' => 'root',
+ 'mailer-daemon' => 'postmaster',
+ 'postmaster' => 'root',
+ 'security' => 'root',
+ 'webmaster' => 'hostmaster',
+ }
+
+ $aliases = merge($default_public_aliases, $public_aliases)
+
+ exec { 'postmap_virtual_aliases':
+ command => '/usr/sbin/postmap /etc/postfix/virtual-aliases',
+ refreshonly => true,
+ user => root,
+ group => root,
+ require => Package['postfix'],
+ subscribe => File['/etc/postfix/virtual-aliases']
+ }
+ file { '/etc/postfix/virtual-aliases':
+ content => template('site_postfix/virtual-aliases.erb'),
+ owner => root,
+ group => root,
+ mode => '0600',
+ require => Package['postfix']
+ }
+}
diff --git a/puppet/modules/site_postfix/manifests/satellite.pp b/puppet/modules/site_postfix/manifests/satellite.pp
new file mode 100644
index 00000000..5725e6b8
--- /dev/null
+++ b/puppet/modules/site_postfix/manifests/satellite.pp
@@ -0,0 +1,47 @@
+class site_postfix::satellite {
+
+ $root_mail_recipient = hiera ('contacts')
+ $mail = hiera ('mail')
+ $relayhost = $mail['smarthost']
+ $cert_name = hiera('name')
+
+ class { '::postfix::satellite':
+ relayhost => $relayhost,
+ root_mail_recipient => $root_mail_recipient
+ }
+
+ # There are special conditions for satellite hosts that will make them not be
+ # able to contact their relayhost:
+ #
+ # 1. they are on openstack/amazon/PC and are on the same cluster as the relay
+ # host, the MX lookup for the relay host will use the public IP, which cannot
+ # be contacted
+ #
+ # 2. When a domain is used that is not in DNS, because it is internal,
+ # a testing domain, etc. eg. a .local domain cannot be looked up in DNS
+ #
+ # to resolve this, so the satellite can contact the relayhost, we need to set
+ # the http://www.postfix.org/postconf.5.html#smtp_host_lookup to be 'native'
+ # which will cause the lookup to use the native naming service
+ # (nsswitch.conf), which typically defaults to 'files, dns' allowing the
+ # /etc/hosts to be consulted first, then DNS if the entry doesn't exist.
+ #
+ # NOTE: this will make it not possible to enable DANE support through DNSSEC
+ # with http://www.postfix.org/postconf.5.html#smtp_dns_support_level - but
+ # this parameter is not available until 2.11. If this ends up being important
+ # we could also make this an optional parameter for providers without
+ # dns / local domains
+
+ postfix::config {
+ 'smtp_host_lookup':
+ value => 'native';
+
+ # Note: we are setting this here, instead of in site_postfix::mx::smtp_tls
+ # because the mx server has to have a different value
+ 'smtp_tls_security_level':
+ value => 'encrypt';
+ }
+
+ include site_postfix::mx::smtp_tls
+
+}
diff --git a/puppet/modules/site_postfix/templates/checks/helo_access.erb b/puppet/modules/site_postfix/templates/checks/helo_access.erb
new file mode 100644
index 00000000..bac2c45a
--- /dev/null
+++ b/puppet/modules/site_postfix/templates/checks/helo_access.erb
@@ -0,0 +1,21 @@
+# THIS FILE IS MANAGED BY PUPPET
+# To make changes to this file, please edit your platform directory under
+# puppet/modules/site_postfix/templates/checks/helo_access.erb and then deploy
+
+# The format of this file is the HELO/EHLO domain followed by an action.
+# The action could be OK to allow it, REJECT to reject it, or a custom
+# status code and message. Any lines that are prefixed by an octothorpe (#)
+# will be considered comments.
+
+# Some examples:
+#
+# Reject anyone that HELO's with foobar:
+# foobar REJECT
+#
+# Allow the switches to skip this check:
+# switch1 OK
+# switch2 OK
+
+# Reject anybody that HELO's as being in our own domain(s)
+# anyone who identifies themselves as us is a virus/spammer
+<%= @domain %> 554 You are not in domain <%= @domain %>
diff --git a/puppet/modules/site_postfix/templates/checks/rewrite_openpgp_headers.erb b/puppet/modules/site_postfix/templates/checks/rewrite_openpgp_headers.erb
new file mode 100644
index 00000000..7af14f7d
--- /dev/null
+++ b/puppet/modules/site_postfix/templates/checks/rewrite_openpgp_headers.erb
@@ -0,0 +1,13 @@
+# THIS FILE IS MANAGED BY PUPPET
+#
+# This will replace the OpenPGP header that the client adds, because it is
+# sometimes incorrect (due to the client not always knowing what the proper URL
+# is for the webapp).
+# e.g. This will rewrite this header:
+# OpenPGP: id=4C0E01CD50E2F653; url="https://leap.se/key/elijah"; preference="signencrypt
+# with this replacement:
+# OpenPGP: id=4C0E01CD50E2F653; url="https://user.leap.se/key/elijah"; preference="signencrypt
+#
+# Note: whitespace in the pattern is represented by [[:space:]] to avoid these warnings from postmap:
+# "record is in "key: value" format; is this an alias file?" and "duplicate entry"
+/^(OpenPGP:[[:space:]]id=[[:alnum:]]+;[[:space:]]url="https:\/\/)<%= @domain %>(\/key\/[[:alpha:]]+";.*)/i REPLACE ${1}<%= @correct_domain %>${2}
diff --git a/puppet/modules/site_postfix/templates/virtual-aliases.erb b/puppet/modules/site_postfix/templates/virtual-aliases.erb
new file mode 100644
index 00000000..8373de97
--- /dev/null
+++ b/puppet/modules/site_postfix/templates/virtual-aliases.erb
@@ -0,0 +1,21 @@
+#
+# This file is managed by puppet.
+#
+# These virtual aliases take precedence over all other aliases.
+#
+
+#
+# enable these virtual domains:
+#
+<%= @domain %> enabled
+<%- @aliases.keys.map {|addr| addr.split('@')[1] }.compact.sort.uniq.each do |virt_domain| -%>
+<%= virt_domain %> enabled
+<%- end %>
+
+#
+# virtual aliases:
+#
+<%- @aliases.keys.sort.each do |from| -%>
+<%- full_address = from =~ /@/ ? from : from + "@" + @domain -%>
+<%= full_address %> <%= [@aliases[from]].flatten.map{|a| a =~ /@/ ? a : a + "@" + @domain}.join(', ') %>
+<%- end -%>
diff --git a/puppet/modules/site_rsyslog/templates/client.conf.erb b/puppet/modules/site_rsyslog/templates/client.conf.erb
new file mode 100644
index 00000000..7f94759d
--- /dev/null
+++ b/puppet/modules/site_rsyslog/templates/client.conf.erb
@@ -0,0 +1,134 @@
+
+# An "In-Memory Queue" is created for remote logging.
+$WorkDirectory <%= scope.lookupvar('rsyslog::spool_dir') -%> # where to place spool files
+$ActionQueueFileName queue # unique name prefix for spool files
+$ActionQueueMaxDiskSpace <%= scope.lookupvar('rsyslog::client::spool_size') -%> # spool space limit (use as much as possible)
+$ActionQueueSaveOnShutdown on # save messages to disk on shutdown
+$ActionQueueType LinkedList # run asynchronously
+$ActionResumeRetryCount -1 # infinety retries if host is down
+<% if scope.lookupvar('rsyslog::client::log_templates') and ! scope.lookupvar('rsyslog::client::log_templates').empty?-%>
+
+# Define custom logging templates
+<% scope.lookupvar('rsyslog::client::log_templates').flatten.compact.each do |log_template| -%>
+$template <%= log_template['name'] %>,"<%= log_template['template'] %>"
+<% end -%>
+<% end -%>
+<% if scope.lookupvar('rsyslog::client::actionfiletemplate') -%>
+
+# Using specified format for default logging format:
+$ActionFileDefaultTemplate <%= scope.lookupvar('rsyslog::client::actionfiletemplate') %>
+<% else -%>
+
+#Using default format for default logging format:
+$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat
+<% end -%>
+<% if scope.lookupvar('rsyslog::client::ssl') -%>
+
+# Setup SSL connection.
+# CA/Cert
+$DefaultNetStreamDriverCAFile <%= scope.lookupvar('rsyslog::client::ssl_ca') %>
+
+# Connection settings.
+$DefaultNetstreamDriver gtls
+$ActionSendStreamDriverMode 1
+$ActionSendStreamDriverAuthMode anon
+<% end -%>
+<% if scope.lookupvar('rsyslog::client::remote_servers') -%>
+
+<% scope.lookupvar('rsyslog::client::remote_servers').flatten.compact.each do |server| -%>
+<% if server['pattern'] and server['pattern'] != ''-%>
+<% pattern = server['pattern'] -%>
+<% else -%>
+<% pattern = '*.*' -%>
+<% end -%>
+<% if server['protocol'] == 'TCP' or server['protocol'] == 'tcp'-%>
+<% protocol = '@@' -%>
+<% protocol_type = 'TCP' -%>
+<% else -%>
+<% protocol = '@' -%>
+<% protocol_type = 'UDP' -%>
+<% end -%>
+<% if server['host'] and server['host'] != ''-%>
+<% host = server['host'] -%>
+<% else -%>
+<% host = 'localhost' -%>
+<% end -%>
+<% if server['port'] and server['port'] != ''-%>
+<% port = server['port'] -%>
+<% else -%>
+<% port = '514' -%>
+<% end -%>
+<% if server['format'] -%>
+<% format = ";#{server['format']}" -%>
+<% format_type = server['format'] -%>
+<% else -%>
+<% format = '' -%>
+<% format_type = 'the default' -%>
+<% end -%>
+# Sending logs that match <%= pattern %> to <%= host %> via <%= protocol_type %> on <%= port %> using <%=format_type %> format.
+<%= pattern %> <%= protocol %><%= host %>:<%= port %><%= format %>
+<% end -%>
+<% elsif scope.lookupvar('rsyslog::client::log_remote') -%>
+
+# Log to remote syslog server using <%= scope.lookupvar('rsyslog::client::remote_type') %>
+<% if scope.lookupvar('rsyslog::client::remote_type') == 'tcp' -%>
+*.* @@<%= scope.lookupvar('rsyslog::client::server') -%>:<%= scope.lookupvar('rsyslog::client::port') -%>;<%= scope.lookupvar('remote_forward_format') -%>
+<% else -%>
+*.* @<%= scope.lookupvar('rsyslog::client::server') -%>:<%= scope.lookupvar('rsyslog::client::port') -%>;<%= scope.lookupvar('remote_forward_format') -%>
+<% end -%>
+<% end -%>
+<% if scope.lookupvar('rsyslog::client::log_auth_local') or scope.lookupvar('rsyslog::client::log_local') -%>
+
+# Logging locally.
+
+<% if scope.lookupvar('rsyslog::log_style') == 'debian' -%>
+# Log auth messages locally
+.*;auth,authpriv.none;mail.none -/var/log/syslog
+<% elsif scope.lookupvar('rsyslog::log_style') == 'redhat' -%>
+# Log auth messages locally
+auth,authpriv.* /var/log/secure
+<% end -%>
+<% end -%>
+<% if scope.lookupvar('rsyslog::client::log_local') -%>
+<% if scope.lookupvar('rsyslog::log_style') == 'debian' -%>
+# First some standard log files. Log by facility.
+#
+*.*;auth,authpriv.none -/var/log/syslog
+cron.* /var/log/cron.log
+daemon.* -/var/log/daemon.log
+kern.* -/var/log/kern.log
+mail.* -/var/log/mail.log
+user.* -/var/log/user.log
+
+#
+# Some "catch-all" log files.
+#
+*.=debug;\
+ auth,authpriv.none;\
+ news.none;mail.none -/var/log/debug
+*.=info;*.=notice;*.=warn;\
+ auth,authpriv.none;\
+ cron,daemon.none;\
+ mail,news.none -/var/log/messages
+
+# Log anything (except mail) of level info or higher.
+# Don't log private authentication messages!
+*.info;mail.none;authpriv.none;cron.none /var/log/messages
+
+# Log cron stuff
+cron.* /var/log/cron
+
+# Everybody gets emergency messages
+<% if @rsyslog_version and @rsyslog_version.split('.')[0].to_i >= 8 -%>
+*.emerg :omusrmsg:*
+<% else -%>
+*.emerg *
+<% end -%>
+
+# Save boot messages also to boot.log
+local7.* -/var/log/boot.log
+<% end -%>
+<% end -%>
+
+
+
diff --git a/puppet/modules/site_shorewall/files/Debian/shorewall.service b/puppet/modules/site_shorewall/files/Debian/shorewall.service
new file mode 100644
index 00000000..ec250ef1
--- /dev/null
+++ b/puppet/modules/site_shorewall/files/Debian/shorewall.service
@@ -0,0 +1,23 @@
+#
+# The Shoreline Firewall (Shorewall) Packet Filtering Firewall
+#
+# Copyright 2011 Jonathan Underwood <jonathan.underwood@gmail.com>
+# Copyright 2015 Tom Eastep <teastep@shorewall.net>
+#
+[Unit]
+Description=Shorewall IPv4 firewall
+Wants=network-online.target
+After=network-online.target
+Conflicts=iptables.service firewalld.service
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+EnvironmentFile=-/etc/default/shorewall
+StandardOutput=syslog
+ExecStart=/sbin/shorewall $OPTIONS start $STARTOPTIONS
+ExecStop=/sbin/shorewall $OPTIONS stop
+ExecReload=/sbin/shorewall $OPTIONS reload $RELOADOPTIONS
+
+[Install]
+WantedBy=basic.target
diff --git a/puppet/modules/site_shorewall/manifests/defaults.pp b/puppet/modules/site_shorewall/manifests/defaults.pp
new file mode 100644
index 00000000..ceb17868
--- /dev/null
+++ b/puppet/modules/site_shorewall/manifests/defaults.pp
@@ -0,0 +1,86 @@
+class site_shorewall::defaults {
+
+ include shorewall
+ include site_config::params
+
+ # be safe for development
+ # if ( $::site_config::params::environment == 'local' ) {
+ # $shorewall_startup='0'
+ # }
+
+ # If you want logging:
+ shorewall::params {
+ 'LOG': value => 'debug';
+ }
+
+ shorewall::zone {'net': type => 'ipv4'; }
+
+ # define interfaces
+ shorewall::interface { $site_config::params::interface:
+ zone => 'net',
+ options => 'tcpflags,blacklist,nosmurfs';
+ }
+
+ shorewall::policy {
+ 'fw-to-all':
+ sourcezone => 'fw',
+ destinationzone => 'all',
+ policy => 'ACCEPT',
+ order => 100;
+ 'all-to-all':
+ sourcezone => 'all',
+ destinationzone => 'all',
+ policy => 'DROP',
+ order => 200;
+ }
+
+ shorewall::rule {
+ # ping party
+ 'all2all-ping':
+ source => 'all',
+ destination => 'all',
+ action => 'Ping(ACCEPT)',
+ order => 200;
+ }
+
+ package { 'shorewall-init':
+ ensure => installed
+ }
+
+ include ::systemd
+ file { '/etc/systemd/system/shorewall.service':
+ ensure => file,
+ owner => 'root',
+ group => 'root',
+ mode => '0644',
+ source => 'puppet:///modules/site_shorewall/Debian/shorewall.service',
+ require => Package['shorewall'],
+ notify => Service['shorewall'],
+ } ~>
+ Exec['systemctl-daemon-reload']
+
+ augeas {
+ # stop instead of clear firewall on shutdown
+ 'shorewall_SAFESTOP':
+ changes => 'set /files/etc/shorewall/shorewall.conf/SAFESTOP Yes',
+ lens => 'Shellvars.lns',
+ incl => '/etc/shorewall/shorewall.conf',
+ require => Package['shorewall'],
+ notify => Service['shorewall'];
+ # require that the interface exist
+ 'shorewall_REQUIRE_INTERFACE':
+ changes => 'set /files/etc/shorewall/shorewall.conf/REQUIRE_INTERFACE Yes',
+ lens => 'Shellvars.lns',
+ incl => '/etc/shorewall/shorewall.conf',
+ require => Package['shorewall'],
+ notify => Service['shorewall'];
+ # configure shorewall-init
+ 'shorewall-init':
+ changes => 'set /files/etc/default/shorewall-init/PRODUCTS shorewall',
+ lens => 'Shellvars.lns',
+ incl => '/etc/default/shorewall-init',
+ require => [ Package['shorewall-init'], Service['shorewall'] ]
+ }
+
+ include site_shorewall::sshd
+}
diff --git a/puppet/modules/site_shorewall/manifests/dnat.pp b/puppet/modules/site_shorewall/manifests/dnat.pp
new file mode 100644
index 00000000..a73294cc
--- /dev/null
+++ b/puppet/modules/site_shorewall/manifests/dnat.pp
@@ -0,0 +1,19 @@
+define site_shorewall::dnat (
+ $source,
+ $destination,
+ $proto,
+ $destinationport,
+ $originaldest ) {
+
+
+ shorewall::rule {
+ "dnat_${name}_${destinationport}":
+ action => 'DNAT',
+ source => $source,
+ destination => $destination,
+ proto => $proto,
+ destinationport => $destinationport,
+ originaldest => $originaldest,
+ order => 200
+ }
+}
diff --git a/puppet/modules/site_shorewall/manifests/dnat_rule.pp b/puppet/modules/site_shorewall/manifests/dnat_rule.pp
new file mode 100644
index 00000000..f9fbe950
--- /dev/null
+++ b/puppet/modules/site_shorewall/manifests/dnat_rule.pp
@@ -0,0 +1,50 @@
+define site_shorewall::dnat_rule {
+
+ $port = $name
+ if $port != 1194 {
+ if $site_openvpn::openvpn_allow_unlimited {
+ shorewall::rule {
+ "dnat_tcp_port_${port}":
+ action => 'DNAT',
+ source => 'net',
+ destination => "\$FW:${site_openvpn::unlimited_gateway_address}:1194",
+ proto => 'tcp',
+ destinationport => $port,
+ originaldest => $site_openvpn::unlimited_gateway_address,
+ order => 100;
+ }
+ shorewall::rule {
+ "dnat_udp_port_${port}":
+ action => 'DNAT',
+ source => 'net',
+ destination => "\$FW:${site_openvpn::unlimited_gateway_address}:1194",
+ proto => 'udp',
+ destinationport => $port,
+ originaldest => $site_openvpn::unlimited_gateway_address,
+ order => 100;
+ }
+ }
+ if $site_openvpn::openvpn_allow_limited {
+ shorewall::rule {
+ "dnat_free_tcp_port_${port}":
+ action => 'DNAT',
+ source => 'net',
+ destination => "\$FW:${site_openvpn::limited_gateway_address}:1194",
+ proto => 'tcp',
+ destinationport => $port,
+ originaldest => $site_openvpn::unlimited_gateway_address,
+ order => 100;
+ }
+ shorewall::rule {
+ "dnat_free_udp_port_${port}":
+ action => 'DNAT',
+ source => 'net',
+ destination => "\$FW:${site_openvpn::limited_gateway_address}:1194",
+ proto => 'udp',
+ destinationport => $port,
+ originaldest => $site_openvpn::unlimited_gateway_address,
+ order => 100;
+ }
+ }
+ }
+}
diff --git a/puppet/modules/site_shorewall/manifests/eip.pp b/puppet/modules/site_shorewall/manifests/eip.pp
new file mode 100644
index 00000000..8fbba658
--- /dev/null
+++ b/puppet/modules/site_shorewall/manifests/eip.pp
@@ -0,0 +1,92 @@
+class site_shorewall::eip {
+
+ include site_shorewall::defaults
+ include site_config::params
+ include site_shorewall::ip_forward
+
+ # define macro for incoming services
+ file { '/etc/shorewall/macro.leap_eip':
+ content => "PARAM - - tcp 1194
+ PARAM - - udp 1194
+ ",
+ notify => Service['shorewall'],
+ require => Package['shorewall']
+ }
+
+ shorewall::interface {
+ 'tun0':
+ zone => 'eip',
+ options => 'tcpflags,blacklist,nosmurfs';
+ 'tun1':
+ zone => 'eip',
+ options => 'tcpflags,blacklist,nosmurfs';
+ 'tun2':
+ zone => 'eip',
+ options => 'tcpflags,blacklist,nosmurfs';
+ 'tun3':
+ zone => 'eip',
+ options => 'tcpflags,blacklist,nosmurfs';
+ }
+
+ shorewall::zone {
+ 'eip':
+ type => 'ipv4';
+ }
+
+ $interface = $site_config::params::interface
+
+ shorewall::masq {
+ "${interface}_unlimited_tcp":
+ interface => $interface,
+ source => "${site_openvpn::openvpn_unlimited_tcp_network_prefix}.0/${site_openvpn::openvpn_unlimited_tcp_cidr}";
+ "${interface}_unlimited_udp":
+ interface => $interface,
+ source => "${site_openvpn::openvpn_unlimited_udp_network_prefix}.0/${site_openvpn::openvpn_unlimited_udp_cidr}";
+ }
+ if ! $::ec2_instance_id {
+ shorewall::masq {
+ "${interface}_limited_tcp":
+ interface => $interface,
+ source => "${site_openvpn::openvpn_limited_tcp_network_prefix}.0/${site_openvpn::openvpn_limited_tcp_cidr}";
+ "${interface}_limited_udp":
+ interface => $interface,
+ source => "${site_openvpn::openvpn_limited_udp_network_prefix}.0/${site_openvpn::openvpn_limited_udp_cidr}";
+ }
+ }
+
+ shorewall::policy {
+ 'eip-to-all':
+ sourcezone => 'eip',
+ destinationzone => 'all',
+ policy => 'ACCEPT',
+ order => 100;
+ }
+
+ shorewall::rule {
+ 'net2fw-openvpn':
+ source => 'net',
+ destination => '$FW',
+ action => 'leap_eip(ACCEPT)',
+ order => 200;
+
+ 'block_eip_dns_udp':
+ action => 'REJECT',
+ source => 'eip',
+ destination => 'net',
+ proto => 'udp',
+ destinationport => 'domain',
+ order => 300;
+
+ 'block_eip_dns_tcp':
+ action => 'REJECT',
+ source => 'eip',
+ destination => 'net',
+ proto => 'tcp',
+ destinationport => 'domain',
+ order => 301;
+ }
+
+ # create dnat rule for each port
+ site_shorewall::dnat_rule { $site_openvpn::openvpn_ports: }
+
+}
diff --git a/puppet/modules/site_shorewall/manifests/ip_forward.pp b/puppet/modules/site_shorewall/manifests/ip_forward.pp
new file mode 100644
index 00000000..d53ee8a5
--- /dev/null
+++ b/puppet/modules/site_shorewall/manifests/ip_forward.pp
@@ -0,0 +1,10 @@
+class site_shorewall::ip_forward {
+ include augeas
+ augeas { 'enable_ip_forwarding':
+ changes => 'set /files/etc/shorewall/shorewall.conf/IP_FORWARDING Yes',
+ lens => 'Shellvars.lns',
+ incl => '/etc/shorewall/shorewall.conf',
+ notify => Service[shorewall],
+ require => [ Class[augeas], Package[shorewall] ];
+ }
+}
diff --git a/puppet/modules/site_shorewall/manifests/monitor.pp b/puppet/modules/site_shorewall/manifests/monitor.pp
new file mode 100644
index 00000000..f4ed4f7c
--- /dev/null
+++ b/puppet/modules/site_shorewall/manifests/monitor.pp
@@ -0,0 +1,8 @@
+class site_shorewall::monitor {
+
+ include site_shorewall::defaults
+ include site_shorewall::service::http
+ include site_shorewall::service::https
+
+
+}
diff --git a/puppet/modules/site_shorewall/manifests/mx.pp b/puppet/modules/site_shorewall/manifests/mx.pp
new file mode 100644
index 00000000..332f164e
--- /dev/null
+++ b/puppet/modules/site_shorewall/manifests/mx.pp
@@ -0,0 +1,24 @@
+class site_shorewall::mx {
+
+ include site_shorewall::defaults
+
+ $smtpd_ports = '25,465,587'
+
+ # define macro for incoming services
+ file { '/etc/shorewall/macro.leap_mx':
+ content => "PARAM - - tcp ${smtpd_ports} ",
+ notify => Service['shorewall'],
+ require => Package['shorewall']
+ }
+
+
+ shorewall::rule {
+ 'net2fw-mx':
+ source => 'net',
+ destination => '$FW',
+ action => 'leap_mx(ACCEPT)',
+ order => 200;
+ }
+
+ include site_shorewall::service::smtp
+}
diff --git a/puppet/modules/site_shorewall/manifests/obfsproxy.pp b/puppet/modules/site_shorewall/manifests/obfsproxy.pp
new file mode 100644
index 00000000..75846705
--- /dev/null
+++ b/puppet/modules/site_shorewall/manifests/obfsproxy.pp
@@ -0,0 +1,25 @@
+# configure shorewell for obfsproxy
+class site_shorewall::obfsproxy {
+
+ include site_shorewall::defaults
+
+ $obfsproxy = hiera('obfsproxy')
+ $scramblesuit = $obfsproxy['scramblesuit']
+ $scram_port = $scramblesuit['port']
+
+ # define macro for incoming services
+ file { '/etc/shorewall/macro.leap_obfsproxy':
+ content => "PARAM - - tcp ${scram_port} ",
+ notify => Service['shorewall'],
+ require => Package['shorewall']
+ }
+
+ shorewall::rule {
+ 'net2fw-obfs':
+ source => 'net',
+ destination => '$FW',
+ action => 'leap_obfsproxy(ACCEPT)',
+ order => 200;
+ }
+
+}
diff --git a/puppet/modules/site_shorewall/manifests/service/http.pp b/puppet/modules/site_shorewall/manifests/service/http.pp
new file mode 100644
index 00000000..74b874d5
--- /dev/null
+++ b/puppet/modules/site_shorewall/manifests/service/http.pp
@@ -0,0 +1,13 @@
+class site_shorewall::service::http {
+
+ include site_shorewall::defaults
+
+ shorewall::rule {
+ 'net2fw-http':
+ source => 'net',
+ destination => '$FW',
+ action => 'HTTP(ACCEPT)',
+ order => 200;
+ }
+
+}
diff --git a/puppet/modules/site_shorewall/manifests/service/https.pp b/puppet/modules/site_shorewall/manifests/service/https.pp
new file mode 100644
index 00000000..4a8b119c
--- /dev/null
+++ b/puppet/modules/site_shorewall/manifests/service/https.pp
@@ -0,0 +1,12 @@
+class site_shorewall::service::https {
+
+ include site_shorewall::defaults
+
+ shorewall::rule {
+ 'net2fw-https':
+ source => 'net',
+ destination => '$FW',
+ action => 'HTTPS(ACCEPT)',
+ order => 200;
+ }
+}
diff --git a/puppet/modules/site_shorewall/manifests/service/smtp.pp b/puppet/modules/site_shorewall/manifests/service/smtp.pp
new file mode 100644
index 00000000..7fbdf14e
--- /dev/null
+++ b/puppet/modules/site_shorewall/manifests/service/smtp.pp
@@ -0,0 +1,13 @@
+class site_shorewall::service::smtp {
+
+ include site_shorewall::defaults
+
+ shorewall::rule {
+ 'fw2net-http':
+ source => '$FW',
+ destination => 'net',
+ action => 'SMTP(ACCEPT)',
+ order => 200;
+ }
+
+}
diff --git a/puppet/modules/site_shorewall/manifests/service/webapp_api.pp b/puppet/modules/site_shorewall/manifests/service/webapp_api.pp
new file mode 100644
index 00000000..d3a1aeed
--- /dev/null
+++ b/puppet/modules/site_shorewall/manifests/service/webapp_api.pp
@@ -0,0 +1,23 @@
+# configure shorewall for webapp api
+class site_shorewall::service::webapp_api {
+
+ $api = hiera('api')
+ $api_port = $api['port']
+
+ # define macro for incoming services
+ file { '/etc/shorewall/macro.leap_webapp_api':
+ content => "PARAM - - tcp ${api_port} ",
+ notify => Service['shorewall'],
+ require => Package['shorewall']
+ }
+
+
+ shorewall::rule {
+ 'net2fw-webapp_api':
+ source => 'net',
+ destination => '$FW',
+ action => 'leap_webapp_api(ACCEPT)',
+ order => 200;
+ }
+
+}
diff --git a/puppet/modules/site_shorewall/manifests/soledad.pp b/puppet/modules/site_shorewall/manifests/soledad.pp
new file mode 100644
index 00000000..518d8689
--- /dev/null
+++ b/puppet/modules/site_shorewall/manifests/soledad.pp
@@ -0,0 +1,23 @@
+class site_shorewall::soledad {
+
+ $soledad = hiera('soledad')
+ $soledad_port = $soledad['port']
+
+ include site_shorewall::defaults
+
+ # define macro for incoming services
+ file { '/etc/shorewall/macro.leap_soledad':
+ content => "PARAM - - tcp ${soledad_port}",
+ notify => Service['shorewall'],
+ require => Package['shorewall']
+ }
+
+ shorewall::rule {
+ 'net2fw-soledad':
+ source => 'net',
+ destination => '$FW',
+ action => 'leap_soledad(ACCEPT)',
+ order => 200;
+ }
+}
+
diff --git a/puppet/modules/site_shorewall/manifests/sshd.pp b/puppet/modules/site_shorewall/manifests/sshd.pp
new file mode 100644
index 00000000..e2332592
--- /dev/null
+++ b/puppet/modules/site_shorewall/manifests/sshd.pp
@@ -0,0 +1,31 @@
+# configure shorewall for sshd
+class site_shorewall::sshd {
+
+ $ssh_config = hiera('ssh')
+ $ssh_port = $ssh_config['port']
+
+ include shorewall
+
+ # define macro for incoming sshd
+ file { '/etc/shorewall/macro.leap_sshd':
+ content => "PARAM - - tcp ${ssh_port}",
+ notify => Service['shorewall'],
+ require => Package['shorewall']
+ }
+
+
+ shorewall::rule {
+ # outside to server
+ 'net2fw-ssh':
+ source => 'net',
+ destination => '$FW',
+ action => 'leap_sshd(ACCEPT)',
+ order => 200;
+ }
+
+ # setup a routestopped rule to allow ssh when shorewall is stopped
+ shorewall::routestopped { $site_config::params::interface:
+ options => "- tcp ${ssh_port}"
+ }
+
+}
diff --git a/puppet/modules/site_shorewall/manifests/stunnel/client.pp b/puppet/modules/site_shorewall/manifests/stunnel/client.pp
new file mode 100644
index 00000000..9a89a244
--- /dev/null
+++ b/puppet/modules/site_shorewall/manifests/stunnel/client.pp
@@ -0,0 +1,40 @@
+#
+# Adds some firewall magic to the stunnel.
+#
+# Using DNAT, this firewall rule allow a locally running program
+# to try to connect to the normal remote IP and remote port of the
+# service on another machine, but have this connection magically
+# routed through the locally running stunnel client.
+#
+# The network looks like this:
+#
+# From the client's perspective:
+#
+# |------- stunnel client --------------| |---------- stunnel server -----------------------|
+# consumer app -> localhost:accept_port -> connect:connect_port -> localhost:original_port
+#
+# From the server's perspective:
+#
+# |------- stunnel client --------------| |---------- stunnel server -----------------------|
+# ?? -> *:accept_port -> localhost:connect_port -> service
+#
+
+define site_shorewall::stunnel::client(
+ $accept_port,
+ $connect,
+ $connect_port,
+ $original_port) {
+
+ include site_shorewall::defaults
+
+ shorewall::rule {
+ "stunnel_dnat_${name}":
+ action => 'DNAT',
+ source => '$FW',
+ destination => "\$FW:127.0.0.1:${accept_port}",
+ proto => 'tcp',
+ destinationport => $original_port,
+ originaldest => $connect,
+ order => 200
+ }
+}
diff --git a/puppet/modules/site_shorewall/manifests/stunnel/server.pp b/puppet/modules/site_shorewall/manifests/stunnel/server.pp
new file mode 100644
index 00000000..798cd631
--- /dev/null
+++ b/puppet/modules/site_shorewall/manifests/stunnel/server.pp
@@ -0,0 +1,22 @@
+#
+# Allow all incoming connections to stunnel server port
+#
+
+define site_shorewall::stunnel::server($port) {
+
+ include site_shorewall::defaults
+
+ file { "/etc/shorewall/macro.stunnel_server_${name}":
+ content => "PARAM - - tcp ${port}",
+ notify => Service['shorewall'],
+ require => Package['shorewall']
+ }
+ shorewall::rule {
+ "net2fw-stunnel-server-${name}":
+ source => 'net',
+ destination => '$FW',
+ action => "stunnel_server_${name}(ACCEPT)",
+ order => 200;
+ }
+
+} \ No newline at end of file
diff --git a/puppet/modules/site_shorewall/manifests/tor.pp b/puppet/modules/site_shorewall/manifests/tor.pp
new file mode 100644
index 00000000..324b4844
--- /dev/null
+++ b/puppet/modules/site_shorewall/manifests/tor.pp
@@ -0,0 +1,26 @@
+# configure shorewall for tor
+class site_shorewall::tor {
+
+ include site_shorewall::defaults
+ include site_shorewall::ip_forward
+
+ $tor_port = '9001'
+
+ # define macro for incoming services
+ file { '/etc/shorewall/macro.leap_tor':
+ content => "PARAM - - tcp ${tor_port} ",
+ notify => Service['shorewall'],
+ require => Package['shorewall']
+ }
+
+
+ shorewall::rule {
+ 'net2fw-tor':
+ source => 'net',
+ destination => '$FW',
+ action => 'leap_tor(ACCEPT)',
+ order => 200;
+ }
+
+ include site_shorewall::service::http
+}
diff --git a/puppet/modules/site_shorewall/manifests/webapp.pp b/puppet/modules/site_shorewall/manifests/webapp.pp
new file mode 100644
index 00000000..a8d2aa5b
--- /dev/null
+++ b/puppet/modules/site_shorewall/manifests/webapp.pp
@@ -0,0 +1,7 @@
+class site_shorewall::webapp {
+
+ include site_shorewall::defaults
+ include site_shorewall::service::https
+ include site_shorewall::service::http
+ include site_shorewall::service::webapp_api
+}
diff --git a/puppet/modules/site_squid_deb_proxy/manifests/client.pp b/puppet/modules/site_squid_deb_proxy/manifests/client.pp
new file mode 100644
index 00000000..27844270
--- /dev/null
+++ b/puppet/modules/site_squid_deb_proxy/manifests/client.pp
@@ -0,0 +1,5 @@
+class site_squid_deb_proxy::client {
+ include squid_deb_proxy::client
+ include site_shorewall::defaults
+ include shorewall::rules::mdns
+}
diff --git a/puppet/modules/site_sshd/manifests/authorized_keys.pp b/puppet/modules/site_sshd/manifests/authorized_keys.pp
new file mode 100644
index 00000000..a1fde3f6
--- /dev/null
+++ b/puppet/modules/site_sshd/manifests/authorized_keys.pp
@@ -0,0 +1,34 @@
+# We want to purge unmanaged keys from the authorized_keys file so that only
+# keys added in the provider are valid. Any manually added keys will be
+# overridden.
+#
+# In order to do this, we have to use a custom define to deploy the
+# authorized_keys file because puppet's internal resource doesn't allow
+# purging before populating this file.
+#
+# See the following for more information:
+# https://tickets.puppetlabs.com/browse/PUP-1174
+# https://leap.se/code/issues/2990
+# https://leap.se/code/issues/3010
+#
+define site_sshd::authorized_keys ($keys, $ensure = 'present', $home = '') {
+ # This line allows default homedir based on $title variable.
+ # If $home is empty, the default is used.
+ $homedir = $home ? {'' => "/home/${title}", default => $home}
+ $owner = $ensure ? {'present' => $title, default => undef }
+ $group = $ensure ? {'present' => $title, default => undef }
+ file {
+ "${homedir}/.ssh":
+ ensure => 'directory',
+ owner => $title,
+ group => $title,
+ mode => '0700';
+ "${homedir}/.ssh/authorized_keys":
+ ensure => $ensure,
+ owner => $owner,
+ group => $group,
+ mode => '0600',
+ require => File["${homedir}/.ssh"],
+ content => template('site_sshd/authorized_keys.erb');
+ }
+}
diff --git a/puppet/modules/site_sshd/manifests/deploy_authorized_keys.pp b/puppet/modules/site_sshd/manifests/deploy_authorized_keys.pp
new file mode 100644
index 00000000..97ca058f
--- /dev/null
+++ b/puppet/modules/site_sshd/manifests/deploy_authorized_keys.pp
@@ -0,0 +1,9 @@
+class site_sshd::deploy_authorized_keys ( $keys ) {
+ tag 'leap_authorized_keys'
+
+ site_sshd::authorized_keys {'root':
+ keys => $keys,
+ home => '/root'
+ }
+
+}
diff --git a/puppet/modules/site_sshd/manifests/init.pp b/puppet/modules/site_sshd/manifests/init.pp
new file mode 100644
index 00000000..a9202da4
--- /dev/null
+++ b/puppet/modules/site_sshd/manifests/init.pp
@@ -0,0 +1,82 @@
+# configures sshd, mosh, authorized keys and known hosts
+class site_sshd {
+ $ssh = hiera_hash('ssh')
+ $ssh_config = $ssh['config']
+ $hosts = hiera('hosts', '')
+
+ ##
+ ## SETUP AUTHORIZED KEYS
+ ##
+
+ $authorized_keys = $ssh['authorized_keys']
+
+ class { 'site_sshd::deploy_authorized_keys':
+ keys => $authorized_keys
+ }
+
+ ##
+ ## SETUP KNOWN HOSTS and SSH_CONFIG
+ ##
+
+ file {
+ '/etc/ssh/ssh_known_hosts':
+ owner => root,
+ group => root,
+ mode => '0644',
+ content => template('site_sshd/ssh_known_hosts.erb');
+
+ '/etc/ssh/ssh_config':
+ owner => root,
+ group => root,
+ mode => '0644',
+ content => template('site_sshd/ssh_config.erb');
+ }
+
+ ##
+ ## OPTIONAL MOSH SUPPORT
+ ##
+
+ $mosh = $ssh['mosh']
+
+ if $mosh['enabled'] {
+ class { 'site_sshd::mosh':
+ ensure => present,
+ ports => $mosh['ports']
+ }
+ }
+ else {
+ class { 'site_sshd::mosh':
+ ensure => absent
+ }
+ }
+
+ # we cannot use the 'hardened' parameter because leap_cli uses an
+ # old net-ssh gem that is incompatible with the included
+ # "KexAlgorithms curve25519-sha256@libssh.org",
+ # see https://leap.se/code/issues/7591
+ # therefore we don't use it here, but include all other options
+ # that would be applied by the 'hardened' parameter
+ # not all options are available on wheezy
+ if ( $::lsbdistcodename == 'wheezy' ) {
+ $tail_additional_options = 'Ciphers aes256-ctr
+MACs hmac-sha2-512,hmac-sha2-256,hmac-ripemd160'
+ } else {
+ $tail_additional_options = 'Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr
+MACs hmac-sha2-512,hmac-sha2-256,hmac-ripemd160'
+ }
+
+ ##
+ ## SSHD SERVER CONFIGURATION
+ ##
+ class { '::sshd':
+ manage_nagios => false,
+ ports => [ $ssh['port'] ],
+ use_pam => 'yes',
+ print_motd => 'no',
+ tcp_forwarding => $ssh_config['AllowTcpForwarding'],
+ manage_client => false,
+ use_storedconfigs => false,
+ tail_additional_options => $tail_additional_options,
+ hostkey_type => [ 'rsa', 'dsa', 'ecdsa' ]
+ }
+}
diff --git a/puppet/modules/site_sshd/manifests/mosh.pp b/puppet/modules/site_sshd/manifests/mosh.pp
new file mode 100644
index 00000000..49f56ca0
--- /dev/null
+++ b/puppet/modules/site_sshd/manifests/mosh.pp
@@ -0,0 +1,21 @@
+class site_sshd::mosh ( $ensure = present, $ports = '60000-61000' ) {
+
+ package { 'mosh':
+ ensure => $ensure
+ }
+
+ file { '/etc/shorewall/macro.mosh':
+ ensure => $ensure,
+ content => "PARAM - - udp ${ports}",
+ notify => Service['shorewall'],
+ require => Package['shorewall'];
+ }
+
+ shorewall::rule { 'net2fw-mosh':
+ ensure => $ensure,
+ source => 'net',
+ destination => '$FW',
+ action => 'mosh(ACCEPT)',
+ order => 200;
+ }
+}
diff --git a/puppet/modules/site_sshd/templates/authorized_keys.erb b/puppet/modules/site_sshd/templates/authorized_keys.erb
new file mode 100644
index 00000000..51bdc5b3
--- /dev/null
+++ b/puppet/modules/site_sshd/templates/authorized_keys.erb
@@ -0,0 +1,10 @@
+# NOTICE: This file is autogenerated by Puppet
+# all manually added keys will be overridden
+
+<% @keys.sort.each do |user, hash| -%>
+<% if user == 'monitor' -%>
+command="/usr/bin/check_mk_agent",no-port-forwarding,no-x11-forwarding,no-agent-forwarding,no-pty,no-user-rc, <%=hash['type']-%> <%=hash['key']%> <%=user%>
+<% else -%>
+<%=hash['type']-%> <%=hash['key']%> <%=user%>
+<% end -%>
+<% end -%>
diff --git a/puppet/modules/site_sshd/templates/ssh_config.erb b/puppet/modules/site_sshd/templates/ssh_config.erb
new file mode 100644
index 00000000..36c0b6d5
--- /dev/null
+++ b/puppet/modules/site_sshd/templates/ssh_config.erb
@@ -0,0 +1,40 @@
+# This file is generated by Puppet
+# This is the ssh client system-wide configuration file. See
+# ssh_config(5) for more information. This file provides defaults for
+# users, and the values can be changed in per-user configuration files
+# or on the command line.
+
+Host *
+ SendEnv LANG LC_*
+ HashKnownHosts yes
+ GSSAPIAuthentication yes
+ GSSAPIDelegateCredentials no
+<% if scope.lookupvar('::site_config::params::environment') == 'local' -%>
+ #
+ # Vagrant nodes should have strict host key checking
+ # turned off. The problem is that the host key for a vagrant
+ # node is specific to the particular instance of the vagrant
+ # node you have running locally. For this reason, we can't
+ # track the host keys, or your host key for vpn1 would conflict
+ # with my host key for vpn1.
+ #
+ StrictHostKeyChecking no
+<% end -%>
+
+#
+# Tell SSH what host key algorithm we should use. I don't understand why this
+# is needed, since the man page says that "if hostkeys are known for the
+# destination host then [HostKeyAlgorithms default] is modified to prefer
+# their algorithms."
+#
+
+<% @hosts.sort.each do |name, host| -%>
+Host <%= name %> <%= host['domain_full'] %> <%= host['domain_internal'] %> <%= host['ip_address'] %>
+<% if host['host_pub_key'] -%>
+HostKeyAlgorithms <%= host['host_pub_key'].split(" ").first %>
+<% end -%>
+<% if host['port'] -%>
+Port <%= host['port'] %>
+<% end -%>
+
+<% end -%>
diff --git a/puppet/modules/site_sshd/templates/ssh_known_hosts.erb b/puppet/modules/site_sshd/templates/ssh_known_hosts.erb
new file mode 100644
index 00000000..002ab732
--- /dev/null
+++ b/puppet/modules/site_sshd/templates/ssh_known_hosts.erb
@@ -0,0 +1,7 @@
+# This file is generated by Puppet
+
+<% @hosts.sort.each do |name, hash| -%>
+<% if hash['host_pub_key'] -%>
+<%= name%>,<%=hash['domain_full']%>,<%=hash['domain_internal']%>,<%=hash['ip_address']%> <%=hash['host_pub_key']%>
+<% end -%>
+<% end -%>
diff --git a/puppet/modules/site_static/README b/puppet/modules/site_static/README
new file mode 100644
index 00000000..bc719782
--- /dev/null
+++ b/puppet/modules/site_static/README
@@ -0,0 +1,3 @@
+Deploy one or more static websites to a node.
+
+For now, it only supports `amber` based static sites. Should support plain html and jekyll in the future.
diff --git a/puppet/modules/site_static/manifests/domain.pp b/puppet/modules/site_static/manifests/domain.pp
new file mode 100644
index 00000000..b26cc9e3
--- /dev/null
+++ b/puppet/modules/site_static/manifests/domain.pp
@@ -0,0 +1,33 @@
+# configure static service for domain
+define site_static::domain (
+ $ca_cert,
+ $key,
+ $cert,
+ $tls_only=true,
+ $locations=undef,
+ $aliases=undef,
+ $apache_config=undef) {
+
+ $domain = $name
+ $base_dir = '/srv/static'
+
+ $cafile = "${cert}\n${ca_cert}"
+
+ if is_hash($locations) {
+ create_resources(site_static::location, $locations)
+ }
+
+ x509::cert { $domain:
+ content => $cafile,
+ notify => Service[apache]
+ }
+ x509::key { $domain:
+ content => $key,
+ notify => Service[apache]
+ }
+
+ apache::vhost::file { $domain:
+ content => template('site_static/apache.conf.erb')
+ }
+
+}
diff --git a/puppet/modules/site_static/manifests/init.pp b/puppet/modules/site_static/manifests/init.pp
new file mode 100644
index 00000000..4a722d62
--- /dev/null
+++ b/puppet/modules/site_static/manifests/init.pp
@@ -0,0 +1,72 @@
+# deploy static service
+class site_static {
+ tag 'leap_service'
+
+ include site_config::default
+ include site_config::x509::cert
+ include site_config::x509::key
+ include site_config::x509::ca_bundle
+
+ $static = hiera('static')
+ $domains = $static['domains']
+ $formats = $static['formats']
+ $bootstrap = $static['bootstrap_files']
+ $tor = hiera('tor', false)
+
+ if $bootstrap['enabled'] {
+ $bootstrap_domain = $bootstrap['domain']
+ $bootstrap_client = $bootstrap['client_version']
+ file { '/srv/leap/provider.json':
+ content => $bootstrap['provider_json'],
+ owner => 'www-data',
+ group => 'www-data',
+ mode => '0444';
+ }
+ # It is important to always touch provider.json: the client needs to check x-min-client-version header,
+ # but this is only sent when the file has been modified (otherwise 304 is sent by apache). The problem
+ # is that changing min client version won't alter the content of provider.json, so we must touch it.
+ exec { '/bin/touch /srv/leap/provider.json':
+ require => File['/srv/leap/provider.json'];
+ }
+ }
+
+ include apache::module::headers
+ include apache::module::alias
+ include apache::module::expires
+ include apache::module::removeip
+ include apache::module::dir
+ include apache::module::negotiation
+ include site_apache::common
+ include site_config::ruby::dev
+
+ if (member($formats, 'rack')) {
+ include site_apt::preferences::passenger
+ class { 'passenger':
+ use_munin => false,
+ require => Class['site_apt::preferences::passenger']
+ }
+ }
+
+ if (member($formats, 'amber')) {
+ rubygems::gem{'amber-0.3.8':
+ require => Package['zlib1g-dev']
+ }
+
+ package { 'zlib1g-dev':
+ ensure => installed
+ }
+ }
+
+ create_resources(site_static::domain, $domains)
+
+ if $tor {
+ $hidden_service = $tor['hidden_service']
+ if $hidden_service['active'] {
+ include site_webapp::hidden_service
+ }
+ }
+
+ include site_shorewall::defaults
+ include site_shorewall::service::http
+ include site_shorewall::service::https
+}
diff --git a/puppet/modules/site_static/manifests/location.pp b/puppet/modules/site_static/manifests/location.pp
new file mode 100644
index 00000000..d116de2f
--- /dev/null
+++ b/puppet/modules/site_static/manifests/location.pp
@@ -0,0 +1,36 @@
+# configure static service for location
+define site_static::location($path, $format, $source) {
+
+ $file_path = "/srv/static/${name}"
+ $allowed_formats = ['amber','rack']
+
+ if $format == undef {
+ fail("static_site location `${path}` is missing `format` field.")
+ }
+
+ if ! member($allowed_formats, $format) {
+ $formats_str = join($allowed_formats, ', ')
+ fail("Unsupported static_site location format `${format}`. Supported formats include ${formats_str}.")
+ }
+
+ if ($format == 'amber') {
+ exec {"amber_build_${name}":
+ cwd => $file_path,
+ command => 'amber rebuild',
+ user => 'www-data',
+ timeout => 600,
+ subscribe => Vcsrepo[$file_path]
+ }
+ }
+
+ vcsrepo { $file_path:
+ ensure => present,
+ force => true,
+ revision => $source['revision'],
+ provider => $source['type'],
+ source => $source['repo'],
+ owner => 'www-data',
+ group => 'www-data'
+ }
+
+}
diff --git a/puppet/modules/site_static/templates/amber.erb b/puppet/modules/site_static/templates/amber.erb
new file mode 100644
index 00000000..694f1136
--- /dev/null
+++ b/puppet/modules/site_static/templates/amber.erb
@@ -0,0 +1,13 @@
+<%- if @location_path != '' -%>
+ AliasMatch ^/[a-z]{2}/<%=@location_path%>(/.+|/|)$ "<%=@directory%>/$1"
+ Alias /<%=@location_path%> "<%=@directory%>/"
+<%- end -%>
+ <Directory "<%=@directory%>/">
+ AllowOverride FileInfo Indexes Options=All,MultiViews
+<% if scope.function_guess_apache_version([]) == '2.4' %>
+ Require all granted
+<% else %>
+ Order deny,allow
+ Allow from all
+<% end %>
+ </Directory>
diff --git a/puppet/modules/site_static/templates/apache.conf.erb b/puppet/modules/site_static/templates/apache.conf.erb
new file mode 100644
index 00000000..6b969d1c
--- /dev/null
+++ b/puppet/modules/site_static/templates/apache.conf.erb
@@ -0,0 +1,88 @@
+<%-
+ ##
+ ## An apache config for static websites.
+ ##
+
+ def location_directory(name, location)
+ if ['amber', 'rack'].include?(location['format'])
+ File.join(@base_dir, name, 'public')
+ else
+ File.join(@base_dir, name)
+ end
+ end
+
+ @document_root = begin
+ root = '/var/www'
+ @locations && @locations.each do |name, location|
+ root = location_directory(name, location) if location['path'] == '/'
+ end
+ root.gsub(%r{^/|/$}, '')
+ end
+
+ bootstrap_domain = scope.lookupvar('site_static::bootstrap_domain')
+ bootstrap_client = scope.lookupvar('site_static::bootstrap_client')
+-%>
+
+<VirtualHost *:80>
+ ServerName <%= @domain %>
+ ServerAlias www.<%= @domain %>
+<%- @aliases && @aliases.each do |domain_alias| -%>
+ ServerAlias <%= domain_alias %>
+<%- end -%>
+<%- if @tls_only -%>
+ RewriteEngine On
+ RewriteRule ^.*$ https://<%= @domain -%>%{REQUEST_URI} [R=permanent,L]
+<%- end -%>
+</VirtualHost>
+
+<VirtualHost *:443>
+ ServerName <%= @domain %>
+ ServerAlias www.<%= @domain %>
+<%- @aliases && @aliases.each do |domain_alias| -%>
+ ServerAlias <%= domain_alias %>
+<%- end -%>
+
+ #RewriteLog "/var/log/apache2/rewrite.log"
+ #RewriteLogLevel 3
+
+ Include include.d/ssl_common.inc
+
+<%- if @tls_only -%>
+ Header always set Strict-Transport-Security: "max-age=15768000;includeSubdomains"
+<%- end -%>
+ Header set X-Frame-Options "deny"
+ Header always unset X-Powered-By
+ Header always unset X-Runtime
+
+ SSLCertificateKeyFile /etc/x509/keys/<%= @domain %>.key
+ SSLCertificateFile /etc/x509/certs/<%= @domain %>.crt
+
+ RequestHeader set X_FORWARDED_PROTO 'https'
+
+ DocumentRoot "/<%= @document_root %>/"
+ AccessFileName .htaccess
+
+<%- if ([@aliases]+[@domain]).flatten.include?(bootstrap_domain) -%>
+ Alias /provider.json /srv/leap/provider.json
+ <Location /provider.json>
+ Header set X-Minimum-Client-Version <%= bootstrap_client['min'] %>
+ </Location>
+<%- end -%>
+
+<%- if @apache_config -%>
+<%= @apache_config.gsub(':percent:','%') %>
+<%- end -%>
+
+<%- @locations && @locations.each do |name, location| -%>
+<%- location_path = location['path'].gsub(%r{^/|/$}, '') -%>
+<%- directory = location_directory(name, location) -%>
+<%- local_vars = {'location_path'=>location_path, 'directory'=>directory, 'location'=>location, 'name'=>name} -%>
+<%- template_path = File.join(File.dirname(__FILE__), location['format']) + '.erb' -%>
+<%- break unless File.exists?(template_path) -%>
+ ##
+ ## <%= name %> (<%= location['format'] %>)
+ ##
+<%= scope.function_templatewlv([template_path, local_vars]) %>
+<%- end -%>
+
+</VirtualHost>
diff --git a/puppet/modules/site_static/templates/rack.erb b/puppet/modules/site_static/templates/rack.erb
new file mode 100644
index 00000000..431778bb
--- /dev/null
+++ b/puppet/modules/site_static/templates/rack.erb
@@ -0,0 +1,19 @@
+ #PassengerLogLevel 1
+ #PassengerAppEnv production
+ #PassengerFriendlyErrorPages on
+<%- if @location_path != '' -%>
+ Alias /<%=@location_path%> "<%=@directory%>"
+ <Location /<%=@location_path%>>
+ PassengerBaseURI /<%=@location_path%>
+ PassengerAppRoot "<%=File.dirname(@directory)%>"
+ </Location>
+<%- end -%>
+ <Directory "<%=@directory%>">
+ Options -MultiViews
+<% if scope.function_guess_apache_version([]) == '2.4' %>
+ Require all granted
+<% else %>
+ Order deny,allow
+ Allow from all
+<% end %>
+ </Directory>
diff --git a/puppet/modules/site_stunnel/manifests/client.pp b/puppet/modules/site_stunnel/manifests/client.pp
new file mode 100644
index 00000000..c9e034f1
--- /dev/null
+++ b/puppet/modules/site_stunnel/manifests/client.pp
@@ -0,0 +1,64 @@
+#
+# Sets up stunnel and firewall configuration for
+# a single stunnel client
+#
+# As a client, we accept connections on localhost,
+# and connect to a remote $connect:$connect_port
+#
+
+define site_stunnel::client (
+ $accept_port,
+ $connect_port,
+ $connect,
+ $original_port,
+ $verify = '2',
+ $pid = $name,
+ $rndfile = '/var/lib/stunnel4/.rnd',
+ $debuglevel = 'warning' ) {
+
+ $logfile = "/var/log/stunnel4/${name}.log"
+
+ include site_config::x509::cert
+ include site_config::x509::key
+ include site_config::x509::ca
+ include x509::variables
+ $ca_path = "${x509::variables::local_CAs}/${site_config::params::ca_name}.crt"
+ $cert_path = "${x509::variables::certs}/${site_config::params::cert_name}.crt"
+ $key_path = "${x509::variables::keys}/${site_config::params::cert_name}.key"
+
+ stunnel::service { $name:
+ accept => "127.0.0.1:${accept_port}",
+ connect => "${connect}:${connect_port}",
+ client => true,
+ cafile => $ca_path,
+ key => $key_path,
+ cert => $cert_path,
+ verify => $verify,
+ pid => "/var/run/stunnel4/${pid}.pid",
+ rndfile => $rndfile,
+ debuglevel => $debuglevel,
+ sslversion => 'TLSv1',
+ syslog => 'no',
+ output => $logfile;
+ }
+
+ # define the log files so that we can purge the
+ # files from /var/log/stunnel4 that are not defined.
+ file {
+ $logfile:;
+ "${logfile}.1.gz":;
+ "${logfile}.2.gz":;
+ "${logfile}.3.gz":;
+ "${logfile}.4.gz":;
+ "${logfile}.5.gz":;
+ }
+
+ site_shorewall::stunnel::client { $name:
+ accept_port => $accept_port,
+ connect => $connect,
+ connect_port => $connect_port,
+ original_port => $original_port
+ }
+
+ include site_check_mk::agent::stunnel
+}
diff --git a/puppet/modules/site_stunnel/manifests/clients.pp b/puppet/modules/site_stunnel/manifests/clients.pp
new file mode 100644
index 00000000..c0958b5f
--- /dev/null
+++ b/puppet/modules/site_stunnel/manifests/clients.pp
@@ -0,0 +1,23 @@
+#
+# example hiera yaml:
+#
+# stunnel:
+# clients:
+# ednp_clients:
+# thrips_9002:
+# accept_port: 4001
+# connect: thrips.demo.bitmask.i
+# connect_port: 19002
+# epmd_clients:
+# thrips_4369:
+# accept_port: 4000
+# connect: thrips.demo.bitmask.i
+# connect_port: 14369
+#
+# In the above example, this resource definition is called twice, with $name
+# 'ednp_clients' and 'epmd_clients'
+#
+
+define site_stunnel::clients {
+ create_resources(site_stunnel::client, $site_stunnel::clients[$name])
+}
diff --git a/puppet/modules/site_stunnel/manifests/init.pp b/puppet/modules/site_stunnel/manifests/init.pp
new file mode 100644
index 00000000..a874721f
--- /dev/null
+++ b/puppet/modules/site_stunnel/manifests/init.pp
@@ -0,0 +1,48 @@
+#
+# If you need something to happen after stunnel is started,
+# you can depend on Service['stunnel'] or Class['site_stunnel']
+#
+
+class site_stunnel {
+
+ # include the generic stunnel module
+ # increase the number of open files to allow for 800 connections
+ class { 'stunnel': default_extra => 'ulimit -n 4096' }
+
+ # The stunnel.conf provided by the Debian package is broken by default
+ # so we get rid of it and just define our own. See #549384
+ if !defined(File['/etc/stunnel/stunnel.conf']) {
+ file {
+ # this file is a broken config installed by the package
+ '/etc/stunnel/stunnel.conf':
+ ensure => absent;
+ }
+ }
+
+ $stunnel = hiera('stunnel')
+
+ # add server stunnels
+ create_resources(site_stunnel::servers, $stunnel['servers'])
+
+ # add client stunnels
+ $clients = $stunnel['clients']
+ $client_sections = keys($clients)
+ site_stunnel::clients { $client_sections: }
+
+ # remove any old stunnel logs that are not
+ # defined by this puppet run
+ file {'/var/log/stunnel4': purge => true;}
+
+ # the default is to keep 356 log files for each stunnel.
+ # here we set a more reasonable number.
+ augeas {
+ 'logrotate_stunnel':
+ context => '/files/etc/logrotate.d/stunnel4/rule',
+ changes => [
+ 'set rotate 5',
+ ]
+ }
+
+ include site_stunnel::override_service
+}
+
diff --git a/puppet/modules/site_stunnel/manifests/override_service.pp b/puppet/modules/site_stunnel/manifests/override_service.pp
new file mode 100644
index 00000000..435b9aa0
--- /dev/null
+++ b/puppet/modules/site_stunnel/manifests/override_service.pp
@@ -0,0 +1,18 @@
+# override stunnel::debian defaults
+#
+# ignore puppet lint error about inheriting from different namespace
+# lint:ignore:inherits_across_namespaces
+class site_stunnel::override_service inherits stunnel::debian {
+# lint:endignore
+
+ include site_config::x509::cert
+ include site_config::x509::key
+ include site_config::x509::ca
+
+ Service[stunnel] {
+ subscribe => [
+ Class['Site_config::X509::Key'],
+ Class['Site_config::X509::Cert'],
+ Class['Site_config::X509::Ca'] ]
+ }
+}
diff --git a/puppet/modules/site_stunnel/manifests/servers.pp b/puppet/modules/site_stunnel/manifests/servers.pp
new file mode 100644
index 00000000..e76d1e9d
--- /dev/null
+++ b/puppet/modules/site_stunnel/manifests/servers.pp
@@ -0,0 +1,51 @@
+#
+# example hiera yaml:
+#
+# stunnel:
+# servers:
+# couch_server:
+# accept_port: 15984
+# connect_port: 5984
+#
+
+define site_stunnel::servers (
+ $accept_port,
+ $connect_port,
+ $verify = '2',
+ $pid = $name,
+ $rndfile = '/var/lib/stunnel4/.rnd',
+ $debuglevel = '4' ) {
+
+ $logfile = "/var/log/stunnel4/${name}.log"
+
+ include site_config::x509::cert
+ include site_config::x509::key
+ include site_config::x509::ca
+ include x509::variables
+ $ca_path = "${x509::variables::local_CAs}/${site_config::params::ca_name}.crt"
+ $cert_path = "${x509::variables::certs}/${site_config::params::cert_name}.crt"
+ $key_path = "${x509::variables::keys}/${site_config::params::cert_name}.key"
+
+ stunnel::service { $name:
+ accept => $accept_port,
+ connect => "127.0.0.1:${connect_port}",
+ client => false,
+ cafile => $ca_path,
+ key => $key_path,
+ cert => $cert_path,
+ verify => $verify,
+ pid => "/var/run/stunnel4/${pid}.pid",
+ rndfile => '/var/lib/stunnel4/.rnd',
+ debuglevel => $debuglevel,
+ sslversion => 'TLSv1',
+ syslog => 'no',
+ output => $logfile;
+ }
+
+ # allow incoming connections on $accept_port
+ site_shorewall::stunnel::server { $name:
+ port => $accept_port
+ }
+
+ include site_check_mk::agent::stunnel
+}
diff --git a/puppet/modules/site_tor/manifests/disable_exit.pp b/puppet/modules/site_tor/manifests/disable_exit.pp
new file mode 100644
index 00000000..078f80ae
--- /dev/null
+++ b/puppet/modules/site_tor/manifests/disable_exit.pp
@@ -0,0 +1,7 @@
+class site_tor::disable_exit {
+ tor::daemon::exit_policy {
+ 'no_exit_at_all':
+ reject => [ '*:*' ];
+ }
+}
+
diff --git a/puppet/modules/site_tor/manifests/init.pp b/puppet/modules/site_tor/manifests/init.pp
new file mode 100644
index 00000000..2207a5a9
--- /dev/null
+++ b/puppet/modules/site_tor/manifests/init.pp
@@ -0,0 +1,45 @@
+class site_tor {
+ tag 'leap_service'
+ Class['site_config::default'] -> Class['site_tor']
+
+ $tor = hiera('tor')
+ $bandwidth_rate = $tor['bandwidth_rate']
+ $tor_type = $tor['type']
+ $nickname = $tor['nickname']
+ $contact_emails = join($tor['contacts'],', ')
+ $family = $tor['family']
+
+ $address = hiera('ip_address')
+
+ $openvpn = hiera('openvpn', undef)
+ if $openvpn {
+ $openvpn_ports = $openvpn['ports']
+ }
+ else {
+ $openvpn_ports = []
+ }
+
+ include site_config::default
+ include tor::daemon
+ tor::daemon::relay { $nickname:
+ port => 9001,
+ address => $address,
+ contact_info => obfuscate_email($contact_emails),
+ bandwidth_rate => $bandwidth_rate,
+ my_family => $family
+ }
+
+ if ( $tor_type == 'exit'){
+ # Only enable the daemon directory if the node isn't also a webapp node
+ # or running openvpn on port 80
+ if ! member($::services, 'webapp') and ! member($openvpn_ports, '80') {
+ tor::daemon::directory { $::hostname: port => 80 }
+ }
+ }
+ else {
+ include site_tor::disable_exit
+ }
+
+ include site_shorewall::tor
+
+}
diff --git a/puppet/modules/site_webapp/files/server-status.conf b/puppet/modules/site_webapp/files/server-status.conf
new file mode 100644
index 00000000..10b2d4ed
--- /dev/null
+++ b/puppet/modules/site_webapp/files/server-status.conf
@@ -0,0 +1,26 @@
+# Keep track of extended status information for each request
+ExtendedStatus On
+
+# Determine if mod_status displays the first 63 characters of a request or
+# the last 63, assuming the request itself is greater than 63 chars.
+# Default: Off
+#SeeRequestTail On
+
+Listen 127.0.0.1:8162
+
+<VirtualHost 127.0.0.1:8162>
+
+<Location /server-status>
+ SetHandler server-status
+ Require all granted
+ Allow from 127.0.0.1
+</Location>
+
+</VirtualHost>
+
+
+<IfModule mod_proxy.c>
+ # Show Proxy LoadBalancer status in mod_status
+ ProxyStatus On
+</IfModule>
+
diff --git a/puppet/modules/site_webapp/manifests/apache.pp b/puppet/modules/site_webapp/manifests/apache.pp
new file mode 100644
index 00000000..80c7b29b
--- /dev/null
+++ b/puppet/modules/site_webapp/manifests/apache.pp
@@ -0,0 +1,28 @@
+# configure apache and passenger to serve the webapp
+class site_webapp::apache {
+
+ $web_api = hiera('api')
+ $api_domain = $web_api['domain']
+ $api_port = $web_api['port']
+
+ $web_domain = hiera('domain')
+ $domain_name = $web_domain['name']
+
+ $webapp = hiera('webapp')
+ $webapp_domain = $webapp['domain']
+
+ include site_apache::common
+ include apache::module::headers
+ include apache::module::alias
+ include apache::module::expires
+ include apache::module::removeip
+ include site_webapp::common_vhost
+
+ class { 'passenger': use_munin => false }
+
+ apache::vhost::file {
+ 'api':
+ content => template('site_apache/vhosts.d/api.conf.erb');
+ }
+
+}
diff --git a/puppet/modules/site_webapp/manifests/common_vhost.pp b/puppet/modules/site_webapp/manifests/common_vhost.pp
new file mode 100644
index 00000000..c57aad57
--- /dev/null
+++ b/puppet/modules/site_webapp/manifests/common_vhost.pp
@@ -0,0 +1,18 @@
+class site_webapp::common_vhost {
+ # installs x509 cert + key and common config
+ # that both nagios + leap webapp use
+
+ include x509::variables
+ include site_config::x509::commercial::cert
+ include site_config::x509::commercial::key
+ include site_config::x509::commercial::ca
+
+ Class['Site_config::X509::Commercial::Key'] ~> Service[apache]
+ Class['Site_config::X509::Commercial::Cert'] ~> Service[apache]
+ Class['Site_config::X509::Commercial::Ca'] ~> Service[apache]
+
+ apache::vhost::file {
+ 'common':
+ content => template('site_apache/vhosts.d/common.conf.erb')
+ }
+}
diff --git a/puppet/modules/site_webapp/manifests/couchdb.pp b/puppet/modules/site_webapp/manifests/couchdb.pp
new file mode 100644
index 00000000..71450370
--- /dev/null
+++ b/puppet/modules/site_webapp/manifests/couchdb.pp
@@ -0,0 +1,52 @@
+class site_webapp::couchdb {
+
+ $webapp = hiera('webapp')
+ # haproxy listener on port localhost:4096, see site_webapp::haproxy
+ $couchdb_host = 'localhost'
+ $couchdb_port = '4096'
+ $couchdb_webapp_user = $webapp['couchdb_webapp_user']['username']
+ $couchdb_webapp_password = $webapp['couchdb_webapp_user']['password']
+ $couchdb_admin_user = $webapp['couchdb_admin_user']['username']
+ $couchdb_admin_password = $webapp['couchdb_admin_user']['password']
+
+ include x509::variables
+
+ file {
+ '/srv/leap/webapp/config/couchdb.yml':
+ content => template('site_webapp/couchdb.yml.erb'),
+ owner => 'leap-webapp',
+ group => 'leap-webapp',
+ mode => '0600',
+ require => Vcsrepo['/srv/leap/webapp'];
+
+ # couchdb.admin.yml is a symlink to prevent the vcsrepo resource
+ # from changing its user permissions every time.
+ '/srv/leap/webapp/config/couchdb.admin.yml':
+ ensure => 'link',
+ target => '/etc/leap/couchdb.admin.yml',
+ require => Vcsrepo['/srv/leap/webapp'];
+
+ '/etc/leap/couchdb.admin.yml':
+ content => template('site_webapp/couchdb.admin.yml.erb'),
+ owner => 'root',
+ group => 'root',
+ mode => '0600',
+ require => File['/etc/leap'];
+
+ '/srv/leap/webapp/log':
+ ensure => directory,
+ owner => 'leap-webapp',
+ group => 'leap-webapp',
+ mode => '0755',
+ require => Vcsrepo['/srv/leap/webapp'];
+
+ '/srv/leap/webapp/log/production.log':
+ ensure => present,
+ owner => 'leap-webapp',
+ group => 'leap-webapp',
+ mode => '0666',
+ require => Vcsrepo['/srv/leap/webapp'];
+ }
+
+ include site_stunnel
+}
diff --git a/puppet/modules/site_webapp/manifests/cron.pp b/puppet/modules/site_webapp/manifests/cron.pp
new file mode 100644
index 00000000..70b9da04
--- /dev/null
+++ b/puppet/modules/site_webapp/manifests/cron.pp
@@ -0,0 +1,37 @@
+# setup webapp cronjobs
+class site_webapp::cron {
+
+ # cron tasks that need to be performed to cleanup the database
+ cron {
+ 'rotate_databases':
+ command => 'cd /srv/leap/webapp && bundle exec rake db:rotate',
+ environment => 'RAILS_ENV=production',
+ user => 'root',
+ hour => [0,6,12,18],
+ minute => 0;
+
+ 'delete_tmp_databases':
+ command => 'cd /srv/leap/webapp && bundle exec rake db:deletetmp',
+ environment => 'RAILS_ENV=production',
+ user => 'root',
+ hour => 1,
+ minute => 1;
+
+ # there is no longer a need to remove expired sessions, since the database
+ # will get destroyed.
+ 'remove_expired_sessions':
+ ensure => absent,
+ command => 'cd /srv/leap/webapp && bundle exec rake cleanup:sessions',
+ environment => 'RAILS_ENV=production',
+ user => 'leap-webapp',
+ hour => 2,
+ minute => 30;
+
+ 'remove_expired_tokens':
+ command => 'cd /srv/leap/webapp && bundle exec rake cleanup:tokens',
+ environment => 'RAILS_ENV=production',
+ user => 'leap-webapp',
+ hour => 3,
+ minute => 0;
+ }
+}
diff --git a/puppet/modules/site_webapp/manifests/hidden_service.pp b/puppet/modules/site_webapp/manifests/hidden_service.pp
new file mode 100644
index 00000000..72a2ce95
--- /dev/null
+++ b/puppet/modules/site_webapp/manifests/hidden_service.pp
@@ -0,0 +1,52 @@
+class site_webapp::hidden_service {
+ $tor = hiera('tor')
+ $hidden_service = $tor['hidden_service']
+ $tor_domain = "${hidden_service['address']}.onion"
+
+ include site_apache::common
+ include apache::module::headers
+ include apache::module::alias
+ include apache::module::expires
+ include apache::module::removeip
+
+ include tor::daemon
+ tor::daemon::hidden_service { 'webapp': ports => [ '80 127.0.0.1:80'] }
+
+ file {
+ '/var/lib/tor/webapp/':
+ ensure => directory,
+ owner => 'debian-tor',
+ group => 'debian-tor',
+ mode => '2700';
+
+ '/var/lib/tor/webapp/private_key':
+ ensure => present,
+ source => "/srv/leap/files/nodes/${::hostname}/tor.key",
+ owner => 'debian-tor',
+ group => 'debian-tor',
+ mode => '0600';
+
+ '/var/lib/tor/webapp/hostname':
+ ensure => present,
+ content => $tor_domain,
+ owner => 'debian-tor',
+ group => 'debian-tor',
+ mode => '0600';
+ }
+
+ # it is necessary to zero out the config of the status module
+ # because we are configuring our own version that is unavailable
+ # over the hidden service (see: #7456 and #7776)
+ apache::module { 'status': ensure => present, conf_content => ' ' }
+ # the access_compat module is required to enable Allow directives
+ apache::module { 'access_compat': ensure => present }
+
+ apache::vhost::file {
+ 'hidden_service':
+ content => template('site_apache/vhosts.d/hidden_service.conf.erb');
+ 'server_status':
+ vhost_source => 'modules/site_webapp/server-status.conf';
+ }
+
+ include site_shorewall::tor
+}
diff --git a/puppet/modules/site_webapp/manifests/init.pp b/puppet/modules/site_webapp/manifests/init.pp
new file mode 100644
index 00000000..15925aba
--- /dev/null
+++ b/puppet/modules/site_webapp/manifests/init.pp
@@ -0,0 +1,179 @@
+# configure webapp service
+class site_webapp {
+ tag 'leap_service'
+ $definition_files = hiera('definition_files')
+ $provider = $definition_files['provider']
+ $eip_service = $definition_files['eip_service']
+ $soledad_service = $definition_files['soledad_service']
+ $smtp_service = $definition_files['smtp_service']
+ $node_domain = hiera('domain')
+ $provider_domain = $node_domain['full_suffix']
+ $webapp = hiera('webapp')
+ $api_version = $webapp['api_version']
+ $secret_token = $webapp['secret_token']
+ $tor = hiera('tor', false)
+ $sources = hiera('sources')
+
+ Class['site_config::default'] -> Class['site_webapp']
+
+ include site_config::ruby::dev
+ include site_webapp::apache
+ include site_webapp::couchdb
+ include site_haproxy
+ include site_webapp::cron
+ include site_config::default
+ include site_config::x509::cert
+ include site_config::x509::key
+ include site_config::x509::ca
+ include site_config::x509::client_ca::ca
+ include site_config::x509::client_ca::key
+ include site_nickserver
+
+ # remove leftovers from previous installations on webapp nodes
+ include site_config::remove::webapp
+
+ group { 'leap-webapp':
+ ensure => present,
+ allowdupe => false;
+ }
+
+ user { 'leap-webapp':
+ ensure => present,
+ allowdupe => false,
+ gid => 'leap-webapp',
+ groups => 'ssl-cert',
+ home => '/srv/leap/webapp',
+ require => [ Group['leap-webapp'] ];
+ }
+
+ vcsrepo { '/srv/leap/webapp':
+ ensure => present,
+ force => true,
+ revision => $sources['webapp']['revision'],
+ provider => $sources['webapp']['type'],
+ source => $sources['webapp']['source'],
+ owner => 'leap-webapp',
+ group => 'leap-webapp',
+ require => [ User['leap-webapp'], Group['leap-webapp'] ],
+ notify => Exec['bundler_update']
+ }
+
+ exec { 'bundler_update':
+ cwd => '/srv/leap/webapp',
+ command => '/bin/bash -c "/usr/bin/bundle check --path vendor/bundle || /usr/bin/bundle install --path vendor/bundle --without test development debug"',
+ unless => '/usr/bin/bundle check --path vendor/bundle',
+ user => 'leap-webapp',
+ timeout => 600,
+ require => [
+ Class['bundler::install'],
+ Vcsrepo['/srv/leap/webapp'],
+ Class['site_config::ruby::dev'],
+ Service['shorewall'] ],
+ notify => Service['apache'];
+ }
+
+ #
+ # NOTE: in order to support a webapp that is running on a subpath and not the
+ # root of the domain assets:precompile needs to be run with
+ # RAILS_RELATIVE_URL_ROOT=/application-root
+ #
+
+ exec { 'compile_assets':
+ cwd => '/srv/leap/webapp',
+ command => '/bin/bash -c "RAILS_ENV=production /usr/bin/bundle exec rake assets:precompile"',
+ user => 'leap-webapp',
+ logoutput => on_failure,
+ require => Exec['bundler_update'],
+ notify => Service['apache'];
+ }
+
+ file {
+ '/srv/leap/webapp/config/provider':
+ ensure => directory,
+ require => Vcsrepo['/srv/leap/webapp'],
+ owner => leap-webapp, group => leap-webapp, mode => '0755';
+
+ '/srv/leap/webapp/config/provider/provider.json':
+ content => $provider,
+ require => Vcsrepo['/srv/leap/webapp'],
+ owner => leap-webapp, group => leap-webapp, mode => '0644';
+
+ '/srv/leap/webapp/public/ca.crt':
+ ensure => link,
+ require => Vcsrepo['/srv/leap/webapp'],
+ target => "${x509::variables::local_CAs}/${site_config::params::ca_name}.crt";
+
+ "/srv/leap/webapp/public/${api_version}":
+ ensure => directory,
+ require => Vcsrepo['/srv/leap/webapp'],
+ owner => leap-webapp, group => leap-webapp, mode => '0755';
+
+ "/srv/leap/webapp/public/${api_version}/config/":
+ ensure => directory,
+ require => Vcsrepo['/srv/leap/webapp'],
+ owner => leap-webapp, group => leap-webapp, mode => '0755';
+
+ "/srv/leap/webapp/public/${api_version}/config/eip-service.json":
+ content => $eip_service,
+ require => Vcsrepo['/srv/leap/webapp'],
+ owner => leap-webapp, group => leap-webapp, mode => '0644';
+
+ "/srv/leap/webapp/public/${api_version}/config/soledad-service.json":
+ content => $soledad_service,
+ require => Vcsrepo['/srv/leap/webapp'],
+ owner => leap-webapp, group => leap-webapp, mode => '0644';
+
+ "/srv/leap/webapp/public/${api_version}/config/smtp-service.json":
+ content => $smtp_service,
+ require => Vcsrepo['/srv/leap/webapp'],
+ owner => leap-webapp, group => leap-webapp, mode => '0644';
+ }
+
+ try::file {
+ '/srv/leap/webapp/config/customization':
+ ensure => directory,
+ recurse => true,
+ purge => true,
+ force => true,
+ owner => leap-webapp,
+ group => leap-webapp,
+ mode => 'u=rwX,go=rX',
+ require => Vcsrepo['/srv/leap/webapp'],
+ notify => Exec['compile_assets'],
+ source => $webapp['customization_dir'];
+ }
+
+ git::changes {
+ 'public/favicon.ico':
+ cwd => '/srv/leap/webapp',
+ require => Vcsrepo['/srv/leap/webapp'],
+ user => 'leap-webapp';
+ }
+
+ file {
+ '/srv/leap/webapp/config/config.yml':
+ content => template('site_webapp/config.yml.erb'),
+ owner => leap-webapp,
+ group => leap-webapp,
+ mode => '0600',
+ require => Vcsrepo['/srv/leap/webapp'],
+ notify => Service['apache'];
+ }
+
+ if $tor {
+ $hidden_service = $tor['hidden_service']
+ if $hidden_service['active'] {
+ include site_webapp::hidden_service
+ }
+ }
+
+
+ # needed for the soledad-sync check which is run on the
+ # webapp node
+ include soledad::client
+
+ leap::logfile { 'webapp': }
+
+ include site_shorewall::webapp
+ include site_check_mk::agent::webapp
+}
diff --git a/puppet/modules/site_webapp/templates/config.yml.erb b/puppet/modules/site_webapp/templates/config.yml.erb
new file mode 100644
index 00000000..dd55d3e9
--- /dev/null
+++ b/puppet/modules/site_webapp/templates/config.yml.erb
@@ -0,0 +1,36 @@
+<%
+cert_options = @webapp['client_certificates']
+production = {
+ "admins" => @webapp['admins'],
+ "default_locale" => @webapp['default_locale'],
+ "available_locales" => @webapp['locales'],
+ "domain" => @provider_domain,
+ "force_ssl" => @webapp['secure'],
+ "client_ca_key" => "%s/%s.key" % [scope.lookupvar('x509::variables::keys'), scope.lookupvar('site_config::params::client_ca_name')],
+ "client_ca_cert" => "%s/%s.crt" % [scope.lookupvar('x509::variables::local_CAs'), scope.lookupvar('site_config::params::client_ca_name')],
+ "secret_token" => @secret_token,
+ "client_cert_lifespan" => cert_options['life_span'],
+ "client_cert_bit_size" => cert_options['bit_size'].to_i,
+ "client_cert_hash" => cert_options['digest'],
+ "allow_limited_certs" => @webapp['allow_limited_certs'],
+ "allow_unlimited_certs" => @webapp['allow_unlimited_certs'],
+ "allow_anonymous_certs" => @webapp['allow_anonymous_certs'],
+ "limited_cert_prefix" => cert_options['limited_prefix'],
+ "unlimited_cert_prefix" => cert_options['unlimited_prefix'],
+ "minimum_client_version" => @webapp['client_version']['min'],
+ "default_service_level" => @webapp['default_service_level'],
+ "service_levels" => @webapp['service_levels'],
+ "allow_registration" => @webapp['allow_registration'],
+ "handle_blacklist" => @webapp['forbidden_usernames'],
+ "invite_required" => @webapp['invite_required'],
+ "api_tokens" => @webapp['api_tokens']
+}
+
+if @webapp['engines'] && @webapp['engines'].any?
+ production["engines"] = @webapp['engines']
+end
+-%>
+#
+# This file is generated by puppet. This file inherits from defaults.yml.
+#
+<%= scope.function_sorted_yaml([{"production" => production}]) %>
diff --git a/puppet/modules/site_webapp/templates/couchdb.admin.yml.erb b/puppet/modules/site_webapp/templates/couchdb.admin.yml.erb
new file mode 100644
index 00000000..a0921add
--- /dev/null
+++ b/puppet/modules/site_webapp/templates/couchdb.admin.yml.erb
@@ -0,0 +1,9 @@
+production:
+ prefix: ""
+ protocol: 'http'
+ host: <%= @couchdb_host %>
+ port: <%= @couchdb_port %>
+ auto_update_design_doc: false
+ username: <%= @couchdb_admin_user %>
+ password: <%= @couchdb_admin_password %>
+
diff --git a/puppet/modules/site_webapp/templates/couchdb.yml.erb b/puppet/modules/site_webapp/templates/couchdb.yml.erb
new file mode 100644
index 00000000..2bef0af5
--- /dev/null
+++ b/puppet/modules/site_webapp/templates/couchdb.yml.erb
@@ -0,0 +1,9 @@
+production:
+ prefix: ""
+ protocol: 'http'
+ host: <%= @couchdb_host %>
+ port: <%= @couchdb_port %>
+ auto_update_design_doc: false
+ username: <%= @couchdb_webapp_user %>
+ password: <%= @couchdb_webapp_password %>
+
diff --git a/puppet/modules/soledad/manifests/client.pp b/puppet/modules/soledad/manifests/client.pp
new file mode 100644
index 00000000..e470adeb
--- /dev/null
+++ b/puppet/modules/soledad/manifests/client.pp
@@ -0,0 +1,16 @@
+# setup soledad-client
+# currently needed on webapp node to run the soledad-sync test
+class soledad::client {
+
+ tag 'leap_service'
+ include soledad::common
+
+ package {
+ 'soledad-client':
+ ensure => latest,
+ require => Class['site_apt::leap_repo'];
+ 'python-u1db':
+ ensure => latest;
+ }
+
+}
diff --git a/puppet/modules/soledad/manifests/common.pp b/puppet/modules/soledad/manifests/common.pp
new file mode 100644
index 00000000..8d8339d4
--- /dev/null
+++ b/puppet/modules/soledad/manifests/common.pp
@@ -0,0 +1,8 @@
+# install soledad-common, both needed both soledad-client and soledad-server
+class soledad::common {
+
+ package { 'soledad-common':
+ ensure => latest;
+ }
+
+}
diff --git a/puppet/modules/soledad/manifests/server.pp b/puppet/modules/soledad/manifests/server.pp
new file mode 100644
index 00000000..8674f421
--- /dev/null
+++ b/puppet/modules/soledad/manifests/server.pp
@@ -0,0 +1,104 @@
+# setup soledad-server
+class soledad::server {
+ tag 'leap_service'
+
+ include site_config::default
+ include soledad::common
+
+ $soledad = hiera('soledad')
+ $couchdb_user = $soledad['couchdb_soledad_user']['username']
+ $couchdb_password = $soledad['couchdb_soledad_user']['password']
+ $couchdb_leap_mx_user = $soledad['couchdb_leap_mx_user']['username']
+
+ $couchdb_host = 'localhost'
+ $couchdb_port = '5984'
+
+ $soledad_port = $soledad['port']
+
+ $sources = hiera('sources')
+
+ include site_config::x509::cert
+ include site_config::x509::key
+ include site_config::x509::ca
+
+ #
+ # SOLEDAD CONFIG
+ #
+
+ file {
+ '/etc/soledad':
+ ensure => directory,
+ owner => 'root',
+ group => 'root',
+ mode => '0755';
+ '/etc/soledad/soledad-server.conf':
+ content => template('soledad/soledad-server.conf.erb'),
+ owner => 'soledad',
+ group => 'soledad',
+ mode => '0640',
+ notify => Service['soledad-server'],
+ require => [ User['soledad'], Group['soledad'] ];
+ '/srv/leap/soledad':
+ ensure => directory,
+ owner => 'soledad',
+ group => 'soledad',
+ require => [ User['soledad'], Group['soledad'] ];
+ '/var/lib/soledad':
+ ensure => directory,
+ owner => 'soledad',
+ group => 'soledad',
+ require => [ User['soledad'], Group['soledad'] ];
+ }
+
+ package { $sources['soledad']['package']:
+ ensure => $sources['soledad']['revision'],
+ require => Class['site_apt::leap_repo'];
+ }
+
+ file { '/etc/default/soledad':
+ content => template('soledad/default-soledad.erb'),
+ owner => 'soledad',
+ group => 'soledad',
+ mode => '0600',
+ notify => Service['soledad-server'],
+ require => [ User['soledad'], Group['soledad'] ];
+ }
+
+ service { 'soledad-server':
+ ensure => running,
+ enable => true,
+ hasstatus => true,
+ hasrestart => true,
+ require => [ User['soledad'], Group['soledad'] ],
+ subscribe => [
+ Package['soledad-server'],
+ Class['Site_config::X509::Key'],
+ Class['Site_config::X509::Cert'],
+ Class['Site_config::X509::Ca'] ];
+ }
+
+ include site_shorewall::soledad
+ include site_check_mk::agent::soledad
+
+ # set up users, group and directories for soledad-server
+ # although the soledad users are already created by the
+ # soledad-server package
+ group { 'soledad':
+ ensure => present,
+ system => true,
+ }
+ user {
+ 'soledad':
+ ensure => present,
+ system => true,
+ gid => 'soledad',
+ home => '/srv/leap/soledad',
+ require => Group['soledad'];
+ 'soledad-admin':
+ ensure => present,
+ system => true,
+ gid => 'soledad',
+ home => '/srv/leap/soledad',
+ require => Group['soledad'];
+ }
+}
diff --git a/puppet/modules/soledad/templates/default-soledad.erb b/puppet/modules/soledad/templates/default-soledad.erb
new file mode 100644
index 00000000..32504e38
--- /dev/null
+++ b/puppet/modules/soledad/templates/default-soledad.erb
@@ -0,0 +1,5 @@
+# this file is managed by puppet
+START=yes
+CERT_PATH=<%= scope.lookupvar('x509::variables::certs') %>/<%= scope.lookupvar('site_config::params::cert_name') %>.crt
+PRIVKEY_PATH=<%= scope.lookupvar('x509::variables::keys') %>/<%= scope.lookupvar('site_config::params::cert_name') %>.key
+HTTPS_PORT=<%=@soledad_port%>
diff --git a/puppet/modules/soledad/templates/soledad-server.conf.erb b/puppet/modules/soledad/templates/soledad-server.conf.erb
new file mode 100644
index 00000000..1c6a0d19
--- /dev/null
+++ b/puppet/modules/soledad/templates/soledad-server.conf.erb
@@ -0,0 +1,12 @@
+[soledad-server]
+couch_url = http://<%= @couchdb_user %>:<%= @couchdb_password %>@<%= @couchdb_host %>:<%= @couchdb_port %>
+create_cmd = sudo -u soledad-admin /usr/bin/create-user-db
+admin_netrc = /etc/couchdb/couchdb-soledad-admin.netrc
+
+[database-security]
+members = <%= @couchdb_user %>, <%= @couchdb_leap_mx_user %>
+# not needed, but for documentation:
+# members_roles = replication
+# admins = admin
+# admins_roles = replication
+
diff --git a/puppet/modules/squid_deb_proxy/README.md b/puppet/modules/squid_deb_proxy/README.md
new file mode 100644
index 00000000..c183c826
--- /dev/null
+++ b/puppet/modules/squid_deb_proxy/README.md
@@ -0,0 +1,8 @@
+This module installes squid-deb-proxy (server or client)
+see https://launchpad.net/squid-deb-proxy for more details
+
+Debian Support
+==============
+
+* As of 2013-07, squid-deb-proxy just arrived in jessie, so you need to
+ configure apt to use jessie.
diff --git a/puppet/modules/squid_deb_proxy/files/Debian/squid-deb-proxy.conf b/puppet/modules/squid_deb_proxy/files/Debian/squid-deb-proxy.conf
new file mode 100644
index 00000000..2a528f84
--- /dev/null
+++ b/puppet/modules/squid_deb_proxy/files/Debian/squid-deb-proxy.conf
@@ -0,0 +1,91 @@
+
+# WELCOME TO SQUID DEB PROXY
+# ------------------
+#
+# This config file is a version of a squid proxy file optimized
+# as a configuration for a caching proxy for Debian/Ubuntu systems.
+#
+# More information about squid and its configuration can be found here
+# http://www.squid-cache.org/ and in the FAQ
+
+# settings that you may want to customize
+# ---------------------------------------
+
+# this file contains private networks (10.0.0.0/8, 172.16.0.0/12,
+# 192.168.0.0/16) by default, you can add/remove additional allowed
+# source networks in it to customize it for your setup
+acl allowed_networks src "/etc/squid-deb-proxy/autogenerated/allowed-networks-src.acl"
+
+# this file contains the archive mirrors by default,
+# if you use a different mirror, add it there
+acl to_archive_mirrors dstdomain "/etc/squid-deb-proxy/autogenerated/mirror-dstdomain.acl"
+
+# this contains the package blacklist
+acl blockedpkgs urlpath_regex "/etc/squid-deb-proxy/autogenerated/pkg-blacklist-regexp.acl"
+
+# default to a different port than stock squid
+http_port 8000
+
+# -------------------------------------------------
+# settings below probably do not need customization
+
+# user visible name
+visible_hostname squid-deb-proxy
+
+# we need a big cache, some debs are huge
+maximum_object_size 512 MB
+
+# use a different dir than stock squid and default to 40G
+cache_dir aufs /var/cache/squid-deb-proxy 40000 16 256
+
+# use different logs
+cache_access_log /var/log/squid-deb-proxy/access.log
+cache_log /var/log/squid-deb-proxy/cache.log
+cache_store_log /var/log/squid-deb-proxy/store.log
+
+# tweaks to speed things up
+cache_mem 200 MB
+maximum_object_size_in_memory 10240 KB
+
+# pid
+pid_filename /var/run/squid-deb-proxy.pid
+
+# refresh pattern for debs and udebs
+refresh_pattern deb$ 129600 100% 129600
+refresh_pattern udeb$ 129600 100% 129600
+refresh_pattern tar.gz$ 129600 100% 129600
+
+# always refresh Packages and Release files
+refresh_pattern \/(Packages|Sources)(|\.bz2|\.gz)$ 0 0% 0
+refresh_pattern \/Release(|\.gpg)$ 0 0% 0
+refresh_pattern \/InRelease$ 0 0% 0
+
+# handle meta-release and changelogs.ubuntu.com special
+# (fine to have this on debian too)
+refresh_pattern changelogs.ubuntu.com/* 0 1% 1
+
+# only allow connects to ports for http, https
+acl Safe_ports port 80
+acl Safe_ports port 443 563
+
+# only allow ports we trust
+http_access deny !Safe_ports
+
+# do not allow to download from the pkg blacklist
+http_access deny blockedpkgs
+
+# allow access only to official archive mirrors
+# uncomment the third and fouth line to permit any unlisted domain
+http_access deny !to_archive_mirrors
+#http_access allow !to_archive_mirrors
+
+# don't cache domains not listed in the mirrors file
+# uncomment the third and fourth line to cache any unlisted domains
+cache deny !to_archive_mirrors
+#cache allow !to_archive_mirrors
+
+# allow access from our network and localhost
+http_access allow allowed_networks
+
+# And finally deny all other access to this proxy
+http_access deny all
diff --git a/puppet/modules/squid_deb_proxy/files/Ubuntu/squid-deb-proxy.conf b/puppet/modules/squid_deb_proxy/files/Ubuntu/squid-deb-proxy.conf
new file mode 100644
index 00000000..ab5bac8a
--- /dev/null
+++ b/puppet/modules/squid_deb_proxy/files/Ubuntu/squid-deb-proxy.conf
@@ -0,0 +1,89 @@
+
+# WELCOME TO SQUID DEB PROXY
+# ------------------
+#
+# This config file is a version of a squid proxy file optimized
+# as a configuration for a caching proxy for Ubuntu systems.
+#
+# More information about squid and its configuration can be found here
+# http://www.squid-cache.org/ and in the FAQ
+
+# settings that you may want to customize
+# ---------------------------------------
+
+# this file contains private networks (10.0.0.0/8, 172.16.0.0/12,
+# 192.168.0.0/16) by default, you can add/remove additional allowed
+# source networks in it to customize it for your setup
+acl allowed_networks src "/etc/squid-deb-proxy/autogenerated/allowed-networks-src.acl"
+
+# this file contains the *archive.ubuntu.com mirrors by default,
+# if you use a different mirror, add it there
+acl to_ubuntu_mirrors dstdomain "/etc/squid-deb-proxy/autogenerated/mirror-dstdomain.acl"
+
+# this contains the package blacklist
+acl blockedpkgs urlpath_regex "/etc/squid-deb-proxy/autogenerated/pkg-blacklist-regexp.acl"
+
+# default to a different port than stock squid
+http_port 8000
+
+# -------------------------------------------------
+# settings below probably do not need customization
+
+# user visible name
+visible_hostname squid-deb-proxy
+
+# we need a big cache, some debs are huge
+maximum_object_size 512 MB
+
+# use a different dir than stock squid and default to 40G
+cache_dir aufs /var/cache/squid-deb-proxy 40000 16 256
+
+# use different logs
+cache_access_log /var/log/squid-deb-proxy/access.log
+cache_log /var/log/squid-deb-proxy/cache.log
+cache_store_log /var/log/squid-deb-proxy/store.log
+
+# tweaks to speed things up
+cache_mem 200 MB
+maximum_object_size_in_memory 10240 KB
+
+# pid
+pid_filename /var/run/squid-deb-proxy.pid
+
+# refresh pattern for debs and udebs
+refresh_pattern deb$ 129600 100% 129600
+refresh_pattern udeb$ 129600 100% 129600
+refresh_pattern tar.gz$ 129600 100% 129600
+
+# always refresh Packages and Release files
+refresh_pattern \/(Packages|Sources)(|\.bz2|\.gz)$ 0 0% 0
+refresh_pattern \/Release(|\.gpg)$ 0 0% 0
+
+# handle meta-release and changelogs.ubuntu.com special
+refresh_pattern changelogs.ubuntu.com/* 0 1% 1
+
+# only allow connects to ports for http, https
+acl Safe_ports port 80
+acl Safe_ports port 443 563
+
+# only allow ports we trust
+http_access deny !Safe_ports
+
+# do not allow to download from the pkg blacklist
+http_access deny blockedpkgs
+
+# allow access only to official ubuntu mirrors
+# uncomment the third and fouth line to permit any unlisted domain
+http_access deny !to_ubuntu_mirrors
+#http_access allow !to_ubuntu_mirrors
+
+# don't cache domains not listed in the mirrors file
+# uncomment the third and fourth line to cache any unlisted domains
+cache deny !to_ubuntu_mirrors
+#cache allow !to_ubuntu_mirrors
+
+# allow access from our network and localhost
+http_access allow allowed_networks
+
+# And finally deny all other access to this proxy
+http_access deny all
diff --git a/puppet/modules/squid_deb_proxy/files/allowed-networks-src.acl.d/20-custom b/puppet/modules/squid_deb_proxy/files/allowed-networks-src.acl.d/20-custom
new file mode 100644
index 00000000..d4058b80
--- /dev/null
+++ b/puppet/modules/squid_deb_proxy/files/allowed-networks-src.acl.d/20-custom
@@ -0,0 +1 @@
+# managed by puppet
diff --git a/puppet/modules/squid_deb_proxy/files/client/apt-avahi-discover b/puppet/modules/squid_deb_proxy/files/client/apt-avahi-discover
new file mode 100755
index 00000000..8dbc1be2
--- /dev/null
+++ b/puppet/modules/squid_deb_proxy/files/client/apt-avahi-discover
@@ -0,0 +1,138 @@
+#!/usr/bin/python
+#
+# use avahi to find a _apt_proxy._tcp provider and return
+# a http proxy string suitable for apt
+
+import asyncore
+import functools
+import os
+import socket
+import sys
+import time
+from subprocess import Popen, PIPE, call
+from syslog import syslog, LOG_INFO, LOG_USER
+
+DEFAULT_CONNECT_TIMEOUT_SEC = 2
+
+def DEBUG(msg):
+ if "--debug" in sys.argv:
+ sys.stderr.write(msg + "\n")
+
+
+def get_avahi_discover_timeout():
+ APT_AVAHI_TIMEOUT_VAR = "APT::Avahi-Discover::Timeout"
+ p = Popen(
+ ["/usr/bin/apt-config", "shell", "TIMEOUT", APT_AVAHI_TIMEOUT_VAR],
+ stdout=PIPE)
+ stdout, stderr = p.communicate()
+ if not stdout:
+ DEBUG(
+ "no timeout set, using default '%s'" % DEFAULT_CONNECT_TIMEOUT_SEC)
+ return DEFAULT_CONNECT_TIMEOUT_SEC
+ if not stdout.startswith("TIMEOUT="):
+ raise ValueError("got unexpected apt-config output: '%s'" % stdout)
+ varname, sep, value = stdout.strip().partition("=")
+ timeout = int(value.strip("'"))
+ DEBUG("using timeout: '%s'" % timeout)
+ return timeout
+
+@functools.total_ordering
+class AptAvahiClient(asyncore.dispatcher):
+ def __init__(self, addr):
+ asyncore.dispatcher.__init__(self)
+ if is_ipv6(addr[0]):
+ self.create_socket(socket.AF_INET6, socket.SOCK_STREAM)
+ self.connect( (addr[0], addr[1], 0, 0) )
+ else:
+ self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.connect(addr)
+ self._time_init = time.time()
+ self.time_to_connect = sys.maxint
+ self.address = addr
+ def handle_connect(self):
+ self.time_to_connect = time.time() - self._time_init
+ self.close()
+ def __eq__(self, other):
+ return self.time_to_connect == other.time_to_connect
+ def __lt__(self, other):
+ return self.time_to_connect < other.time_to_connect
+ def __repr__(self):
+ return "<%s> %s: %s" % (
+ self.__class__.__name__, self.addr, self.time_to_connect)
+ def log(self, message):
+ syslog((LOG_INFO|LOG_USER), '%s\n' % str(message))
+ def log_info(self, message, type='info'):
+ if type not in self.ignore_log_types:
+ self.log('%s: %s' % (type, message))
+
+
+def is_ipv6(a):
+ return ':' in a
+
+def is_linklocal(addr):
+ # Link-local should start with fe80 and six null bytes
+ return addr.startswith("fe80::")
+
+def get_proxy_host_port_from_avahi():
+ service = '_apt_proxy._tcp'
+
+ # Obtain all of the services addresses from avahi, pulling the IPv6
+ # addresses to the top.
+ addr4 = []
+ addr6 = []
+ p = Popen(['avahi-browse', '-kprtf', service], stdout=PIPE)
+ DEBUG("avahi-browse output:")
+ for line in p.stdout:
+ DEBUG(" '%s'" % line)
+ if line.startswith('='):
+ tokens = line.split(';')
+ addr = tokens[7]
+ port = int(tokens[8])
+ if is_ipv6(addr):
+ # We need to skip ipv6 link-local addresses since
+ # APT can't use them
+ if not is_linklocal(addr):
+ addr6.append((addr, port))
+ else:
+ addr4.append((addr, port))
+
+ # Run through the offered addresses and see if we we have a bound local
+ # address for it.
+ addrs = []
+ for (ip, port) in addr6 + addr4:
+ try:
+ res = socket.getaddrinfo(ip, port, 0, 0, 0, socket.AI_ADDRCONFIG)
+ if res:
+ addrs.append((ip, port))
+ except socket.gaierror:
+ pass
+ if not addrs:
+ return None
+
+ # sort by answering speed
+ hosts = []
+ for addr in addrs:
+ hosts.append(AptAvahiClient(addr))
+ # 2s timeout, arbitray
+ timeout = get_avahi_discover_timeout()
+ asyncore.loop(timeout=timeout)
+ DEBUG("sorted hosts: '%s'" % sorted(hosts))
+
+ # No host wanted to connect
+ if (all(h.time_to_connect == sys.maxint for h in hosts)):
+ return None
+
+ fastest_host = sorted(hosts)[0]
+ fastest_address = fastest_host.address
+ return fastest_address
+
+
+if __name__ == "__main__":
+ # Dump the approved address out in an appropriate format.
+ address = get_proxy_host_port_from_avahi()
+ if address:
+ (ip, port) = address
+ if is_ipv6(ip):
+ print "http://[%s]:%s/" % (ip, port)
+ else:
+ print "http://%s:%s/" % (ip, port)
diff --git a/puppet/modules/squid_deb_proxy/files/mirror-dstdomain.acl.d/20-custom b/puppet/modules/squid_deb_proxy/files/mirror-dstdomain.acl.d/20-custom
new file mode 100644
index 00000000..d4058b80
--- /dev/null
+++ b/puppet/modules/squid_deb_proxy/files/mirror-dstdomain.acl.d/20-custom
@@ -0,0 +1 @@
+# managed by puppet
diff --git a/puppet/modules/squid_deb_proxy/manifests/client.pp b/puppet/modules/squid_deb_proxy/manifests/client.pp
new file mode 100644
index 00000000..049f740a
--- /dev/null
+++ b/puppet/modules/squid_deb_proxy/manifests/client.pp
@@ -0,0 +1,16 @@
+# install squid-deb-proxy-client package
+class squid_deb_proxy::client {
+ package { 'squid-deb-proxy-client':
+ ensure => installed,
+ } ->
+
+ # ship newer client discover script than includes in squid-deb-proxy-client
+ # v. 0.8.13 to fix error messages being sent to stdout instead of stderr,
+ # see https://bugs.launchpad.net/ubuntu/+source/squid-deb-proxy/+bug/1505670
+ file { '/usr/share/squid-deb-proxy-client/apt-avahi-discover':
+ source => 'puppet:///modules/squid_deb_proxy/client/apt-avahi-discover',
+ mode => '0755',
+ owner => 'root',
+ group => 'root',
+ }
+}
diff --git a/puppet/modules/squid_deb_proxy/manifests/server.pp b/puppet/modules/squid_deb_proxy/manifests/server.pp
new file mode 100644
index 00000000..b6c3b2ed
--- /dev/null
+++ b/puppet/modules/squid_deb_proxy/manifests/server.pp
@@ -0,0 +1,41 @@
+class squid_deb_proxy::server {
+ package { 'squid-deb-proxy':
+ ensure => installed,
+ }
+
+ service { 'squid-deb-proxy':
+ ensure => running,
+ hasstatus => false,
+ require => Package[ 'squid-deb-proxy' ],
+ }
+
+ file {'/etc/squid-deb-proxy/mirror-dstdomain.acl.d/20-custom':
+ source => [ 'puppet:///modules/site_squid_deb_proxy/mirror-dstdomain.acl.d/20-custom',
+ 'puppet:///modules/squid_deb_proxy/mirror-dstdomain.acl.d/20-custom' ],
+ notify => Service[ 'squid-deb-proxy' ],
+ require => Package[ 'squid-deb-proxy' ],
+ mode => '0644',
+ owner => 'root',
+ group => 'root',
+ }
+
+ file {'/etc/squid-deb-proxy/allowed-networks-src.acl.d/20-custom':
+ source => [ 'puppet:///modules/site_squid_deb_proxy/allowed-networks-src.acl.d/20-custom',
+ 'puppet:///modules/squid_deb_proxy/allowed-networks-src.acl.d/20-custom' ],
+ notify => Service[ 'squid-deb-proxy' ],
+ require => Package[ 'squid-deb-proxy' ],
+ mode => '0644',
+ owner => 'root',
+ group => 'root',
+ }
+
+ file { '/etc/squid-deb-proxy/squid-deb-proxy.conf':
+ source => [ "puppet:///modules/site_squid_deb_proxy/${::operatingsystem}/squid-deb-proxy.conf",
+ "puppet:///modules/squid_deb_proxy/${::operatingsystem}/squid-deb-proxy.conf" ],
+ notify => Service[ 'squid-deb-proxy' ],
+ require => Package[ 'squid-deb-proxy' ],
+ mode => '0644',
+ owner => 'root',
+ group => 'root',
+ }
+}
diff --git a/puppet/modules/sshd/.fixtures.yml b/puppet/modules/sshd/.fixtures.yml
new file mode 100644
index 00000000..42598a65
--- /dev/null
+++ b/puppet/modules/sshd/.fixtures.yml
@@ -0,0 +1,3 @@
+fixtures:
+ symlinks:
+ sshd: "#{source_dir}" \ No newline at end of file
diff --git a/puppet/modules/sshd/.gitignore b/puppet/modules/sshd/.gitignore
new file mode 100644
index 00000000..5ebb01fb
--- /dev/null
+++ b/puppet/modules/sshd/.gitignore
@@ -0,0 +1,4 @@
+.librarian/*
+.tmp/*
+*.log
+spec/fixtures/*
diff --git a/puppet/modules/sshd/.rspec b/puppet/modules/sshd/.rspec
new file mode 100644
index 00000000..f07c903a
--- /dev/null
+++ b/puppet/modules/sshd/.rspec
@@ -0,0 +1,4 @@
+--format documentation
+--color
+--pattern "spec/*/*_spec.rb"
+#--backtrace
diff --git a/puppet/modules/sshd/.travis.yml b/puppet/modules/sshd/.travis.yml
new file mode 100644
index 00000000..7bd2a2bc
--- /dev/null
+++ b/puppet/modules/sshd/.travis.yml
@@ -0,0 +1,27 @@
+before_install:
+ - gem update --system 2.1.11
+ - gem --version
+rvm:
+ - 1.8.7
+ - 1.9.3
+ - 2.0.0
+script: 'bundle exec rake spec'
+env:
+ - PUPPET_VERSION="~> 2.7.0"
+ - PUPPET_VERSION="~> 3.0.0"
+ - PUPPET_VERSION="~> 3.1.0"
+ - PUPPET_VERSION="~> 3.2.0"
+ - PUPPET_VERSION="~> 3.3.0"
+ - PUPPET_VERSION="~> 3.4.0"
+matrix:
+ exclude:
+ # No support for Ruby 1.9 before Puppet 2.7
+ - rvm: 1.9.3
+ env: PUPPET_VERSION=2.6.0
+ # No support for Ruby 2.0 before Puppet 3.2
+ - rvm: 2.0.0
+ env: PUPPET_VERSION="~> 2.7.0"
+ - rvm: 2.0.0
+ env: PUPPET_VERSION="~> 3.0.0"
+ - rvm: 2.0.0
+ env: PUPPET_VERSION="~> 3.1.0"
diff --git a/puppet/modules/sshd/Gemfile b/puppet/modules/sshd/Gemfile
new file mode 100644
index 00000000..ef74f90e
--- /dev/null
+++ b/puppet/modules/sshd/Gemfile
@@ -0,0 +1,14 @@
+source 'https://rubygems.org'
+
+group :development, :test do
+ gem 'puppet', '>= 2.7.0'
+ gem 'puppet-lint', '>=0.3.2'
+ gem 'puppetlabs_spec_helper', '>=0.2.0'
+ gem 'rake', '>=0.9.2.2'
+ gem 'librarian-puppet', '>=0.9.10'
+ gem 'rspec-system-puppet', :require => false
+ gem 'serverspec', :require => false
+ gem 'rspec-system-serverspec', :require => false
+ gem 'rspec-hiera-puppet'
+ gem 'rspec-puppet', :git => 'https://github.com/rodjek/rspec-puppet.git'
+end \ No newline at end of file
diff --git a/puppet/modules/sshd/Gemfile.lock b/puppet/modules/sshd/Gemfile.lock
new file mode 100644
index 00000000..0c2c58e9
--- /dev/null
+++ b/puppet/modules/sshd/Gemfile.lock
@@ -0,0 +1,116 @@
+GIT
+ remote: https://github.com/rodjek/rspec-puppet.git
+ revision: c44381a240ec420d4ffda7bffc55ee4d9c08d682
+ specs:
+ rspec-puppet (1.0.1)
+ rspec
+
+GEM
+ remote: https://rubygems.org/
+ specs:
+ builder (3.2.2)
+ diff-lcs (1.2.5)
+ excon (0.31.0)
+ facter (1.7.4)
+ fog (1.19.0)
+ builder
+ excon (~> 0.31.0)
+ formatador (~> 0.2.0)
+ mime-types
+ multi_json (~> 1.0)
+ net-scp (~> 1.1)
+ net-ssh (>= 2.1.3)
+ nokogiri (~> 1.5)
+ ruby-hmac
+ formatador (0.2.4)
+ hiera (1.3.1)
+ json_pure
+ hiera-puppet (1.0.0)
+ hiera (~> 1.0)
+ highline (1.6.20)
+ json (1.8.1)
+ json_pure (1.8.1)
+ kwalify (0.7.2)
+ librarian-puppet (0.9.10)
+ json
+ thor (~> 0.15)
+ metaclass (0.0.2)
+ mime-types (1.25.1)
+ mocha (1.0.0)
+ metaclass (~> 0.0.1)
+ multi_json (1.8.4)
+ net-scp (1.1.2)
+ net-ssh (>= 2.6.5)
+ net-ssh (2.7.0)
+ nokogiri (1.5.11)
+ puppet (3.4.2)
+ facter (~> 1.6)
+ hiera (~> 1.0)
+ rgen (~> 0.6.5)
+ puppet-lint (0.3.2)
+ puppetlabs_spec_helper (0.4.1)
+ mocha (>= 0.10.5)
+ rake
+ rspec (>= 2.9.0)
+ rspec-puppet (>= 0.1.1)
+ rake (10.1.1)
+ rbvmomi (1.8.1)
+ builder
+ nokogiri (>= 1.4.1)
+ trollop
+ rgen (0.6.6)
+ rspec (2.14.1)
+ rspec-core (~> 2.14.0)
+ rspec-expectations (~> 2.14.0)
+ rspec-mocks (~> 2.14.0)
+ rspec-core (2.14.7)
+ rspec-expectations (2.14.4)
+ diff-lcs (>= 1.1.3, < 2.0)
+ rspec-hiera-puppet (1.0.0)
+ hiera (>= 1.0)
+ hiera-puppet (>= 1.0)
+ puppet (>= 3.0)
+ rspec
+ rspec-puppet
+ rspec-mocks (2.14.4)
+ rspec-system (2.8.0)
+ fog (~> 1.18)
+ kwalify (~> 0.7.2)
+ mime-types (~> 1.16)
+ net-scp (~> 1.1)
+ net-ssh (~> 2.7)
+ nokogiri (~> 1.5.10)
+ rbvmomi (~> 1.6)
+ rspec (~> 2.14)
+ systemu (~> 2.5)
+ rspec-system-puppet (2.2.1)
+ rspec-system (~> 2.0)
+ rspec-system-serverspec (2.0.1)
+ rspec-system (~> 2.0)
+ serverspec (~> 0.0)
+ specinfra (~> 0.0)
+ ruby-hmac (0.4.0)
+ serverspec (0.14.4)
+ highline
+ net-ssh
+ rspec (>= 2.13.0)
+ specinfra (>= 0.1.0)
+ specinfra (0.4.1)
+ systemu (2.6.0)
+ thor (0.18.1)
+ trollop (2.0)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ librarian-puppet (>= 0.9.10)
+ puppet (>= 2.7.0)
+ puppet-lint (>= 0.3.2)
+ puppetlabs_spec_helper (>= 0.2.0)
+ rake (>= 0.9.2.2)
+ rspec-hiera-puppet
+ rspec-puppet!
+ rspec-system-puppet
+ rspec-system-serverspec
+ serverspec
diff --git a/puppet/modules/sshd/LICENSE b/puppet/modules/sshd/LICENSE
new file mode 100644
index 00000000..94a9ed02
--- /dev/null
+++ b/puppet/modules/sshd/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/puppet/modules/sshd/Modulefile b/puppet/modules/sshd/Modulefile
new file mode 100644
index 00000000..5e4f92d6
--- /dev/null
+++ b/puppet/modules/sshd/Modulefile
@@ -0,0 +1,10 @@
+name 'puppet-sshd'
+version '0.1.0'
+source 'https://github.com/duritong/puppet-sshd'
+author 'duritong'
+license 'Apache License, Version 2.0'
+summary 'ssh daemon configuration'
+description 'Manages sshd_config'
+project_page 'https://github.com/duritong/puppet-sshd'
+
+dependency 'puppetlabs/stdlib', '>= 2.0.0' \ No newline at end of file
diff --git a/puppet/modules/sshd/Puppetfile b/puppet/modules/sshd/Puppetfile
new file mode 100644
index 00000000..166d3b4d
--- /dev/null
+++ b/puppet/modules/sshd/Puppetfile
@@ -0,0 +1,3 @@
+forge 'http://forge.puppetlabs.com'
+
+mod 'puppetlabs/stdlib', '>=2.0.0' \ No newline at end of file
diff --git a/puppet/modules/sshd/Puppetfile.lock b/puppet/modules/sshd/Puppetfile.lock
new file mode 100644
index 00000000..f9381858
--- /dev/null
+++ b/puppet/modules/sshd/Puppetfile.lock
@@ -0,0 +1,8 @@
+FORGE
+ remote: http://forge.puppetlabs.com
+ specs:
+ puppetlabs/stdlib (4.1.0)
+
+DEPENDENCIES
+ puppetlabs/stdlib (>= 2.0.0)
+
diff --git a/puppet/modules/sshd/README.md b/puppet/modules/sshd/README.md
new file mode 100644
index 00000000..77e4d29b
--- /dev/null
+++ b/puppet/modules/sshd/README.md
@@ -0,0 +1,247 @@
+# Puppet SSH Module
+
+[![Build Status](https://travis-ci.org/duritong/puppet-sshd.png?branch=master)](https://travis-ci.org/duritong/puppet-sshd)
+
+This puppet module manages OpenSSH configuration and services.
+
+**!! Upgrade Notice (05/2015) !!**
+
+The hardened_ssl parameter name was changed to simply 'hardened'.
+
+**!! Upgrade Notice (01/2013) !!**
+
+This module now uses parameterized classes, where it used global variables
+before. So please whatch out before pulling, you need to change the
+class declarations in your manifest !
+
+
+### Dependencies
+
+This module requires puppet => 2.6, and the following modules are required
+pre-dependencies:
+
+- [puppetlabs/stdlib](https://github.com/puppetlabs/puppetlabs-stdlib) >= 2.x
+
+## OpenSSH Server
+
+On a node where you wish to have an openssh server installed, you should
+include
+
+```puppet
+class { 'sshd': }
+```
+
+on that node. If you need to configure any aspects of sshd_config, set the variables before the include. Or you can adjust many parameters:
+
+```puppet
+class { 'sshd':
+ ports => [ 20002 ],
+ permit_root_login => 'no',
+}
+```
+
+See Configurable Variables below for what you can set.
+
+### Nagios
+
+To have nagios checks setup automatically for sshd services, simply set
+`manage_nagios` to `true` for that class. If you want to disable ssh
+nagios checking for a particular node (such as when ssh is firewalled), then you
+can set the class parameter `nagios_check_ssh` to `false` and that node will not be
+monitored.
+
+Nagios will automatically check the ports defined in `ports`, and the
+hostname specified by `nagios_check_ssh_hostname`.
+
+Note that if you need to use some specific logic to decide whether or not to
+create a nagios service check, you should set $manage_nagios to false, and
+use sshd::nagios from within your own manifests. You'll also need to manually
+specify the port to that define. By default, if the $port parameter is not
+specified, it will use the resource name as the port (e.g. if you call it like
+this: `sshd::nagios { '22': }` )
+
+NOTE: this requires that you are using the shared-nagios puppet module which
+supports the nagios native types via `nagios::service`:
+
+https://gitlab.com/shared-puppet-modules-group/sshd
+
+### Firewall
+
+If you wish to have firewall rules setup automatically for you, using shorewall,
+you will need to set: `use_shorewall => true`. The `ports` that you have
+specified will automatically be used.
+
+NOTE: This requires that you are using the shared-shorewall puppet module:
+git://labs.riseup.net/shared-shorewall
+
+
+### Configurable variables
+
+Configuration of sshd is strict, and may not fit all needs, however there are a
+number of variables that you can consider configuring. The defaults are set to
+the distribution shipped sshd_config file defaults.
+
+To set any of these variables, simply set them as variables in your manifests,
+before the class is included, for example:
+
+```puppet
+class {'sshd':
+ listen_address => ['10.0.0.1', '192.168.0.1'],
+ use_pam => yes
+}
+```
+
+If you need to install a version of the ssh daemon or client package other than
+the default one that would be installed by `ensure => installed`, then you can
+set the following variables:
+
+```puppet
+class {'sshd':
+ ensure_version => "1:5.2p2-6"
+}
+```
+
+The following is a list of the currently available variables:
+
+ - `listen_address`
+ specify the addresses sshd should listen on set this to `['10.0.0.1', '192.168.0.1']` to have it listen on both addresses, or leave it unset to listen on all Default: empty -> results in listening on `0.0.0.0`
+ - `allowed_users`
+ list of usernames separated by spaces. set this for example to `"foobar
+ root"` to ensure that only user foobar and root might login. Default: empty
+ -> no restriction is set
+ - `allowed_groups`
+ list of groups separated by spaces. set this for example to `"wheel sftponly"`
+ to ensure that only users in the groups wheel and sftponly might login.
+ Default: empty -> no restriction is set Note: This is set after
+ `allowed_users`, take care of the behaviour if you use these 2 options
+ together.
+ - `use_pam` if you want to use pam or not for authenticaton. Values:
+ - `no` (default)
+ - `yes`
+ - `permit_root_login` If you want to allow root logins or not. Valid values:
+ - `yes`
+ - `no`
+ - `without-password` (default)
+ - `forced-commands-only`
+ - `password_authentication`
+ If you want to enable password authentication or not. Valid values:
+ - `yes`
+ - `no` (default)
+ - `kerberos_authentication`
+ If you want the password that is provided by the user to be validated
+ through the Kerberos KDC. To use this option the server needs a Kerberos
+ servtab which allows the verification of the KDC's identity. Valid values:
+ - `yes`
+ - `no` (default)
+ - `kerberos_orlocalpasswd` If password authentication through Kerberos fails, then the password will be validated via any additional local mechanism. Valid values:
+ - `yes` (default)
+ - `no`
+ - `kerberos_ticketcleanup` Destroy the user's ticket cache file on logout? Valid values:
+ - `yes` (default)
+ - `no`
+ - `gssapi_authentication` Authenticate users based on GSSAPI? Valid values:
+ - `yes`
+ - `no` (default)
+ - `gssapi_cleanupcredentials` Destroy user's credential cache on logout? Valid values:
+ - `yes` (default)
+ - `no`
+ - `challenge_response_authentication` If you want to enable ChallengeResponseAuthentication or not When disabled, s/key passwords are disabled. Valid values:
+ - `yes`
+ - `no` (default)
+ - `tcp_forwarding` If you want to enable TcpForwarding. Valid values:
+ - `yes`
+ - `no` (default)
+ - `x11_forwarding` If you want to enable x11 forwarding. Valid values:
+ - `yes`
+ - `no` (default)
+ - `agent_forwarding` If you want to allow ssh-agent forwarding. Valid values:
+ - `yes`
+ - `no` (default)
+ - `pubkey_authentication` If you want to enable public key authentication. Valid values:
+ - `yes` (default)
+ - `no`
+ - `rsa_authentication` If you want to enable RSA Authentication. Valid values:
+ - `yes`
+ - `no` (default)
+ - `rhosts_rsa_authentication`
+ If you want to enable rhosts RSA Authentication. Valid values:
+ - `yes`
+ - `no` (default)
+ - `hostbased_authentication` If you want to enable `HostbasedAuthentication`. Valid values:
+ - `yes`
+ - `no` (default)
+ - `strict_modes` If you want to set `StrictModes` (check file modes/ownership before accepting login). Valid values:
+ - `yes` (default)
+ - `no`
+ - `permit_empty_passwords`
+ If you want enable PermitEmptyPasswords to allow empty passwords. Valid
+ Values:
+ - `yes`
+ - `no` (default)
+ - `ports` If you want to specify a list of ports other than the default `22`; Default: `[22]`
+ - `authorized_keys_file`
+ Set this to the location of the AuthorizedKeysFile
+ (e.g. `/etc/ssh/authorized_keys/%u`). Default: `AuthorizedKeysFile
+ %h/.ssh/authorized_keys`
+ - `hardened`
+ Use only strong ciphers, MAC, KexAlgorithms, etc.
+ Values:
+ - `no` (default)
+ - `yes`
+ - `print_motd`
+ Show the Message of the day when a user logs in.
+ - `sftp_subsystem`
+ Set a different sftp-subystem than the default one. Might be interesting for
+ sftponly usage. Default: empty -> no change of the default
+ - `head_additional_options`
+ Set this to any additional sshd_options which aren't listed above. Anything
+ set here will be added to the beginning of the sshd_config file. This option
+ might be useful to define complicated Match Blocks. This string is going to
+ be included, like it is defined. So take care! Default: empty -> not added.
+ - `tail_additional_options` Set this to any additional sshd_options which aren't listed above. Anything set here will be added to the end of the sshd_config file. This option might be useful to define complicated Match Blocks. This string is going to be included, like it is defined. So take care! Default: empty -> not added.
+ - `shared_ip` Whether the server uses a shared network IP address. If it does, then we don't want it to export an rsa key for its IP address. Values:
+ - `no` (default)
+ - `yes`
+
+
+### Defines and functions
+
+Deploy authorized_keys file with the define `authorized_key`.
+
+Generate a public/private keypair with the ssh_keygen function. For example, the
+following will generate ssh keys and put the different parts of the key into
+variables:
+
+```puppet
+$ssh_keys = ssh_keygen("${$ssh_key_basepath}/backup/keys/${::fqdn}/${backup_host}")
+$public_key = split($ssh_keys[1],' ')
+$sshkey_type => $public_key[0]
+$sshkey => $public_key[1]
+```
+
+## Client
+
+
+On a node where you wish to have the ssh client managed, you can do:
+
+```puppet
+class{'sshd::client':
+
+}
+```
+
+in the node definition. This will install the appropriate package.
+
+## License
+
+ - Copyright 2008-2011, Riseup Labs micah@riseup.net
+ - Copyright 2008, admin(at)immerda.ch
+ - Copyright 2008, Puzzle ITC GmbH
+ - Marcel Härry haerry+puppet(at)puzzle.ch
+ - Simon Josi josi+puppet(at)puzzle.ch
+
+This program is free software; you can redistribute
+it and/or modify it under the terms of the GNU
+General Public License version 3 as published by
+the Free Software Foundation.
+
diff --git a/puppet/modules/sshd/Rakefile b/puppet/modules/sshd/Rakefile
new file mode 100644
index 00000000..e3213518
--- /dev/null
+++ b/puppet/modules/sshd/Rakefile
@@ -0,0 +1,16 @@
+require 'bundler'
+Bundler.require(:rake)
+
+require 'puppetlabs_spec_helper/rake_tasks'
+require 'puppet-lint/tasks/puppet-lint'
+require 'rspec-system/rake_task'
+
+PuppetLint.configuration.log_format = '%{path}:%{linenumber}:%{KIND}: %{message}'
+PuppetLint.configuration.send("disable_80chars")
+
+puppet_module='sshd'
+task :librarian_spec_prep do
+ sh 'librarian-puppet install --path=spec/fixtures/modules/'
+end
+task :spec_prep => :librarian_spec_prep
+task :default => [:spec, :lint]
diff --git a/puppet/modules/sshd/files/autossh.init.d b/puppet/modules/sshd/files/autossh.init.d
new file mode 100644
index 00000000..92bd5f43
--- /dev/null
+++ b/puppet/modules/sshd/files/autossh.init.d
@@ -0,0 +1,164 @@
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides: AutoSSH
+# Required-Start: $local_fs $network $remote_fs $syslog
+# Required-Stop: $local_fs $network $remote_fs $syslog
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: start the autossh daemon
+# Description: start the autossh daemon
+### END INIT INFO
+
+# Author: Antoine Beaupré <anarcat@koumbit.org>
+
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="autossh"
+NAME=autossh
+USER=$NAME
+DAEMON=/usr/bin/autossh
+DAEMON_ARGS="-f"
+PIDFILE=/var/run/$NAME.pid
+SCRIPTNAME=/etc/init.d/$NAME
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+AUTOSSH_PIDFILE=$PIDFILE
+export AUTOSSH_PIDFILE
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
+# and status_of_proc is working.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+ # Return
+ # 0 if daemon has been started
+ # 1 if daemon was already running
+ # 2 if daemon could not be started
+ start-stop-daemon --start --quiet --user $USER --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
+ || return 1
+ start-stop-daemon --start --quiet --user $USER --chuid $USER --pidfile $PIDFILE --exec $DAEMON -- \
+ $DAEMON_ARGS \
+ || return 2
+ # The above code will not work for interpreted scripts, use the next
+ # six lines below instead (Ref: #643337, start-stop-daemon(8) )
+ #start-stop-daemon --start --quiet --pidfile $PIDFILE --startas $DAEMON \
+ # --name $NAME --test > /dev/null \
+ # || return 1
+ #start-stop-daemon --start --quiet --pidfile $PIDFILE --startas $DAEMON \
+ # --name $NAME -- $DAEMON_ARGS \
+ # || return 2
+
+ # Add code here, if necessary, that waits for the process to be ready
+ # to handle requests from services started subsequently which depend
+ # on this one. As a last resort, sleep for some time.
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+ # Return
+ # 0 if daemon has been stopped
+ # 1 if daemon was already stopped
+ # 2 if daemon could not be stopped
+ # other if a failure occurred
+ start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --user $USER --name $NAME
+ RETVAL="$?"
+ [ "$RETVAL" = 2 ] && return 2
+ # Wait for children to finish too if this is a daemon that forks
+ # and if the daemon is only ever run from this initscript.
+ # If the above conditions are not satisfied then add some other code
+ # that waits for the process to drop all resources that could be
+ # needed by services started subsequently. A last resort is to
+ # sleep for some time.
+ start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --user $USER --exec $DAEMON
+ [ "$?" = 2 ] && return 2
+ # Many daemons don't delete their pidfiles when they exit.
+ rm -f $PIDFILE
+ return "$RETVAL"
+}
+
+#
+# Function that sends a SIGHUP to the daemon/service
+#
+do_reload() {
+ #
+ # If the daemon can reload its configuration without
+ # restarting (for example, when it is sent a SIGHUP),
+ # then implement that here.
+ #
+ start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
+ return 0
+}
+
+case "$1" in
+ start)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
+ do_start
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ stop)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ status)
+ status_of_proc -p "$PIDFILE" "$DAEMON" "$NAME" && exit 0 || exit $?
+ ;;
+ reload|force-reload)
+ log_daemon_msg "Reloading $DESC" "$NAME"
+ do_reload
+ log_end_msg $?
+ ;;
+ restart)
+ #
+ # If the "reload" option is implemented then remove the
+ # 'force-reload' alias
+ #
+ log_daemon_msg "Restarting $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1)
+ do_start
+ case "$?" in
+ 0) log_end_msg 0 ;;
+ 1) log_end_msg 1 ;; # Old process is still running
+ *) log_end_msg 1 ;; # Failed to start
+ esac
+ ;;
+ *)
+ # Failed to stop
+ log_end_msg 1
+ ;;
+ esac
+ ;;
+ *)
+ #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
+ echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
+ exit 3
+ ;;
+esac
+
+:
diff --git a/puppet/modules/sshd/lib/facter/ssh_version.rb b/puppet/modules/sshd/lib/facter/ssh_version.rb
new file mode 100644
index 00000000..51d8a00f
--- /dev/null
+++ b/puppet/modules/sshd/lib/facter/ssh_version.rb
@@ -0,0 +1,5 @@
+Facter.add("ssh_version") do
+ setcode do
+ ssh_version = Facter::Util::Resolution.exec('ssh -V 2>&1 1>/dev/null').chomp.split(' ')[0].split('_')[1]
+ end
+end
diff --git a/puppet/modules/sshd/lib/puppet/parser/functions/ssh_keygen.rb b/puppet/modules/sshd/lib/puppet/parser/functions/ssh_keygen.rb
new file mode 100644
index 00000000..e304f242
--- /dev/null
+++ b/puppet/modules/sshd/lib/puppet/parser/functions/ssh_keygen.rb
@@ -0,0 +1,30 @@
+Puppet::Parser::Functions::newfunction(:ssh_keygen, :type => :rvalue, :doc =>
+ "Returns an array containing the ssh private and public (in this order) key
+ for a certain private key path.
+ It will generate the keypair if both do not exist. It will also generate
+ the directory hierarchy if required.
+ It accepts only fully qualified paths, everything else will fail.") do |args|
+ raise Puppet::ParseError, "Wrong number of arguments" unless args.to_a.length == 1
+ private_key_path = args.to_a[0]
+ raise Puppet::ParseError, "Only fully qualified paths are accepted (#{private_key_path})" unless private_key_path =~ /^\/.+/
+ public_key_path = "#{private_key_path}.pub"
+ raise Puppet::ParseError, "Either only the private or only the public key exists" if File.exists?(private_key_path) ^ File.exists?(public_key_path)
+ [private_key_path,public_key_path].each do |path|
+ raise Puppet::ParseError, "#{path} is a directory" if File.directory?(path)
+ end
+
+ dir = File.dirname(private_key_path)
+ unless File.directory?(dir)
+ require 'fileutils'
+ FileUtils.mkdir_p(dir, :mode => 0700)
+ end
+ unless [private_key_path,public_key_path].all?{|path| File.exists?(path) }
+ executor = (Facter.value(:puppetversion).to_i < 3) ? Puppet::Util : Puppet::Util::Execution
+ output = executor.execute(
+ ['/usr/bin/ssh-keygen','-t', 'rsa', '-b', '4096',
+ '-f', private_key_path, '-P', '', '-q'])
+ raise Puppet::ParseError, "Something went wrong during key generation! Output: #{output}" unless output.empty?
+ end
+ [File.read(private_key_path),File.read(public_key_path)]
+end
+
diff --git a/puppet/modules/sshd/manifests/autossh.pp b/puppet/modules/sshd/manifests/autossh.pp
new file mode 100644
index 00000000..5650584a
--- /dev/null
+++ b/puppet/modules/sshd/manifests/autossh.pp
@@ -0,0 +1,40 @@
+class sshd::autossh($host,
+ $port = undef, # this should be a remote->local hash
+ $remote_user = undef,
+ $user = 'root',
+ $pidfile = '/var/run/autossh.pid',
+) {
+ if $port {
+ $port_ensure = $port
+ }
+ else {
+ # random port between 10000 and 20000
+ $port_ensure = fqdn_rand(10000) + 10000
+ }
+ if $remote_user {
+ $remote_user_ensure = $remote_user
+ }
+ else {
+ $remote_user_ensure = "host-$fqdn"
+ }
+ file {
+ '/etc/init.d/autossh':
+ mode => '0555',
+ source => 'puppet:///modules/sshd/autossh.init.d';
+ '/etc/default/autossh':
+ mode => '0444',
+ content => "USER=$user\nPIDFILE=$pidfile\nDAEMON_ARGS='-M0 -f -o ServerAliveInterval=15 -o ServerAliveCountMax=4 -q -N -R $port_ensure:localhost:22 $remote_user_ensure@$host'\n";
+ }
+ package { 'autossh':
+ ensure => present,
+ }
+ service { 'autossh':
+ ensure => running,
+ enable => true,
+ subscribe => [
+ File['/etc/init.d/autossh'],
+ File['/etc/default/autossh'],
+ Package['autossh'],
+ ],
+ }
+}
diff --git a/puppet/modules/sshd/manifests/base.pp b/puppet/modules/sshd/manifests/base.pp
new file mode 100644
index 00000000..dda9f26c
--- /dev/null
+++ b/puppet/modules/sshd/manifests/base.pp
@@ -0,0 +1,41 @@
+# The base class to setup the common things.
+# This is a private class and will always be used
+# throught the sshd class itself.
+class sshd::base {
+
+ $sshd_config_content = $::operatingsystem ? {
+ 'CentOS' => template("sshd/sshd_config/${::operatingsystem}_${::operatingsystemmajrelease}.erb"),
+ default => $::lsbdistcodename ? {
+ '' => template("sshd/sshd_config/${::operatingsystem}.erb"),
+ default => template("sshd/sshd_config/${::operatingsystem}_${::lsbdistcodename}.erb")
+ }
+ }
+
+ file { 'sshd_config':
+ ensure => present,
+ path => '/etc/ssh/sshd_config',
+ content => $sshd_config_content,
+ notify => Service[sshd],
+ owner => root,
+ group => 0,
+ mode => '0600';
+ }
+
+ # Now add the key, if we've got one
+ case $::sshrsakey {
+ '': { info("no sshrsakey on ${::fqdn}") }
+ default: {
+ # only export sshkey when storedconfigs is enabled
+ if $::sshd::use_storedconfigs {
+ include ::sshd::sshkey
+ }
+ }
+ }
+ service{'sshd':
+ ensure => running,
+ name => 'sshd',
+ enable => true,
+ hasstatus => true,
+ require => File[sshd_config],
+ }
+}
diff --git a/puppet/modules/sshd/manifests/client.pp b/puppet/modules/sshd/manifests/client.pp
new file mode 100644
index 00000000..84dd7abc
--- /dev/null
+++ b/puppet/modules/sshd/manifests/client.pp
@@ -0,0 +1,22 @@
+# manifests/client.pp
+
+class sshd::client(
+ $shared_ip = 'no',
+ $ensure_version = 'installed',
+ $manage_shorewall = false
+) {
+
+ case $::operatingsystem {
+ debian,ubuntu: { include sshd::client::debian }
+ default: {
+ case $::kernel {
+ linux: { include sshd::client::linux }
+ default: { include sshd::client::base }
+ }
+ }
+ }
+
+ if $manage_shorewall{
+ include shorewall::rules::out::ssh
+ }
+}
diff --git a/puppet/modules/sshd/manifests/client/base.pp b/puppet/modules/sshd/manifests/client/base.pp
new file mode 100644
index 00000000..4925c2d0
--- /dev/null
+++ b/puppet/modules/sshd/manifests/client/base.pp
@@ -0,0 +1,15 @@
+class sshd::client::base {
+ # this is needed because the gid might have changed
+ file { '/etc/ssh/ssh_known_hosts':
+ ensure => present,
+ mode => '0644',
+ owner => root,
+ group => 0;
+ }
+
+ # Now collect all server keys
+ case $sshd::client::shared_ip {
+ no: { Sshkey <<||>> }
+ yes: { Sshkey <<| tag == fqdn |>> }
+ }
+}
diff --git a/puppet/modules/sshd/manifests/client/debian.pp b/puppet/modules/sshd/manifests/client/debian.pp
new file mode 100644
index 00000000..2aaf3fb1
--- /dev/null
+++ b/puppet/modules/sshd/manifests/client/debian.pp
@@ -0,0 +1,5 @@
+class sshd::client::debian inherits sshd::client::linux {
+ Package['openssh-clients']{
+ name => 'openssh-client',
+ }
+}
diff --git a/puppet/modules/sshd/manifests/client/linux.pp b/puppet/modules/sshd/manifests/client/linux.pp
new file mode 100644
index 00000000..0c420be2
--- /dev/null
+++ b/puppet/modules/sshd/manifests/client/linux.pp
@@ -0,0 +1,5 @@
+class sshd::client::linux inherits sshd::client::base {
+ package {'openssh-clients':
+ ensure => $sshd::client::ensure_version,
+ }
+}
diff --git a/puppet/modules/sshd/manifests/debian.pp b/puppet/modules/sshd/manifests/debian.pp
new file mode 100644
index 00000000..d827078a
--- /dev/null
+++ b/puppet/modules/sshd/manifests/debian.pp
@@ -0,0 +1,13 @@
+class sshd::debian inherits sshd::linux {
+
+ Package[openssh]{
+ name => 'openssh-server',
+ }
+
+ Service[sshd]{
+ name => 'ssh',
+ pattern => 'sshd',
+ hasstatus => true,
+ hasrestart => true,
+ }
+}
diff --git a/puppet/modules/sshd/manifests/gentoo.pp b/puppet/modules/sshd/manifests/gentoo.pp
new file mode 100644
index 00000000..631f3d19
--- /dev/null
+++ b/puppet/modules/sshd/manifests/gentoo.pp
@@ -0,0 +1,5 @@
+class sshd::gentoo inherits sshd::linux {
+ Package[openssh]{
+ category => 'net-misc',
+ }
+}
diff --git a/puppet/modules/sshd/manifests/init.pp b/puppet/modules/sshd/manifests/init.pp
new file mode 100644
index 00000000..b4157418
--- /dev/null
+++ b/puppet/modules/sshd/manifests/init.pp
@@ -0,0 +1,92 @@
+# manage an sshd installation
+class sshd(
+ $manage_nagios = false,
+ $nagios_check_ssh_hostname = 'absent',
+ $ports = [ 22 ],
+ $shared_ip = 'no',
+ $ensure_version = 'installed',
+ $listen_address = [ '0.0.0.0', '::' ],
+ $allowed_users = '',
+ $allowed_groups = '',
+ $use_pam = 'no',
+ $permit_root_login = 'without-password',
+ $password_authentication = 'no',
+ $kerberos_authentication = 'no',
+ $kerberos_orlocalpasswd = 'yes',
+ $kerberos_ticketcleanup = 'yes',
+ $gssapi_authentication = 'no',
+ $gssapi_cleanupcredentials = 'yes',
+ $tcp_forwarding = 'no',
+ $x11_forwarding = 'no',
+ $agent_forwarding = 'no',
+ $challenge_response_authentication = 'no',
+ $pubkey_authentication = 'yes',
+ $rsa_authentication = 'no',
+ $strict_modes = 'yes',
+ $ignore_rhosts = 'yes',
+ $rhosts_rsa_authentication = 'no',
+ $hostbased_authentication = 'no',
+ $permit_empty_passwords = 'no',
+ $authorized_keys_file = $::osfamily ? {
+ Debian => $::lsbmajdistrelease ? {
+ 6 => '%h/.ssh/authorized_keys',
+ default => '%h/.ssh/authorized_keys %h/.ssh/authorized_keys2',
+ },
+ RedHat => $::operatingsystemmajrelease ? {
+ 5 => '%h/.ssh/authorized_keys',
+ 6 => '%h/.ssh/authorized_keys',
+ default => '%h/.ssh/authorized_keys %h/.ssh/authorized_keys2',
+ },
+ OpenBSD => '%h/.ssh/authorized_keys',
+ default => '%h/.ssh/authorized_keys %h/.ssh/authorized_keys2',
+ },
+ $hardened = 'no',
+ $sftp_subsystem = '',
+ $head_additional_options = '',
+ $tail_additional_options = '',
+ $print_motd = 'yes',
+ $manage_shorewall = false,
+ $shorewall_source = 'net',
+ $sshkey_ipaddress = $::ipaddress,
+ $manage_client = true,
+ $hostkey_type = versioncmp($::ssh_version, '6.5') ? {
+ /(^1|0)/ => [ 'rsa', 'ed25519' ],
+ /-1/ => [ 'rsa', 'dsa' ]
+ },
+ $use_storedconfigs = true
+) {
+
+ validate_bool($manage_shorewall)
+ validate_bool($manage_client)
+ validate_array($listen_address)
+ validate_array($ports)
+
+ if $manage_client {
+ class{'sshd::client':
+ shared_ip => $shared_ip,
+ ensure_version => $ensure_version,
+ manage_shorewall => $manage_shorewall,
+ }
+ }
+
+ case $::operatingsystem {
+ gentoo: { include sshd::gentoo }
+ redhat,centos: { include sshd::redhat }
+ openbsd: { include sshd::openbsd }
+ debian,ubuntu: { include sshd::debian }
+ default: { include sshd::base }
+ }
+
+ if $manage_nagios {
+ sshd::nagios{$ports:
+ check_hostname => $nagios_check_ssh_hostname
+ }
+ }
+
+ if $manage_shorewall {
+ class{'shorewall::rules::ssh':
+ ports => $ports,
+ source => $shorewall_source
+ }
+ }
+}
diff --git a/puppet/modules/sshd/manifests/libssh2.pp b/puppet/modules/sshd/manifests/libssh2.pp
new file mode 100644
index 00000000..403ac7be
--- /dev/null
+++ b/puppet/modules/sshd/manifests/libssh2.pp
@@ -0,0 +1,7 @@
+# manifests/libssh2.pp
+
+class sshd::libssh2 {
+ package{'libssh2':
+ ensure => present,
+ }
+}
diff --git a/puppet/modules/sshd/manifests/libssh2/devel.pp b/puppet/modules/sshd/manifests/libssh2/devel.pp
new file mode 100644
index 00000000..261e34c8
--- /dev/null
+++ b/puppet/modules/sshd/manifests/libssh2/devel.pp
@@ -0,0 +1,7 @@
+# manifests/libssh2/devel.pp
+
+class sshd::libssh2::devel inherits sshd::libssh2 {
+ package{"libssh2-devel.${::architecture}":
+ ensure => installed,
+ }
+}
diff --git a/puppet/modules/sshd/manifests/linux.pp b/puppet/modules/sshd/manifests/linux.pp
new file mode 100644
index 00000000..8628ff5e
--- /dev/null
+++ b/puppet/modules/sshd/manifests/linux.pp
@@ -0,0 +1,8 @@
+class sshd::linux inherits sshd::base {
+ package{'openssh':
+ ensure => $sshd::ensure_version,
+ }
+ File[sshd_config]{
+ require +> Package[openssh],
+ }
+}
diff --git a/puppet/modules/sshd/manifests/nagios.pp b/puppet/modules/sshd/manifests/nagios.pp
new file mode 100644
index 00000000..6921de91
--- /dev/null
+++ b/puppet/modules/sshd/manifests/nagios.pp
@@ -0,0 +1,24 @@
+define sshd::nagios(
+ $port = 'absent',
+ $ensure = 'present',
+ $check_hostname = 'absent'
+) {
+ $real_port = $port ? {
+ 'absent' => $name,
+ default => $port,
+ }
+ case $check_hostname {
+ 'absent': {
+ nagios::service{"ssh_port_${name}":
+ ensure => $ensure,
+ check_command => "check_ssh_port!${real_port}"
+ }
+ }
+ default: {
+ nagios::service{"ssh_port_host_${name}":
+ ensure => $ensure,
+ check_command => "check_ssh_port_host!${real_port}!${check_hostname}"
+ }
+ }
+ }
+}
diff --git a/puppet/modules/sshd/manifests/openbsd.pp b/puppet/modules/sshd/manifests/openbsd.pp
new file mode 100644
index 00000000..cb6dbba6
--- /dev/null
+++ b/puppet/modules/sshd/manifests/openbsd.pp
@@ -0,0 +1,8 @@
+class sshd::openbsd inherits sshd::base {
+ Service[sshd]{
+ restart => '/bin/kill -HUP `/bin/cat /var/run/sshd.pid`',
+ stop => '/bin/kill `/bin/cat /var/run/sshd.pid`',
+ start => '/usr/sbin/sshd',
+ status => '/usr/bin/pgrep -f /usr/sbin/sshd',
+ }
+}
diff --git a/puppet/modules/sshd/manifests/redhat.pp b/puppet/modules/sshd/manifests/redhat.pp
new file mode 100644
index 00000000..d7201774
--- /dev/null
+++ b/puppet/modules/sshd/manifests/redhat.pp
@@ -0,0 +1,5 @@
+class sshd::redhat inherits sshd::linux {
+ Package[openssh]{
+ name => 'openssh-server',
+ }
+}
diff --git a/puppet/modules/sshd/manifests/ssh_authorized_key.pp b/puppet/modules/sshd/manifests/ssh_authorized_key.pp
new file mode 100644
index 00000000..80cb3b70
--- /dev/null
+++ b/puppet/modules/sshd/manifests/ssh_authorized_key.pp
@@ -0,0 +1,85 @@
+# wrapper to have some defaults.
+define sshd::ssh_authorized_key(
+ $ensure = 'present',
+ $type = 'ssh-dss',
+ $key = 'absent',
+ $user = '',
+ $target = undef,
+ $options = 'absent',
+ $override_builtin = undef
+){
+
+ if ($ensure=='present') and ($key=='absent') {
+ fail("You have to set \$key for Sshd::Ssh_authorized_key[${name}]!")
+ }
+
+ $real_user = $user ? {
+ false => $name,
+ '' => $name,
+ default => $user,
+ }
+
+ case $target {
+ undef,'': {
+ case $real_user {
+ 'root': { $real_target = '/root/.ssh/authorized_keys' }
+ default: { $real_target = "/home/${real_user}/.ssh/authorized_keys" }
+ }
+ }
+ default: {
+ $real_target = $target
+ }
+ }
+
+ # The ssh_authorized_key built-in function (in 2.7.23 at least)
+ # will not write an authorized_keys file for a mortal user to
+ # a directory they don't have write permission to, puppet attempts to
+ # create the file as the user specified with the user parameter and fails.
+ # Since ssh will refuse to use authorized_keys files not owned by the
+ # user, or in files/directories that allow other users to write, this
+ # behavior is deliberate in order to prevent typical non-working
+ # configurations. However, it also prevents the case of puppet, running
+ # as root, writing a file owned by a mortal user to a common
+ # authorized_keys directory such as one might specify in sshd_config with
+ # something like
+ # 'AuthorizedKeysFile /etc/ssh/authorized_keys/%u'
+ # So we provide a way to override the built-in and instead just install
+ # via a file resource. There is no additional security risk here, it's
+ # nothing a user can't already do by writing their own file resources,
+ # we still depend on the filesystem permissions to keep things safe.
+ if $override_builtin {
+ $header = "# HEADER: This file is managed by Puppet.\n"
+
+ if $options == 'absent' {
+ info("not setting any option for ssh_authorized_key: ${name}")
+ $content = "${header}${type} ${key}\n"
+ } else {
+ $content = "${header}${options} ${type} ${key}\n"
+ }
+
+ file { $real_target:
+ ensure => $ensure,
+ content => $content,
+ owner => $real_user,
+ mode => '0600',
+ }
+
+ } else {
+
+ if $options == 'absent' {
+ info("not setting any option for ssh_authorized_key: ${name}")
+ } else {
+ $real_options = $options
+ }
+
+ ssh_authorized_key{$name:
+ ensure => $ensure,
+ type => $type,
+ key => $key,
+ user => $real_user,
+ target => $real_target,
+ options => $real_options,
+ }
+ }
+
+}
diff --git a/puppet/modules/sshd/manifests/sshkey.pp b/puppet/modules/sshd/manifests/sshkey.pp
new file mode 100644
index 00000000..df37a66c
--- /dev/null
+++ b/puppet/modules/sshd/manifests/sshkey.pp
@@ -0,0 +1,21 @@
+# deploys the
+class sshd::sshkey {
+
+ @@sshkey{$::fqdn:
+ ensure => present,
+ tag => 'fqdn',
+ type => 'ssh-rsa',
+ key => $::sshrsakey,
+ }
+
+ # In case the node has uses a shared network address,
+ # we don't define a sshkey resource using an IP address
+ if $sshd::shared_ip == 'no' {
+ @@sshkey{$::sshd::sshkey_ipaddress:
+ ensure => present,
+ tag => 'ipaddress',
+ type => 'ssh-rsa',
+ key => $::sshrsakey,
+ }
+ }
+}
diff --git a/puppet/modules/sshd/spec/classes/client_spec.rb b/puppet/modules/sshd/spec/classes/client_spec.rb
new file mode 100644
index 00000000..bd3e35af
--- /dev/null
+++ b/puppet/modules/sshd/spec/classes/client_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+
+describe 'sshd::client' do
+
+ shared_examples "a Linux OS" do
+ it { should contain_file('/etc/ssh/ssh_known_hosts').with(
+ {
+ 'ensure' => 'present',
+ 'owner' => 'root',
+ 'group' => '0',
+ 'mode' => '0644',
+ }
+ )}
+ end
+
+ context "Debian OS" do
+ let :facts do
+ {
+ :operatingsystem => 'Debian',
+ :osfamily => 'Debian',
+ :lsbdistcodename => 'wheezy',
+ }
+ end
+ it_behaves_like "a Linux OS"
+ it { should contain_package('openssh-clients').with({
+ 'name' => 'openssh-client'
+ }) }
+ end
+
+ context "CentOS" do
+ it_behaves_like "a Linux OS" do
+ let :facts do
+ {
+ :operatingsystem => 'CentOS',
+ :osfamily => 'RedHat',
+ :lsbdistcodename => 'Final',
+ }
+ end
+ end
+ end
+
+end \ No newline at end of file
diff --git a/puppet/modules/sshd/spec/classes/init_spec.rb b/puppet/modules/sshd/spec/classes/init_spec.rb
new file mode 100644
index 00000000..e3003d14
--- /dev/null
+++ b/puppet/modules/sshd/spec/classes/init_spec.rb
@@ -0,0 +1,122 @@
+require 'spec_helper'
+
+describe 'sshd' do
+
+ shared_examples "a Linux OS" do
+ it { should compile.with_all_deps }
+ it { should contain_class('sshd') }
+ it { should contain_class('sshd::client') }
+
+ it { should contain_service('sshd').with({
+ :ensure => 'running',
+ :enable => true,
+ :hasstatus => true
+ })}
+
+ it { should contain_file('sshd_config').with(
+ {
+ 'ensure' => 'present',
+ 'owner' => 'root',
+ 'group' => '0',
+ 'mode' => '0600',
+ }
+ )}
+
+ context 'change ssh port' do
+ let(:params){{
+ :ports => [ 22222],
+ }}
+ it { should contain_file(
+ 'sshd_config'
+ ).with_content(/Port 22222/)}
+ end
+ end
+
+ context "Debian OS" do
+ let :facts do
+ {
+ :operatingsystem => 'Debian',
+ :osfamily => 'Debian',
+ :lsbdistcodename => 'wheezy',
+ }
+ end
+ it_behaves_like "a Linux OS"
+ it { should contain_package('openssh') }
+ it { should contain_class('sshd::debian') }
+ it { should contain_service('sshd').with(
+ :hasrestart => true
+ )}
+
+ context "Ubuntu" do
+ let :facts do
+ {
+ :operatingsystem => 'Ubuntu',
+ :lsbdistcodename => 'precise',
+ }
+ end
+ it_behaves_like "a Linux OS"
+ it { should contain_package('openssh') }
+ it { should contain_service('sshd').with({
+ :hasrestart => true
+ })}
+ end
+ end
+
+
+# context "RedHat OS" do
+# it_behaves_like "a Linux OS" do
+# let :facts do
+# {
+# :operatingsystem => 'RedHat',
+# :osfamily => 'RedHat',
+# }
+# end
+# end
+# end
+
+ context "CentOS" do
+ it_behaves_like "a Linux OS" do
+ let :facts do
+ {
+ :operatingsystem => 'CentOS',
+ :osfamily => 'RedHat',
+ :lsbdistcodename => 'Final',
+ }
+ end
+ end
+ end
+
+ context "Gentoo" do
+ let :facts do
+ {
+ :operatingsystem => 'Gentoo',
+ :osfamily => 'Gentoo',
+ }
+ end
+ it_behaves_like "a Linux OS"
+ it { should contain_class('sshd::gentoo') }
+ end
+
+ context "OpenBSD" do
+ let :facts do
+ {
+ :operatingsystem => 'OpenBSD',
+ :osfamily => 'OpenBSD',
+ }
+ end
+ it_behaves_like "a Linux OS"
+ it { should contain_class('sshd::openbsd') }
+ end
+
+# context "FreeBSD" do
+# it_behaves_like "a Linux OS" do
+# let :facts do
+# {
+# :operatingsystem => 'FreeBSD',
+# :osfamily => 'FreeBSD',
+# }
+# end
+# end
+# end
+
+end \ No newline at end of file
diff --git a/puppet/modules/sshd/spec/defines/ssh_authorized_key_spec.rb b/puppet/modules/sshd/spec/defines/ssh_authorized_key_spec.rb
new file mode 100644
index 00000000..c73a91cc
--- /dev/null
+++ b/puppet/modules/sshd/spec/defines/ssh_authorized_key_spec.rb
@@ -0,0 +1,45 @@
+require 'spec_helper'
+
+describe 'sshd::ssh_authorized_key' do
+
+ context 'manage authorized key' do
+ let(:title) { 'foo' }
+ let(:ssh_key) { 'some_secret_ssh_key' }
+
+ let(:params) {{
+ :key => ssh_key,
+ }}
+
+ it { should contain_ssh_authorized_key('foo').with({
+ 'ensure' => 'present',
+ 'type' => 'ssh-dss',
+ 'user' => 'foo',
+ 'target' => '/home/foo/.ssh/authorized_keys',
+ 'key' => ssh_key,
+ })
+ }
+ end
+ context 'manage authoried key with options' do
+ let(:title) { 'foo2' }
+ let(:ssh_key) { 'some_secret_ssh_key' }
+
+ let(:params) {{
+ :key => ssh_key,
+ :options => ['command="/usr/bin/date"',
+ 'no-pty','no-X11-forwarding','no-agent-forwarding',
+ 'no-port-forwarding']
+ }}
+
+ it { should contain_ssh_authorized_key('foo2').with({
+ 'ensure' => 'present',
+ 'type' => 'ssh-dss',
+ 'user' => 'foo2',
+ 'target' => '/home/foo2/.ssh/authorized_keys',
+ 'key' => ssh_key,
+ 'options' => ['command="/usr/bin/date"',
+ 'no-pty','no-X11-forwarding','no-agent-forwarding',
+ 'no-port-forwarding']
+ })
+ }
+ end
+end
diff --git a/puppet/modules/sshd/spec/functions/ssh_keygen_spec.rb b/puppet/modules/sshd/spec/functions/ssh_keygen_spec.rb
new file mode 100644
index 00000000..a6b51173
--- /dev/null
+++ b/puppet/modules/sshd/spec/functions/ssh_keygen_spec.rb
@@ -0,0 +1,116 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+require 'rspec-puppet'
+require 'mocha'
+require 'fileutils'
+
+describe 'ssh_keygen' do
+
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it 'should exist' do
+ Puppet::Parser::Functions.function("ssh_keygen").should == "function_ssh_keygen"
+ end
+
+ it 'should raise a ParseError if no argument is passed' do
+ lambda {
+ scope.function_ssh_keygen([])
+ }.should(raise_error(Puppet::ParseError))
+ end
+
+ it 'should raise a ParseError if there is more than 1 arguments' do
+ lambda {
+ scope.function_ssh_keygen(["foo", "bar"])
+ }.should( raise_error(Puppet::ParseError))
+ end
+
+ it 'should raise a ParseError if the argument is not fully qualified' do
+ lambda {
+ scope.function_ssh_keygen(["foo"])
+ }.should( raise_error(Puppet::ParseError))
+ end
+
+ it "should raise a ParseError if the private key path is a directory" do
+ File.stubs(:directory?).with("/some_dir").returns(true)
+ lambda {
+ scope.function_ssh_keygen(["/some_dir"])
+ }.should( raise_error(Puppet::ParseError))
+ end
+
+ it "should raise a ParseError if the public key path is a directory" do
+ File.stubs(:directory?).with("/some_dir.pub").returns(true)
+ lambda {
+ scope.function_ssh_keygen(["/some_dir.pub"])
+ }.should( raise_error(Puppet::ParseError))
+ end
+
+ describe 'when executing properly' do
+ before do
+ File.stubs(:directory?).with('/tmp/a/b/c').returns(false)
+ File.stubs(:directory?).with('/tmp/a/b/c.pub').returns(false)
+ File.stubs(:read).with('/tmp/a/b/c').returns('privatekey')
+ File.stubs(:read).with('/tmp/a/b/c.pub').returns('publickey')
+ end
+
+ it 'should fail if the public but not the private key exists' do
+ File.stubs(:exists?).with('/tmp/a/b/c').returns(true)
+ File.stubs(:exists?).with('/tmp/a/b/c.pub').returns(false)
+ lambda {
+ scope.function_ssh_keygen(['/tmp/a/b/c'])
+ }.should( raise_error(Puppet::ParseError))
+ end
+
+ it "should fail if the private but not the public key exists" do
+ File.stubs(:exists?).with("/tmp/a/b/c").returns(false)
+ File.stubs(:exists?).with("/tmp/a/b/c.pub").returns(true)
+ lambda {
+ scope.function_ssh_keygen(["/tmp/a/b/c"])
+ }.should( raise_error(Puppet::ParseError))
+ end
+
+
+ it "should return an array of size 2 with the right conent if the keyfiles exists" do
+ File.stubs(:exists?).with("/tmp/a/b/c").returns(true)
+ File.stubs(:exists?).with("/tmp/a/b/c.pub").returns(true)
+ File.stubs(:directory?).with('/tmp/a/b').returns(true)
+ Puppet::Util.expects(:execute).never
+ result = scope.function_ssh_keygen(['/tmp/a/b/c'])
+ result.length.should == 2
+ result[0].should == 'privatekey'
+ result[1].should == 'publickey'
+ end
+
+ it "should create the directory path if it does not exist" do
+ File.stubs(:exists?).with("/tmp/a/b/c").returns(false)
+ File.stubs(:exists?).with("/tmp/a/b/c.pub").returns(false)
+ File.stubs(:directory?).with("/tmp/a/b").returns(false)
+ FileUtils.expects(:mkdir_p).with("/tmp/a/b", :mode => 0700)
+ Puppet::Util::Execution.expects(:execute).returns("")
+ result = scope.function_ssh_keygen(['/tmp/a/b/c'])
+ result.length.should == 2
+ result[0].should == 'privatekey'
+ result[1].should == 'publickey'
+ end
+
+ it "should generate the key if the keyfiles do not exist" do
+ File.stubs(:exists?).with("/tmp/a/b/c").returns(false)
+ File.stubs(:exists?).with("/tmp/a/b/c.pub").returns(false)
+ File.stubs(:directory?).with("/tmp/a/b").returns(true)
+ Puppet::Util::Execution.expects(:execute).with(['/usr/bin/ssh-keygen','-t', 'rsa', '-b', '4096', '-f', '/tmp/a/b/c', '-P', '', '-q']).returns("")
+ result = scope.function_ssh_keygen(['/tmp/a/b/c'])
+ result.length.should == 2
+ result[0].should == 'privatekey'
+ result[1].should == 'publickey'
+ end
+
+ it "should fail if something goes wrong during generation" do
+ File.stubs(:exists?).with("/tmp/a/b/c").returns(false)
+ File.stubs(:exists?).with("/tmp/a/b/c.pub").returns(false)
+ File.stubs(:directory?).with("/tmp/a/b").returns(true)
+ Puppet::Util::Execution.expects(:execute).with(['/usr/bin/ssh-keygen','-t', 'rsa', '-b', '4096', '-f', '/tmp/a/b/c', '-P', '', '-q']).returns("something is wrong")
+ lambda {
+ scope.function_ssh_keygen(["/tmp/a/b/c"])
+ }.should( raise_error(Puppet::ParseError))
+ end
+ end
+end
diff --git a/puppet/modules/sshd/spec/spec_helper.rb b/puppet/modules/sshd/spec/spec_helper.rb
new file mode 100644
index 00000000..b4123fde
--- /dev/null
+++ b/puppet/modules/sshd/spec/spec_helper.rb
@@ -0,0 +1,21 @@
+dir = File.expand_path(File.dirname(__FILE__))
+$LOAD_PATH.unshift File.join(dir, 'lib')
+require 'puppet'
+require 'rspec'
+require 'puppetlabs_spec_helper/module_spec_helper'
+#require 'rspec-hiera-puppet'
+require 'rspec-puppet/coverage'
+require 'rspec/autorun'
+
+fixture_path = File.expand_path(File.join(__FILE__, '..', 'fixtures'))
+
+RSpec.configure do |c|
+ c.module_path = File.join(fixture_path, 'modules')
+ c.manifest_dir = File.join(fixture_path, 'manifests')
+ c.pattern = "spec/*/*_spec.rb"
+end
+
+Puppet::Util::Log.level = :warning
+Puppet::Util::Log.newdestination(:console)
+
+at_exit { RSpec::Puppet::Coverage.report! } \ No newline at end of file
diff --git a/puppet/modules/sshd/spec/spec_helper_system.rb b/puppet/modules/sshd/spec/spec_helper_system.rb
new file mode 100644
index 00000000..2c6812fc
--- /dev/null
+++ b/puppet/modules/sshd/spec/spec_helper_system.rb
@@ -0,0 +1,25 @@
+require 'rspec-system/spec_helper'
+require 'rspec-system-puppet/helpers'
+require 'rspec-system-serverspec/helpers'
+include Serverspec::Helper::RSpecSystem
+include Serverspec::Helper::DetectOS
+include RSpecSystemPuppet::Helpers
+
+RSpec.configure do |c|
+ # Project root
+ proj_root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
+
+ # Enable colour
+ c.tty = true
+
+ c.include RSpecSystemPuppet::Helpers
+
+ # This is where we 'setup' the nodes before running our tests
+ c.before :suite do
+ # Install puppet
+ puppet_install
+ # Install modules and dependencies
+ puppet_module_install(:source => proj_root, :module_name => 'sshd')
+ shell('puppet module install puppetlabs-stdlib')
+ end
+end
diff --git a/puppet/modules/sshd/templates/sshd_config/CentOS_5.erb b/puppet/modules/sshd/templates/sshd_config/CentOS_5.erb
new file mode 120000
index 00000000..71b767a5
--- /dev/null
+++ b/puppet/modules/sshd/templates/sshd_config/CentOS_5.erb
@@ -0,0 +1 @@
+CentOS_6.erb \ No newline at end of file
diff --git a/puppet/modules/sshd/templates/sshd_config/CentOS_6.erb b/puppet/modules/sshd/templates/sshd_config/CentOS_6.erb
new file mode 100644
index 00000000..4593a91a
--- /dev/null
+++ b/puppet/modules/sshd/templates/sshd_config/CentOS_6.erb
@@ -0,0 +1,172 @@
+# $OpenBSD: sshd_config,v 1.73 2005/12/06 22:38:28 reyk Exp $
+
+# This is the sshd server system-wide configuration file. See
+# sshd_config(5) for more information.
+
+# This sshd was compiled with PATH=/usr/local/bin:/bin:/usr/bin
+
+# The strategy used for options in the default sshd_config shipped with
+# OpenSSH is to specify options with their default value where
+# possible, but leave them commented. Uncommented options change a
+# default value.
+
+<% unless (s=scope.lookupvar('::sshd::head_additional_options')).empty? -%>
+<%= s %>
+<% end -%>
+
+<% scope.lookupvar('::sshd::ports').to_a.each do |port| -%>
+<% if port == 'off' -%>
+#Port -- disabled by puppet
+<% else -%>
+Port <%= port %>
+<% end -%>
+<% end -%>
+
+# Use these options to restrict which interfaces/protocols sshd will bind to
+<% scope.lookupvar('::sshd::listen_address').to_a.each do |address| -%>
+ListenAddress <%= address %>
+<% end -%>
+
+# Disable legacy (protocol version 1) support in the server for new
+# installations. In future the default will change to require explicit
+# activation of protocol 1
+Protocol 2
+
+# HostKey for protocol version 1
+#HostKey /etc/ssh/ssh_host_key
+# HostKeys for protocol version 2
+#HostKey /etc/ssh/ssh_host_rsa_key
+#HostKey /etc/ssh/ssh_host_dsa_key
+
+# Lifetime and size of ephemeral version 1 server key
+#KeyRegenerationInterval 1h
+#ServerKeyBits 1024
+
+# Logging
+# obsoletes QuietMode and FascistLogging
+#SyslogFacility AUTH
+SyslogFacility AUTHPRIV
+#LogLevel INFO
+
+# Authentication:
+
+#LoginGraceTime 2m
+PermitRootLogin <%= scope.lookupvar('::sshd::permit_root_login') %>
+
+StrictModes <%= scope.lookupvar('::sshd::strict_modes') %>
+
+#MaxAuthTries 6
+
+RSAAuthentication <%= scope.lookupvar('::sshd::rsa_authentication') %>
+PubkeyAuthentication <%= scope.lookupvar('::sshd::pubkey_authentication') %>
+AuthorizedKeysFile <%= scope.lookupvar('::sshd::authorized_keys_file') %>
+#AuthorizedKeysCommand none
+#AuthorizedKeysCommandRunAs nobody
+
+# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts
+RhostsRSAAuthentication <%= scope.lookupvar('::sshd::rhosts_rsa_authentication') %>
+
+# similar for protocol version 2
+HostbasedAuthentication <%= scope.lookupvar('::sshd::hostbased_authentication') %>
+
+# Change to yes if you don't trust ~/.ssh/known_hosts for
+# RhostsRSAAuthentication and HostbasedAuthentication
+#IgnoreUserKnownHosts no
+
+# Don't read the user's ~/.rhosts and ~/.shosts files
+IgnoreRhosts <%= scope.lookupvar('::sshd::ignore_rhosts') %>
+
+# To disable tunneled clear text passwords, change to no here!
+PasswordAuthentication <%= scope.lookupvar('::sshd::password_authentication') %>
+
+# To enable empty passwords, change to yes (NOT RECOMMENDED)
+PermitEmptyPasswords <%= scope.lookupvar('::sshd::permit_empty_passwords') %>
+
+# Change to no to disable s/key passwords
+ChallengeResponseAuthentication <%= scope.lookupvar('::sshd::challenge_response_authentication') %>
+
+# Kerberos options
+#KerberosAuthentication no
+#KerberosOrLocalPasswd yes
+#KerberosTicketCleanup yes
+#KerberosGetAFSToken no
+#KerberosUseKuserok yes
+
+# GSSAPI options
+#GSSAPIAuthentication no
+#GSSAPICleanupCredentials yes
+
+# Set this to 'yes' to enable PAM authentication, account processing,
+# and session processing. If this is enabled, PAM authentication will
+# be allowed through the ChallengeResponseAuthentication and
+# PasswordAuthentication. Depending on your PAM configuration,
+# PAM authentication via ChallengeResponseAuthentication may bypass
+# the setting of "PermitRootLogin without-password".
+# If you just want the PAM account and session checks to run without
+# PAM authentication, then enable this but set PasswordAuthentication
+# and ChallengeResponseAuthentication to 'no'.
+#UsePAM no
+UsePAM <%= scope.lookupvar('::sshd::use_pam') %>
+
+# Accept locale-related environment variables
+AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES
+AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT
+AcceptEnv LC_IDENTIFICATION LC_ALL LANGUAGE
+AcceptEnv XMODIFIERS
+
+#AllowAgentForwarding yes
+AllowTcpForwarding <%= scope.lookupvar('::sshd::tcp_forwarding') %>
+#GatewayPorts no
+#X11Forwarding no
+X11Forwarding <%= scope.lookupvar('::sshd::x11_forwarding') %>
+#X11DisplayOffset 10
+#X11UseLocalhost yes
+PrintMotd <%= scope.lookupvar('::sshd::print_motd') %>
+#PrintLastLog yes
+#TCPKeepAlive yes
+#UseLogin no
+#UsePrivilegeSeparation yes
+#PermitUserEnvironment no
+#Compression delayed
+#ClientAliveInterval 0
+#ClientAliveCountMax 3
+#ShowPatchLevel no
+#UseDNS yes
+#PidFile /var/run/sshd.pid
+#MaxStartups 10:30:100
+#PermitTunnel no
+#ChrootDirectory none
+
+# no default banner path
+#Banner /some/path
+
+# override default of no subsystems
+Subsystem sftp <%= (s=scope.lookupvar('::sshd::sftp_subsystem')).empty? ? '/usr/libexec/openssh/sftp-server' : s %>
+
+<% unless (s=scope.lookupvar('::sshd::allowed_users')).empty? -%>
+AllowUsers <%= s %>
+<% end -%>
+<% unless (s=scope.lookupvar('::sshd::allowed_groups')).empty? -%>
+AllowGroups <%= s %>
+<%- end -%>
+
+<% if scope.lookupvar('::sshd::hardened') == 'yes' -%>
+<% if (scope.function_versioncmp([scope.lookupvar('::ssh_version'),'6.5'])) >= 0 -%>
+KexAlgorithms curve25519-sha256@libssh.org
+Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr
+MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-ripemd160-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,hmac-ripemd160,umac-128@openssh.com
+<% else -%>
+Ciphers aes256-ctr
+MACs hmac-sha1
+<% end -%>
+<% end -%>
+
+# Example of overriding settings on a per-user basis
+#Match User anoncvs
+# X11Forwarding no
+# AllowTcpForwarding no
+# ForceCommand cvs server
+#
+<% unless (s=scope.lookupvar('::sshd::tail_additional_options')).empty? -%>
+<%= s %>
+<% end -%>
diff --git a/puppet/modules/sshd/templates/sshd_config/CentOS_7.erb b/puppet/modules/sshd/templates/sshd_config/CentOS_7.erb
new file mode 100644
index 00000000..f55fb9d0
--- /dev/null
+++ b/puppet/modules/sshd/templates/sshd_config/CentOS_7.erb
@@ -0,0 +1,186 @@
+# $OpenBSD: sshd_config,v 1.90 2013/05/16 04:09:14 dtucker Exp $
+
+# This is the sshd server system-wide configuration file. See
+# sshd_config(5) for more information.
+
+# This sshd was compiled with PATH=/usr/local/bin:/bin:/usr/bin
+
+# The strategy used for options in the default sshd_config shipped with
+# OpenSSH is to specify options with their default value where
+# possible, but leave them commented. Uncommented options change a
+# default value.
+
+<% unless (s=scope.lookupvar('::sshd::head_additional_options')).empty? -%>
+<%= s %>
+<% end -%>
+
+# If you want to change the port on a SELinux system, you have to tell
+# SELinux about this change.
+# semanage port -a -t ssh_port_t -p tcp #PORTNUMBER
+#
+<% scope.lookupvar('::sshd::ports').to_a.each do |port| -%>
+<% if port == 'off' -%>
+#Port -- disabled by puppet
+<% else -%>
+Port <%= port %>
+<% end -%>
+<% end -%>
+<% scope.lookupvar('::sshd::listen_address').to_a.each do |address| -%>
+ListenAddress <%= address %>
+<% end -%>
+
+# The default requires explicit activation of protocol 1
+#Protocol 2
+
+# HostKey for protocol version 1
+#HostKey /etc/ssh/ssh_host_key
+# HostKeys for protocol version 2
+<% scope.lookupvar('::sshd::hostkey_type').to_a.each do |hostkey_type| -%>
+HostKey /etc/ssh/ssh_host_<%=hostkey_type %>_key
+<% end -%>
+
+# Lifetime and size of ephemeral version 1 server key
+#KeyRegenerationInterval 1h
+#ServerKeyBits 1024
+
+# Ciphers and keying
+#RekeyLimit default none
+
+# Logging
+# obsoletes QuietMode and FascistLogging
+#SyslogFacility AUTH
+SyslogFacility AUTHPRIV
+#LogLevel INFO
+
+# Authentication:
+
+#LoginGraceTime 2m
+PermitRootLogin <%= scope.lookupvar('::sshd::permit_root_login') %>
+StrictModes <%= scope.lookupvar('::sshd::strict_modes') %>
+#MaxAuthTries 6
+#MaxSessions 10
+
+RSAAuthentication <%= scope.lookupvar('::sshd::rsa_authentication') %>
+PubkeyAuthentication <%= scope.lookupvar('::sshd::pubkey_authentication') %>
+AuthorizedKeysFile <%= scope.lookupvar('::sshd::authorized_keys_file') %>
+#AuthorizedPrincipalsFile none
+#AuthorizedKeysCommand none
+#AuthorizedKeysCommandRunAs nobody
+
+# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts
+RhostsRSAAuthentication <%= scope.lookupvar('::sshd::rhosts_rsa_authentication') %>
+
+# similar for protocol version 2
+HostbasedAuthentication <%= scope.lookupvar('::sshd::hostbased_authentication') %>
+
+# Change to yes if you don't trust ~/.ssh/known_hosts for
+# RhostsRSAAuthentication and HostbasedAuthentication
+#IgnoreUserKnownHosts no
+
+# Don't read the user's ~/.rhosts and ~/.shosts files
+IgnoreRhosts <%= scope.lookupvar('::sshd::ignore_rhosts') %>
+
+# To disable tunneled clear text passwords, change to no here!
+PasswordAuthentication <%= scope.lookupvar('::sshd::password_authentication') %>
+
+# To enable empty passwords, change to yes (NOT RECOMMENDED)
+PermitEmptyPasswords <%= scope.lookupvar('::sshd::permit_empty_passwords') %>
+
+# Change to no to disable s/key passwords
+ChallengeResponseAuthentication <%= scope.lookupvar('::sshd::challenge_response_authentication') %>
+
+# Kerberos options
+#KerberosAuthentication no
+#KerberosOrLocalPasswd yes
+#KerberosTicketCleanup yes
+#KerberosGetAFSToken no
+#KerberosUseKuserok yes
+
+# GSSAPI options
+GSSAPIAuthentication no
+GSSAPICleanupCredentials yes
+#GSSAPIStrictAcceptorCheck yes
+#GSSAPIKeyExchange no
+
+# Set this to 'yes' to enable PAM authentication, account processing,
+# and session processing. If this is enabled, PAM authentication will
+# be allowed through the ChallengeResponseAuthentication and
+# PasswordAuthentication. Depending on your PAM configuration,
+# PAM authentication via ChallengeResponseAuthentication may bypass
+# the setting of "PermitRootLogin without-password".
+# If you just want the PAM account and session checks to run without
+# PAM authentication, then enable this but set PasswordAuthentication
+# and ChallengeResponseAuthentication to 'no'.
+# WARNING: 'UsePAM no' is not supported in Red Hat Enterprise Linux and may cause several
+# problems.
+#UsePAM no
+UsePAM <%= scope.lookupvar('::sshd::use_pam') %>
+
+#AllowAgentForwarding yes
+AllowTcpForwarding <%= scope.lookupvar('::sshd::tcp_forwarding') %>
+#GatewayPorts no
+#X11Forwarding no
+X11Forwarding <%= scope.lookupvar('::sshd::x11_forwarding') %>
+#X11DisplayOffset 10
+#X11UseLocalhost yes
+PrintMotd <%= scope.lookupvar('::sshd::print_motd') %>
+#PrintLastLog yes
+#TCPKeepAlive yes
+#UseLogin no
+UsePrivilegeSeparation sandbox # Default for new installations.
+#PermitUserEnvironment no
+#Compression delayed
+#ClientAliveInterval 0
+#ClientAliveCountMax 3
+#ShowPatchLevel no
+#UseDNS yes
+#PidFile /var/run/sshd.pid
+#MaxStartups 10:30:100
+#PermitTunnel no
+#ChrootDirectory none
+#VersionAddendum none
+
+# no default banner path
+#Banner none
+
+# Accept locale-related environment variables
+AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES
+AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT
+AcceptEnv LC_IDENTIFICATION LC_ALL LANGUAGE
+AcceptEnv XMODIFIERS
+
+
+# override default of no subsystems
+Subsystem sftp <%= (s=scope.lookupvar('::sshd::sftp_subsystem')).empty? ? '/usr/libexec/openssh/sftp-server' : s %>
+
+<% unless (s=scope.lookupvar('::sshd::allowed_users')).empty? -%>
+AllowUsers <%= s %>
+<% end -%>
+<% unless (s=scope.lookupvar('::sshd::allowed_groups')).empty? -%>
+AllowGroups <%= s %>
+<%- end -%>
+
+# Uncomment this if you want to use .local domain
+#Host *.local
+# CheckHostIP no
+
+<% if scope.lookupvar('::sshd::hardened') == 'yes' -%>
+<% if (scope.function_versioncmp([scope.lookupvar('::ssh_version'),'6.5'])) >= 0 -%>
+KexAlgorithms curve25519-sha256@libssh.org
+Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr
+MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-ripemd160-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,hmac-ripemd160,umac-128@openssh.com
+<% else -%>
+Ciphers aes256-ctr
+MACs hmac-sha1
+<% end -%>
+<% end -%>
+
+# Example of overriding settings on a per-user basis
+#Match User anoncvs
+# X11Forwarding no
+# AllowTcpForwarding no
+# ForceCommand cvs server
+
+<% unless (s=scope.lookupvar('::sshd::tail_additional_options')).empty? -%>
+<%= s %>
+<% end -%>
diff --git a/puppet/modules/sshd/templates/sshd_config/Debian_jessie.erb b/puppet/modules/sshd/templates/sshd_config/Debian_jessie.erb
new file mode 100644
index 00000000..91dbfff0
--- /dev/null
+++ b/puppet/modules/sshd/templates/sshd_config/Debian_jessie.erb
@@ -0,0 +1,124 @@
+# This file is managed by Puppet, all local modifications will be overwritten
+#
+# Package generated configuration file
+# See the sshd_config(5) manpage for details
+
+<% unless (s=scope.lookupvar('::sshd::head_additional_options')).empty? -%>
+<%= s %>
+<% end -%>
+
+# What ports, IPs and protocols we listen for
+<% scope.lookupvar('::sshd::ports').to_a.each do |port| -%>
+<% if port == 'off' -%>
+#Port -- disabled by puppet
+<% else -%>
+Port <%= port %>
+<% end -%>
+<% end -%>
+
+# Use these options to restrict which interfaces/protocols sshd will bind to
+<% scope.lookupvar('::sshd::listen_address').to_a.each do |address| -%>
+ListenAddress <%= address %>
+<% end -%>
+Protocol 2
+# HostKeys for protocol version 2
+<% scope.lookupvar('::sshd::hostkey_type').to_a.each do |hostkey_type| -%>
+HostKey /etc/ssh/ssh_host_<%=hostkey_type %>_key
+<% end -%>
+#Privilege Separation is turned on for security
+UsePrivilegeSeparation yes
+
+# Lifetime and size of ephemeral version 1 server key
+KeyRegenerationInterval 3600
+ServerKeyBits 1024
+
+# Logging
+SyslogFacility AUTH
+LogLevel INFO
+
+# Authentication:
+LoginGraceTime 120
+PermitRootLogin <%= scope.lookupvar('::sshd::permit_root_login') %>
+StrictModes <%= scope.lookupvar('::sshd::strict_modes') %>
+
+RSAAuthentication <%= scope.lookupvar('::sshd::rsa_authentication') %>
+PubkeyAuthentication <%= scope.lookupvar('::sshd::pubkey_authentication') %>
+AuthorizedKeysFile <%= scope.lookupvar('::sshd::authorized_keys_file') %>
+
+# Don't read the user's ~/.rhosts and ~/.shosts files
+IgnoreRhosts <%= scope.lookupvar('::sshd::ignore_rhosts') %>
+# For this to work you will also need host keys in /etc/ssh_known_hosts
+RhostsRSAAuthentication <%= scope.lookupvar('::sshd::rhosts_rsa_authentication') %>
+# similar for protocol version 2
+HostbasedAuthentication <%= scope.lookupvar('::sshd::hostbased_authentication') %>
+# Uncomment if you don't trust ~/.ssh/known_hosts for RhostsRSAAuthentication
+#IgnoreUserKnownHosts yes
+
+# To enable empty passwords, change to yes (NOT RECOMMENDED)
+PermitEmptyPasswords <%= scope.lookupvar('::sshd::permit_empty_passwords') %>
+
+# Change to yes to enable challenge-response passwords (beware issues with
+# some PAM modules and threads)
+ChallengeResponseAuthentication <%= scope.lookupvar('::sshd::challenge_response_authentication') %>
+
+# Change to no to disable tunnelled clear text passwords
+PasswordAuthentication <%= scope.lookupvar('::sshd::password_authentication') %>
+
+# Kerberos options
+KerberosAuthentication <%= scope.lookupvar('::sshd::kerberos_authentication') %>
+#KerberosGetAFSToken no
+KerberosOrLocalPasswd <%= scope.lookupvar('::sshd::kerberos_orlocalpasswd') %>
+KerberosTicketCleanup <%= scope.lookupvar('::sshd::kerberos_ticketcleanup') %>
+
+# GSSAPI options
+GSSAPIAuthentication <%= scope.lookupvar('::sshd::gssapi_authentication') %>
+GSSAPICleanupCredentials <%= scope.lookupvar('::sshd::gssapi_cleanupcredentials') %>
+
+X11Forwarding <%= scope.lookupvar('::sshd::x11_forwarding') %>
+X11DisplayOffset 10
+PrintMotd <%= scope.lookupvar('::sshd::print_motd') %>
+PrintLastLog yes
+TCPKeepAlive yes
+#UseLogin no
+
+#MaxStartups 10:30:60
+#Banner /etc/issue.net
+# do not reveal debian version (default is yes)
+DebianBanner no
+
+# Allow client to pass locale environment variables
+AcceptEnv LANG LC_*
+
+Subsystem sftp <%= (s=scope.lookupvar('::sshd::sftp_subsystem')).empty? ? '/usr/lib/openssh/sftp-server' : s %>
+
+# Set this to 'yes' to enable PAM authentication, account processing,
+# and session processing. If this is enabled, PAM authentication will
+# be allowed through the ChallengeResponseAuthentication and
+# PasswordAuthentication. Depending on your PAM configuration,
+# PAM authentication via ChallengeResponseAuthentication may bypass
+# the setting of "PermitRootLogin without-password".
+# If you just want the PAM account and session checks to run without
+# PAM authentication, then enable this but set PasswordAuthentication
+# and ChallengeResponseAuthentication to 'no'.
+UsePAM <%= scope.lookupvar('::sshd::use_pam') %>
+
+AllowTcpForwarding <%= scope.lookupvar('::sshd::tcp_forwarding') %>
+
+AllowAgentForwarding <%= scope.lookupvar('::sshd::agent_forwarding') %>
+
+<% unless (s=scope.lookupvar('::sshd::allowed_users')).empty? -%>
+AllowUsers <%= s %>
+<% end -%>
+<% unless (s=scope.lookupvar('::sshd::allowed_groups')).empty? -%>
+AllowGroups <%= s %>
+<%- end -%>
+
+<% if scope.lookupvar('::sshd::hardened') == 'yes' -%>
+KexAlgorithms curve25519-sha256@libssh.org
+Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr
+MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-ripemd160-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,hmac-ripemd160,umac-128@openssh.com
+<% end -%>
+
+<% unless (s=scope.lookupvar('::sshd::tail_additional_options')).empty? -%>
+<%= s %>
+<% end -%>
diff --git a/puppet/modules/sshd/templates/sshd_config/Debian_sid.erb b/puppet/modules/sshd/templates/sshd_config/Debian_sid.erb
new file mode 100644
index 00000000..91dbfff0
--- /dev/null
+++ b/puppet/modules/sshd/templates/sshd_config/Debian_sid.erb
@@ -0,0 +1,124 @@
+# This file is managed by Puppet, all local modifications will be overwritten
+#
+# Package generated configuration file
+# See the sshd_config(5) manpage for details
+
+<% unless (s=scope.lookupvar('::sshd::head_additional_options')).empty? -%>
+<%= s %>
+<% end -%>
+
+# What ports, IPs and protocols we listen for
+<% scope.lookupvar('::sshd::ports').to_a.each do |port| -%>
+<% if port == 'off' -%>
+#Port -- disabled by puppet
+<% else -%>
+Port <%= port %>
+<% end -%>
+<% end -%>
+
+# Use these options to restrict which interfaces/protocols sshd will bind to
+<% scope.lookupvar('::sshd::listen_address').to_a.each do |address| -%>
+ListenAddress <%= address %>
+<% end -%>
+Protocol 2
+# HostKeys for protocol version 2
+<% scope.lookupvar('::sshd::hostkey_type').to_a.each do |hostkey_type| -%>
+HostKey /etc/ssh/ssh_host_<%=hostkey_type %>_key
+<% end -%>
+#Privilege Separation is turned on for security
+UsePrivilegeSeparation yes
+
+# Lifetime and size of ephemeral version 1 server key
+KeyRegenerationInterval 3600
+ServerKeyBits 1024
+
+# Logging
+SyslogFacility AUTH
+LogLevel INFO
+
+# Authentication:
+LoginGraceTime 120
+PermitRootLogin <%= scope.lookupvar('::sshd::permit_root_login') %>
+StrictModes <%= scope.lookupvar('::sshd::strict_modes') %>
+
+RSAAuthentication <%= scope.lookupvar('::sshd::rsa_authentication') %>
+PubkeyAuthentication <%= scope.lookupvar('::sshd::pubkey_authentication') %>
+AuthorizedKeysFile <%= scope.lookupvar('::sshd::authorized_keys_file') %>
+
+# Don't read the user's ~/.rhosts and ~/.shosts files
+IgnoreRhosts <%= scope.lookupvar('::sshd::ignore_rhosts') %>
+# For this to work you will also need host keys in /etc/ssh_known_hosts
+RhostsRSAAuthentication <%= scope.lookupvar('::sshd::rhosts_rsa_authentication') %>
+# similar for protocol version 2
+HostbasedAuthentication <%= scope.lookupvar('::sshd::hostbased_authentication') %>
+# Uncomment if you don't trust ~/.ssh/known_hosts for RhostsRSAAuthentication
+#IgnoreUserKnownHosts yes
+
+# To enable empty passwords, change to yes (NOT RECOMMENDED)
+PermitEmptyPasswords <%= scope.lookupvar('::sshd::permit_empty_passwords') %>
+
+# Change to yes to enable challenge-response passwords (beware issues with
+# some PAM modules and threads)
+ChallengeResponseAuthentication <%= scope.lookupvar('::sshd::challenge_response_authentication') %>
+
+# Change to no to disable tunnelled clear text passwords
+PasswordAuthentication <%= scope.lookupvar('::sshd::password_authentication') %>
+
+# Kerberos options
+KerberosAuthentication <%= scope.lookupvar('::sshd::kerberos_authentication') %>
+#KerberosGetAFSToken no
+KerberosOrLocalPasswd <%= scope.lookupvar('::sshd::kerberos_orlocalpasswd') %>
+KerberosTicketCleanup <%= scope.lookupvar('::sshd::kerberos_ticketcleanup') %>
+
+# GSSAPI options
+GSSAPIAuthentication <%= scope.lookupvar('::sshd::gssapi_authentication') %>
+GSSAPICleanupCredentials <%= scope.lookupvar('::sshd::gssapi_cleanupcredentials') %>
+
+X11Forwarding <%= scope.lookupvar('::sshd::x11_forwarding') %>
+X11DisplayOffset 10
+PrintMotd <%= scope.lookupvar('::sshd::print_motd') %>
+PrintLastLog yes
+TCPKeepAlive yes
+#UseLogin no
+
+#MaxStartups 10:30:60
+#Banner /etc/issue.net
+# do not reveal debian version (default is yes)
+DebianBanner no
+
+# Allow client to pass locale environment variables
+AcceptEnv LANG LC_*
+
+Subsystem sftp <%= (s=scope.lookupvar('::sshd::sftp_subsystem')).empty? ? '/usr/lib/openssh/sftp-server' : s %>
+
+# Set this to 'yes' to enable PAM authentication, account processing,
+# and session processing. If this is enabled, PAM authentication will
+# be allowed through the ChallengeResponseAuthentication and
+# PasswordAuthentication. Depending on your PAM configuration,
+# PAM authentication via ChallengeResponseAuthentication may bypass
+# the setting of "PermitRootLogin without-password".
+# If you just want the PAM account and session checks to run without
+# PAM authentication, then enable this but set PasswordAuthentication
+# and ChallengeResponseAuthentication to 'no'.
+UsePAM <%= scope.lookupvar('::sshd::use_pam') %>
+
+AllowTcpForwarding <%= scope.lookupvar('::sshd::tcp_forwarding') %>
+
+AllowAgentForwarding <%= scope.lookupvar('::sshd::agent_forwarding') %>
+
+<% unless (s=scope.lookupvar('::sshd::allowed_users')).empty? -%>
+AllowUsers <%= s %>
+<% end -%>
+<% unless (s=scope.lookupvar('::sshd::allowed_groups')).empty? -%>
+AllowGroups <%= s %>
+<%- end -%>
+
+<% if scope.lookupvar('::sshd::hardened') == 'yes' -%>
+KexAlgorithms curve25519-sha256@libssh.org
+Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr
+MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-ripemd160-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,hmac-ripemd160,umac-128@openssh.com
+<% end -%>
+
+<% unless (s=scope.lookupvar('::sshd::tail_additional_options')).empty? -%>
+<%= s %>
+<% end -%>
diff --git a/puppet/modules/sshd/templates/sshd_config/Debian_squeeze.erb b/puppet/modules/sshd/templates/sshd_config/Debian_squeeze.erb
new file mode 100644
index 00000000..649b320a
--- /dev/null
+++ b/puppet/modules/sshd/templates/sshd_config/Debian_squeeze.erb
@@ -0,0 +1,127 @@
+# This file is managed by Puppet, all local modifications will be overwritten
+#
+# Package generated configuration file
+# See the sshd(8) manpage for details
+
+<% unless (s=scope.lookupvar('::sshd::head_additional_options')).empty? -%>
+<%= s %>
+<% end -%>
+
+# What ports, IPs and protocols we listen for
+<% scope.lookupvar('::sshd::ports').to_a.each do |port| -%>
+<% if port == 'off' -%>
+#Port -- disabled by puppet
+<% else -%>
+Port <%= port %>
+<% end -%>
+<% end -%>
+
+# Use these options to restrict which interfaces/protocols sshd will bind to
+<% scope.lookupvar('::sshd::listen_address').to_a.each do |address| -%>
+ListenAddress <%= address %>
+<% end -%>
+Protocol 2
+# HostKeys for protocol version 2
+<% scope.lookupvar('::sshd::hostkey_type').to_a.each do |hostkey_type| -%>
+HostKey /etc/ssh/ssh_host_<%=hostkey_type %>_key
+<% end -%>
+
+#Privilege Separation is turned on for security
+UsePrivilegeSeparation yes
+
+# Lifetime and size of ephemeral version 1 server key
+KeyRegenerationInterval 3600
+ServerKeyBits 768
+
+# Logging
+SyslogFacility AUTH
+LogLevel INFO
+
+# Authentication:
+LoginGraceTime 120
+PermitRootLogin <%= scope.lookupvar('::sshd::permit_root_login') %>
+
+StrictModes <%= scope.lookupvar('::sshd::strict_modes') %>
+
+RSAAuthentication <%= scope.lookupvar('::sshd::rsa_authentication') %>
+
+PubkeyAuthentication <%= scope.lookupvar('::sshd::pubkey_authentication') %>
+
+AuthorizedKeysFile <%= scope.lookupvar('::sshd::authorized_keys_file') %>
+
+# Don't read the user's ~/.rhosts and ~/.shosts files
+IgnoreRhosts <%= scope.lookupvar('::sshd::ignore_rhosts') %>
+# For this to work you will also need host keys in /etc/ssh_known_hosts
+RhostsRSAAuthentication <%= scope.lookupvar('::sshd::rhosts_rsa_authentication') %>
+# similar for protocol version 2
+HostbasedAuthentication <%= scope.lookupvar('::sshd::hostbased_authentication') %>
+# Uncomment if you don't trust ~/.ssh/known_hosts for RhostsRSAAuthentication
+#IgnoreUserKnownHosts yes
+
+# To enable empty passwords, change to yes (NOT RECOMMENDED)
+PermitEmptyPasswords <%= scope.lookupvar('::sshd::permit_empty_passwords') %>
+
+# Change to yes to enable challenge-response passwords (beware issues with
+# some PAM modules and threads)
+ChallengeResponseAuthentication <%= scope.lookupvar('::sshd::challenge_response_authentication') %>
+
+# To disable tunneled clear text passwords, change to no here!
+PasswordAuthentication <%= scope.lookupvar('::sshd::password_authentication') %>
+
+# Kerberos options
+KerberosAuthentication <%= scope.lookupvar('::sshd::kerberos_authentication') %>
+KerberosOrLocalPasswd <%= scope.lookupvar('::sshd::kerberos_orlocalpasswd') %>
+KerberosTicketCleanup <%= scope.lookupvar('::sshd::kerberos_ticketcleanup') %>
+
+# GSSAPI options
+GSSAPIAuthentication <%= scope.lookupvar('::sshd::gssapi_authentication') %>
+GSSAPICleanupCredentials <%= scope.lookupvar('::sshd::gssapi_cleanupcredentials') %>
+
+X11Forwarding <%= scope.lookupvar('::sshd::x11_forwarding') %>
+X11DisplayOffset 10
+PrintMotd <%= scope.lookupvar('::sshd::print_motd') %>
+PrintLastLog yes
+TCPKeepAlive yes
+
+#UseLogin no
+
+#MaxStartups 10:30:60
+#Banner /etc/issue.net
+# do not reveal debian version (default is yes)
+DebianBanner no
+
+# Allow client to pass locale environment variables
+AcceptEnv LANG LC_*
+
+Subsystem sftp <%= (s=scope.lookupvar('::sshd::sftp_subsystem')).empty? ? '/usr/lib/openssh/sftp-server' : s %>
+
+# Set this to 'yes' to enable PAM authentication, account processing,
+# and session processing. If this is enabled, PAM authentication will
+# be allowed through the ChallengeResponseAuthentication and
+# PasswordAuthentication. Depending on your PAM configuration,
+# PAM authentication via ChallengeResponseAuthentication may bypass
+# the setting of "PermitRootLogin without-password".
+# If you just want the PAM account and session checks to run without
+# PAM authentication, then enable this but set PasswordAuthentication
+# and ChallengeResponseAuthentication to 'no'.
+UsePAM <%= scope.lookupvar('::sshd::use_pam') %>
+
+AllowTcpForwarding <%= scope.lookupvar('::sshd::tcp_forwarding') %>
+
+AllowAgentForwarding <%= scope.lookupvar('::sshd::agent_forwarding') %>
+
+<% unless (s=scope.lookupvar('::sshd::allowed_users')).empty? -%>
+AllowUsers <%= s %>
+<% end -%>
+<% unless (s=scope.lookupvar('::sshd::allowed_groups')).empty? -%>
+AllowGroups <%= s %>
+<%- end -%>
+
+<% if scope.lookupvar('::sshd::hardened') == 'yes' -%>
+Ciphers aes256-ctr
+MACs hmac-sha2-512
+<% end -%>
+
+<% unless (s=scope.lookupvar('::sshd::tail_additional_options')).empty? -%>
+<%= s %>
+<% end -%>
diff --git a/puppet/modules/sshd/templates/sshd_config/Debian_wheezy.erb b/puppet/modules/sshd/templates/sshd_config/Debian_wheezy.erb
new file mode 100644
index 00000000..bcb15286
--- /dev/null
+++ b/puppet/modules/sshd/templates/sshd_config/Debian_wheezy.erb
@@ -0,0 +1,132 @@
+# This file is managed by Puppet, all local modifications will be overwritten
+#
+# Package generated configuration file
+# See the sshd(8) manpage for details
+
+<% unless (s=scope.lookupvar('::sshd::head_additional_options')).empty? -%>
+<%= s %>
+<% end -%>
+
+# What ports, IPs and protocols we listen for
+<% scope.lookupvar('::sshd::ports').to_a.each do |port| -%>
+<% if port == 'off' -%>
+#Port -- disabled by puppet
+<% else -%>
+Port <%= port %>
+<% end -%>
+<% end -%>
+
+# Use these options to restrict which interfaces/protocols sshd will bind to
+<% scope.lookupvar('::sshd::listen_address').to_a.each do |address| -%>
+ListenAddress <%= address %>
+<% end -%>
+Protocol 2
+# HostKeys for protocol version 2
+<% scope.lookupvar('::sshd::hostkey_type').to_a.each do |hostkey_type| -%>
+HostKey /etc/ssh/ssh_host_<%=hostkey_type %>_key
+<% end -%>
+#Privilege Separation is turned on for security
+UsePrivilegeSeparation yes
+
+# Lifetime and size of ephemeral version 1 server key
+KeyRegenerationInterval 3600
+ServerKeyBits 768
+
+# Logging
+SyslogFacility AUTH
+LogLevel INFO
+
+# Authentication:
+LoginGraceTime 120
+PermitRootLogin <%= scope.lookupvar('::sshd::permit_root_login') %>
+
+StrictModes <%= scope.lookupvar('::sshd::strict_modes') %>
+
+RSAAuthentication <%= scope.lookupvar('::sshd::rsa_authentication') %>
+
+PubkeyAuthentication <%= scope.lookupvar('::sshd::pubkey_authentication') %>
+
+AuthorizedKeysFile <%= scope.lookupvar('::sshd::authorized_keys_file') %>
+
+# Don't read the user's ~/.rhosts and ~/.shosts files
+IgnoreRhosts <%= scope.lookupvar('::sshd::ignore_rhosts') %>
+# For this to work you will also need host keys in /etc/ssh_known_hosts
+RhostsRSAAuthentication <%= scope.lookupvar('::sshd::rhosts_rsa_authentication') %>
+# similar for protocol version 2
+HostbasedAuthentication <%= scope.lookupvar('::sshd::hostbased_authentication') %>
+# Uncomment if you don't trust ~/.ssh/known_hosts for RhostsRSAAuthentication
+#IgnoreUserKnownHosts yes
+
+# To enable empty passwords, change to yes (NOT RECOMMENDED)
+PermitEmptyPasswords <%= scope.lookupvar('::sshd::permit_empty_passwords') %>
+
+# Change to yes to enable challenge-response passwords (beware issues with
+# some PAM modules and threads)
+ChallengeResponseAuthentication <%= scope.lookupvar('::sshd::challenge_response_authentication') %>
+
+# To disable tunneled clear text passwords, change to no here!
+PasswordAuthentication <%= scope.lookupvar('::sshd::password_authentication') %>
+
+# Kerberos options
+KerberosAuthentication <%= scope.lookupvar('::sshd::kerberos_authentication') %>
+KerberosOrLocalPasswd <%= scope.lookupvar('::sshd::kerberos_orlocalpasswd') %>
+KerberosTicketCleanup <%= scope.lookupvar('::sshd::kerberos_ticketcleanup') %>
+
+# GSSAPI options
+GSSAPIAuthentication <%= scope.lookupvar('::sshd::gssapi_authentication') %>
+GSSAPICleanupCredentials <%= scope.lookupvar('::sshd::gssapi_cleanupcredentials') %>
+
+X11Forwarding <%= scope.lookupvar('::sshd::x11_forwarding') %>
+X11DisplayOffset 10
+PrintMotd <%= scope.lookupvar('::sshd::print_motd') %>
+PrintLastLog yes
+TCPKeepAlive yes
+
+#UseLogin no
+
+#MaxStartups 10:30:60
+#Banner /etc/issue.net
+# do not reveal debian version (default is yes)
+DebianBanner no
+
+# Allow client to pass locale environment variables
+AcceptEnv LANG LC_*
+
+Subsystem sftp <%= (s=scope.lookupvar('::sshd::sftp_subsystem')).empty? ? '/usr/lib/openssh/sftp-server' : s %>
+
+# Set this to 'yes' to enable PAM authentication, account processing,
+# and session processing. If this is enabled, PAM authentication will
+# be allowed through the ChallengeResponseAuthentication and
+# PasswordAuthentication. Depending on your PAM configuration,
+# PAM authentication via ChallengeResponseAuthentication may bypass
+# the setting of "PermitRootLogin without-password".
+# If you just want the PAM account and session checks to run without
+# PAM authentication, then enable this but set PasswordAuthentication
+# and ChallengeResponseAuthentication to 'no'.
+UsePAM <%= scope.lookupvar('::sshd::use_pam') %>
+
+AllowTcpForwarding <%= scope.lookupvar('::sshd::tcp_forwarding') %>
+
+AllowAgentForwarding <%= scope.lookupvar('::sshd::agent_forwarding') %>
+
+<% unless (s=scope.lookupvar('::sshd::allowed_users')).empty? -%>
+AllowUsers <%= s %>
+<% end -%>
+<% unless (s=scope.lookupvar('::sshd::allowed_groups')).empty? -%>
+AllowGroups <%= s %>
+<%- end -%>
+
+<% if scope.lookupvar('::sshd::hardened') == 'yes' -%>
+<% if (scope.function_versioncmp([scope.lookupvar('::ssh_version'),'6.5'])) >= 0 -%>
+KexAlgorithms curve25519-sha256@libssh.org
+Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr
+MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-ripemd160-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,hmac-ripemd160,umac-128@openssh.com
+<% else -%>
+Ciphers aes256-ctr
+MACs hmac-sha2-512
+<% end -%>
+<% end -%>
+
+<% unless (s=scope.lookupvar('::sshd::tail_additional_options')).empty? -%>
+<%= s %>
+<% end -%>
diff --git a/puppet/modules/sshd/templates/sshd_config/FreeBSD.erb b/puppet/modules/sshd/templates/sshd_config/FreeBSD.erb
new file mode 100644
index 00000000..5298ade9
--- /dev/null
+++ b/puppet/modules/sshd/templates/sshd_config/FreeBSD.erb
@@ -0,0 +1,168 @@
+# $OpenBSD: sshd_config,v 1.81 2009/10/08 14:03:41 markus Exp $
+# $FreeBSD: src/crypto/openssh/sshd_config,v 1.49.2.2.2.1 2010/06/14 02:09:06 kensmith Exp $
+
+# This is the sshd server system-wide configuration file. See
+# sshd_config(5) for more information.
+
+# This sshd was compiled with PATH=/usr/bin:/bin:/usr/sbin:/sbin
+
+# The strategy used for options in the default sshd_config shipped with
+# OpenSSH is to specify options with their default value where
+# possible, but leave them commented. Uncommented options change a
+# default value.
+
+# Note that some of FreeBSD's defaults differ from OpenBSD's, and
+# FreeBSD has a few additional options.
+
+#VersionAddendum FreeBSD-20100308
+
+<% unless (s=scope.lookupvar('::sshd::head_additional_options')).empty? -%>
+<%= s %>
+<% end -%>
+
+# What ports, IPs and protocols we listen for
+<% scope.lookupvar('::sshd::ports').to_a.each do |port| -%>
+<% if port == 'off' -%>
+#Port -- disabled by puppet
+<% else -%>
+Port <%= port %>
+<% end -%>
+<% end -%>
+
+#AddressFamily any
+<% scope.lookupvar('::sshd::listen_address').to_a.each do |address| -%>
+ListenAddress <%= address %>
+<% end -%>
+
+# The default requires explicit activation of protocol 1
+Protocol 2
+
+# HostKey for protocol version 1
+#HostKey /etc/ssh/ssh_host_key
+# HostKeys for protocol version 2
+<% scope.lookupvar('::sshd::hostkey_type').to_a.each do |hostkey_type| -%>
+HostKey /etc/ssh/ssh_host_<%=hostkey_type %>_key
+<% end -%>
+
+# Lifetime and size of ephemeral version 1 server key
+#KeyRegenerationInterval 1h
+#ServerKeyBits 1024
+
+# Logging
+# obsoletes QuietMode and FascistLogging
+SyslogFacility AUTH
+LogLevel INFO
+
+# Authentication:
+
+LoginGraceTime 600
+PermitRootLogin <%= scope.lookupvar('::sshd::permit_root_login') %>
+
+StrictModes <%= scope.lookupvar('::sshd::strict_modes') %>
+
+#MaxAuthTries 6
+#MaxSessions 10
+
+RSAAuthentication <%= scope.lookupvar('::sshd::rsa_authentication') %>
+
+PubkeyAuthentication <%= scope.lookupvar('::sshd::pubkey_authentication') %>
+
+AuthorizedKeysFile <%= scope.lookupvar('::sshd::authorized_keys_file') %>
+
+# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts
+RhostsRSAAuthentication <%= scope.lookupvar('::sshd::rhosts_rsa_authentication') %>
+
+# similar for protocol version 2
+HostbasedAuthentication <%= scope.lookupvar('::sshd::hostbased_authentication') %>
+
+# Change to yes if you don't trust ~/.ssh/known_hosts for
+# RhostsRSAAuthentication and HostbasedAuthentication
+#IgnoreUserKnownHosts no
+# Don't read the user's ~/.rhosts and ~/.shosts files
+#IgnoreRhosts yes
+
+# Change to yes to enable built-in password authentication.
+PasswordAuthentication <%= scope.lookupvar('::sshd::password_authentication') %>
+
+PermitEmptyPasswords <%= scope.lookupvar('::sshd::permit_empty_passwords') %>
+
+# Change to no to disable PAM authentication
+ChallengeResponseAuthentication <%= scope.lookupvar('::sshd::challenge_response_authentication') %>
+
+# Kerberos options
+KerberosAuthentication <%= scope.lookupvar('::sshd::kerberos_authentication') %>
+KerberosOrLocalPasswd <%= scope.lookupvar('::sshd::kerberos_orlocalpasswd') %>
+KerberosTicketCleanup <%= scope.lookupvar('::sshd::kerberos_ticketcleanup') %>
+
+# GSSAPI options
+GSSAPIAuthentication <%= scope.lookupvar('::sshd::gssapi_authentication') %>
+GSSAPICleanupCredentials <%= scope.lookupvar('::sshd::gssapi_cleanupcredentials') %>
+
+# Set this to 'no' to disable PAM authentication, account processing,
+# and session processing. If this is enabled, PAM authentication will
+# be allowed through the ChallengeResponseAuthentication and
+# PasswordAuthentication. Depending on your PAM configuration,
+# PAM authentication via ChallengeResponseAuthentication may bypass
+# the setting of "PermitRootLogin without-password".
+# If you just want the PAM account and session checks to run without
+# PAM authentication, then enable this but set PasswordAuthentication
+# and ChallengeResponseAuthentication to 'no'.
+UsePAM <%= scope.lookupvar('::sshd::use_pam') %>
+
+AllowAgentForwarding <%= scope.lookupvar('::sshd::agent_forwarding') %>
+
+AllowTcpForwarding <%= scope.lookupvar('::sshd::tcp_forwarding') %>
+
+#GatewayPorts no
+X11Forwarding <%= scope.lookupvar('::sshd::x11_forwarding') %>
+
+X11DisplayOffset 10
+#X11UseLocalhost yes
+PrintMotd <%= sshd_print_motd %>
+#PrintLastLog yes
+TCPKeepAlive yes
+#UseLogin no
+#UsePrivilegeSeparation yes
+#PermitUserEnvironment no
+#Compression delayed
+#ClientAliveInterval 0
+#ClientAliveCountMax 3
+#UseDNS yes
+#PidFile /var/run/sshd.pid
+#MaxStartups 10
+#PermitTunnel no
+#ChrootDirectory none
+
+# no default banner path
+#Banner none
+
+# override default of no subsystems
+Subsystem sftp <%= (s=scope.lookupvar('::sshd::sftp_subsystem')).empty? ? '/usr/libexec/sftp-server' : s %>
+
+# Example of overriding settings on a per-user basis
+#Match User anoncvs
+# X11Forwarding no
+# AllowTcpForwarding no
+# ForceCommand cvs server
+
+<% unless (s=scope.lookupvar('::sshd::allowed_users')).empty? -%>
+AllowUsers <%= s %>
+<% end -%>
+<% unless (s=scope.lookupvar('::sshd::allowed_groups')).empty? -%>
+AllowGroups <%= s %>
+<%- end -%>
+
+<% if scope.lookupvar('::sshd::hardened') == 'yes' -%>
+<% if (scope.function_versioncmp([scope.lookupvar('::ssh_version'),'6.5'])) >= 0 -%>
+KexAlgorithms curve25519-sha256@libssh.org
+Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr
+MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-ripemd160-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,hmac-ripemd160,umac-128@openssh.com
+<% else -%>
+Ciphers aes256-ctr
+MACs hmac-sha1
+<% end -%>
+<% end -%>
+
+<% unless (s=scope.lookupvar('::sshd::tail_additional_options')).empty? -%>
+<%= s %>
+<% end -%>
diff --git a/puppet/modules/sshd/templates/sshd_config/Gentoo.erb b/puppet/modules/sshd/templates/sshd_config/Gentoo.erb
new file mode 100644
index 00000000..022a26e7
--- /dev/null
+++ b/puppet/modules/sshd/templates/sshd_config/Gentoo.erb
@@ -0,0 +1,164 @@
+# $OpenBSD: sshd_config,v 1.75 2007/03/19 01:01:29 djm Exp $
+
+# This is the sshd server system-wide configuration file. See
+# sshd_config(5) for more information.
+
+# This sshd was compiled with PATH=/usr/bin:/bin:/usr/sbin:/sbin
+
+# The strategy used for options in the default sshd_config shipped with
+# OpenSSH is to specify options with their default value where
+# possible, but leave them commented. Uncommented options change a
+# default value.
+
+<% unless (s=scope.lookupvar('::sshd::head_additional_options')).empty? -%>
+<%= s %>
+<% end -%>
+
+<% scope.lookupvar('::sshd::ports').to_a.each do |port| -%>
+<% if port == 'off' -%>
+#Port -- disabled by puppet
+<% else -%>
+Port <%= port %>
+<% end -%>
+<% end -%>
+
+# Use these options to restrict which interfaces/protocols sshd will bind to
+<% scope.lookupvar('::sshd::listen_address').to_a.each do |address| -%>
+ListenAddress <%= address %>
+<% end -%>
+#AddressFamily any
+
+# Disable legacy (protocol version 1) support in the server for new
+# installations. In future the default will change to require explicit
+# activation of protocol 1
+Protocol 2
+
+# HostKey for protocol version 1
+#HostKey /etc/ssh/ssh_host_key
+# HostKeys for protocol version 2
+#HostKey /etc/ssh/ssh_host_rsa_key
+#HostKey /etc/ssh/ssh_host_dsa_key
+
+# Lifetime and size of ephemeral version 1 server key
+#KeyRegenerationInterval 1h
+#ServerKeyBits 768
+
+# Logging
+# obsoletes QuietMode and FascistLogging
+#SyslogFacility AUTH
+#LogLevel INFO
+
+# Authentication:
+
+#LoginGraceTime 2m
+PermitRootLogin <%= scope.lookupvar('::sshd::permit_root_login') %>
+
+StrictModes <%= scope.lookupvar('::sshd::strict_modes') %>
+
+#MaxAuthTries 6
+
+RSAAuthentication <%= scope.lookupvar('::sshd::rsa_authentication') %>
+
+PubkeyAuthentication <%= scope.lookupvar('::sshd::pubkey_authentication') %>
+
+AuthorizedKeysFile <%= scope.lookupvar('::sshd::authorized_keys_file') %>
+
+# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts
+RhostsRSAAuthentication <%= scope.lookupvar('::sshd::rhosts_rsa_authentication') %>
+
+# similar for protocol version 2
+HostbasedAuthentication <%= scope.lookupvar('::sshd::hostbased_authentication') %>
+
+# Change to yes if you don't trust ~/.ssh/known_hosts for
+# RhostsRSAAuthentication and HostbasedAuthentication
+#IgnoreUserKnownHosts no
+
+# Don't read the user's ~/.rhosts and ~/.shosts files
+IgnoreRhosts <%= scope.lookupvar('::sshd::ignore_rhosts') %>
+
+# To disable tunneled clear text passwords, change to no here!
+PasswordAuthentication <%= scope.lookupvar('::sshd::password_authentication') %>
+
+# To enable empty passwords, change to yes (NOT RECOMMENDED)
+PermitEmptyPasswords <%= scope.lookupvar('::sshd::permit_empty_passwords') %>
+
+# Change to no to disable s/key passwords
+ChallengeResponseAuthentication <%= scope.lookupvar('::sshd::challenge_response_authentication') %>
+
+# Kerberos options
+#KerberosAuthentication no
+#KerberosOrLocalPasswd yes
+#KerberosTicketCleanup yes
+#KerberosGetAFSToken no
+
+# GSSAPI options
+#GSSAPIAuthentication no
+#GSSAPICleanupCredentials yes
+#GSSAPIStrictAcceptorCheck yes
+#GSSAPIKeyExchange no
+
+# Set this to 'yes' to enable PAM authentication, account processing,
+# and session processing. If this is enabled, PAM authentication will
+# be allowed through the ChallengeResponseAuthentication and
+# PasswordAuthentication. Depending on your PAM configuration,
+# PAM authentication via ChallengeResponseAuthentication may bypass
+# the setting of "PermitRootLogin without-password".
+# If you just want the PAM account and session checks to run without
+# PAM authentication, then enable this but set PasswordAuthentication
+# and ChallengeResponseAuthentication to 'no'.
+UsePAM <%= scope.lookupvar('::sshd::use_pam') %>
+
+AllowTcpForwarding <%= scope.lookupvar('::sshd::tcp_forwarding') %>
+
+#GatewayPorts no
+X11Forwarding <%= scope.lookupvar('::sshd::x11_forwarding') %>
+#X11DisplayOffset 10
+#X11UseLocalhost yes
+PrintMotd <%= scope.lookupvar('::sshd::print_motd') %>
+#PrintLastLog yes
+#TCPKeepAlive yes
+#UseLogin no
+#UsePrivilegeSeparation yes
+#PermitUserEnvironment no
+#Compression delayed
+#ClientAliveInterval 0
+#ClientAliveCountMax 3
+#UseDNS yes
+#PidFile /var/run/sshd.pid
+#MaxStartups 10
+#PermitTunnel no
+
+# no default banner path
+#Banner /some/path
+
+# override default of no subsystems
+Subsystem sftp <%= (s=scope.lookupvar('::sshd::sftp_subsystem')).empty? ? '/usr/lib/misc/sftp-server' : s %>
+
+# Example of overriding settings on a per-user basis
+#Match User anoncvs
+# X11Forwarding no
+# AllowTcpForwarding no
+# ForceCommand cvs server
+
+<% unless (s=scope.lookupvar('::sshd::allowed_users')).empty? -%>
+AllowUsers <%= s %>
+<% end -%>
+<% unless (s=scope.lookupvar('::sshd::allowed_groups')).empty? -%>
+AllowGroups <%= s %>
+<%- end -%>
+
+<% if scope.lookupvar('::sshd::hardened') == 'yes' -%>
+<% if (scope.function_versioncmp([scope.lookupvar('::ssh_version'),'6.5'])) >= 0 -%>
+KexAlgorithms curve25519-sha256@libssh.org
+Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr
+MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-ripemd160-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,hmac-ripemd160,umac-128@openssh.com
+<% else -%>
+Ciphers aes256-ctr
+MACs hmac-sha1
+<% end -%>
+<% end -%>
+
+<% unless (s=scope.lookupvar('::sshd::tail_additional_options')).empty? -%>
+<%= s %>
+<% end -%>
+
diff --git a/puppet/modules/sshd/templates/sshd_config/OpenBSD.erb b/puppet/modules/sshd/templates/sshd_config/OpenBSD.erb
new file mode 100644
index 00000000..db730300
--- /dev/null
+++ b/puppet/modules/sshd/templates/sshd_config/OpenBSD.erb
@@ -0,0 +1,144 @@
+# $OpenBSD: sshd_config,v 1.74 2006/07/19 13:07:10 dtucker Exp $
+
+# This is the sshd server system-wide configuration file. See
+# sshd_config(5) for more information.
+
+# The strategy used for options in the default sshd_config shipped with
+# OpenSSH is to specify options with their default value where
+# possible, but leave them commented. Uncommented options change a
+# default value.
+
+<% unless (s=scope.lookupvar('::sshd::head_additional_options')).empty? -%>
+<%= s %>
+<% end -%>
+
+<% scope.lookupvar('::sshd::ports').to_a.each do |port| -%>
+<% if port == 'off' -%>
+#Port -- disabled by puppet
+<% else -%>
+Port <%= port %>
+<% end -%>
+<% end -%>
+
+# Use these options to restrict which interfaces/protocols sshd will bind to
+<% scope.lookupvar('::sshd::listen_address').to_a.each do |address| -%>
+ListenAddress <%= address %>
+<% end -%>
+#Protocol 2,1
+#AddressFamily any
+
+# HostKey for protocol version 1
+#HostKey /etc/ssh/ssh_host_key
+# HostKeys for protocol version 2
+#HostKey /etc/ssh/ssh_host_rsa_key
+#HostKey /etc/ssh/ssh_host_dsa_key
+
+# Lifetime and size of ephemeral version 1 server key
+#KeyRegenerationInterval 1h
+#ServerKeyBits 768
+
+# Logging
+# obsoletes QuietMode and FascistLogging
+#SyslogFacility AUTH
+#LogLevel INFO
+
+# Authentication:
+
+#LoginGraceTime 2m
+PermitRootLogin <%= scope.lookupvar('::sshd::permit_root_login') %>
+
+StrictModes <%= scope.lookupvar('::sshd::strict_modes') %>
+
+#MaxAuthTries 6
+
+RSAAuthentication <%= scope.lookupvar('::sshd::rsa_authentication') %>
+
+PubkeyAuthentication <%= scope.lookupvar('::sshd::pubkey_authentication') %>
+
+AuthorizedKeysFile <%= scope.lookupvar('::sshd::authorized_keys_file') %>
+
+# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts
+RhostsRSAAuthentication <%= scope.lookupvar('::sshd::rhosts_rsa_authentication') %>
+
+# similar for protocol version 2
+HostbasedAuthentication <%= scope.lookupvar('::sshd::hostbased_authentication') %>
+
+# Change to yes if you don't trust ~/.ssh/known_hosts for
+# RhostsRSAAuthentication and HostbasedAuthentication
+#IgnoreUserKnownHosts no
+
+# Don't read the user's ~/.rhosts and ~/.shosts files
+IgnoreRhosts <%= scope.lookupvar('::sshd::ignore_rhosts') %>
+
+# To disable tunneled clear text passwords, change to no here!
+PasswordAuthentication <%= scope.lookupvar('::sshd::password_authentication') %>
+
+# To enable empty passwords, change to yes (NOT RECOMMENDED)
+PermitEmptyPasswords <%= scope.lookupvar('::sshd::permit_empty_passwords') %>
+
+# Change to no to disable s/key passwords
+ChallengeResponseAuthentication <%= scope.lookupvar('::sshd::challenge_response_authentication') %>
+
+# Kerberos options
+#KerberosAuthentication no
+#KerberosOrLocalPasswd yes
+#KerberosTicketCleanup yes
+#KerberosGetAFSToken no
+
+# GSSAPI options
+#GSSAPIAuthentication no
+#GSSAPICleanupCredentials yes
+
+AllowTcpForwarding <%= scope.lookupvar('::sshd::tcp_forwarding') %>
+
+#GatewayPorts no
+X11Forwarding <%= scope.lookupvar('::sshd::x11_forwarding') %>
+#X11DisplayOffset 10
+#X11UseLocalhost yes
+PrintMotd <%= scope.lookupvar('::sshd::print_motd') %>
+#PrintLastLog yes
+#TCPKeepAlive yes
+#UseLogin no
+#UsePrivilegeSeparation yes
+#PermitUserEnvironment no
+#Compression delayed
+#ClientAliveInterval 0
+#ClientAliveCountMax 3
+#UseDNS yes
+#PidFile /var/run/sshd.pid
+#MaxStartups 10
+#PermitTunnel no
+
+# no default banner path
+#Banner /some/path
+
+# override default of no subsystems
+Subsystem sftp <%= (s=scope.lookupvar('::sshd::sftp_subsystem')).empty? ? '/usr/libexec/sftp-server' : s %>
+
+<% unless (s=scope.lookupvar('::sshd::allowed_users')).empty? -%>
+AllowUsers <%= s %>
+<% end -%>
+<% unless (s=scope.lookupvar('::sshd::allowed_groups')).empty? -%>
+AllowGroups <%= s %>
+<%- end -%>
+
+# Example of overriding settings on a per-user basis
+#Match User anoncvs
+# X11Forwarding no
+# AllowTcpForwarding no
+# ForceCommand cvs server
+
+<% if scope.lookupvar('::sshd::hardened') == 'yes' -%>
+<% if (scope.function_versioncmp([scope.lookupvar('::ssh_version'),'6.5'])) >= 0 -%>
+KexAlgorithms curve25519-sha256@libssh.org
+Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr
+MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-ripemd160-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,hmac-ripemd160,umac-128@openssh.com
+<% else -%>
+Ciphers aes256-ctr
+MACs hmac-sha1
+<% end -%>
+<% end -%>
+
+<% unless (s=scope.lookupvar('::sshd::tail_additional_options')).empty? -%>
+<%= s %>
+<% end -%>
diff --git a/puppet/modules/sshd/templates/sshd_config/Ubuntu.erb b/puppet/modules/sshd/templates/sshd_config/Ubuntu.erb
new file mode 100644
index 00000000..a326ab87
--- /dev/null
+++ b/puppet/modules/sshd/templates/sshd_config/Ubuntu.erb
@@ -0,0 +1,133 @@
+# This file is managed by Puppet, all local modifications will be overwritten
+#
+# Package generated configuration file
+# See the sshd(8) manpage for details
+
+<% unless (s=scope.lookupvar('::sshd::head_additional_options')).empty? -%>
+<%= s %>
+<% end -%>
+
+# What ports, IPs and protocols we listen for
+<% scope.lookupvar('::sshd::ports').to_a.each do |port| -%>
+<% if port == 'off' -%>
+#Port -- disabled by puppet
+<% else -%>
+Port <%= port %>
+<% end -%>
+<% end -%>
+
+# Use these options to restrict which interfaces/protocols sshd will bind to
+<% scope.lookupvar('::sshd::listen_address').to_a.each do |address| -%>
+ListenAddress <%= address %>
+<% end -%>
+Protocol 2
+# HostKeys for protocol version 2
+<% scope.lookupvar('::sshd::hostkey_type').to_a.each do |hostkey_type| -%>
+HostKey /etc/ssh/ssh_host_<%=hostkey_type %>_key
+<% end -%>
+
+#Privilege Separation is turned on for security
+UsePrivilegeSeparation yes
+
+# Lifetime and size of ephemeral version 1 server key
+KeyRegenerationInterval 3600
+ServerKeyBits 768
+
+# Logging
+SyslogFacility AUTH
+LogLevel INFO
+
+# Authentication:
+LoginGraceTime 120
+PermitRootLogin <%= scope.lookupvar('::sshd::permit_root_login') %>
+
+StrictModes <%= scope.lookupvar('::sshd::strict_modes') %>
+
+RSAAuthentication <%= scope.lookupvar('::sshd::rsa_authentication') %>
+
+PubkeyAuthentication <%= scope.lookupvar('::sshd::pubkey_authentication') %>
+
+AuthorizedKeysFile <%= scope.lookupvar('::sshd::authorized_keys_file') %>
+
+# Don't read the user's ~/.rhosts and ~/.shosts files
+IgnoreRhosts <%= scope.lookupvar('::sshd::ignore_rhosts') %>
+# For this to work you will also need host keys in /etc/ssh_known_hosts
+RhostsRSAAuthentication <%= scope.lookupvar('::sshd::rhosts_rsa_authentication') %>
+# similar for protocol version 2
+HostbasedAuthentication <%= scope.lookupvar('::sshd::hostbased_authentication') %>
+# Uncomment if you don't trust ~/.ssh/known_hosts for RhostsRSAAuthentication
+#IgnoreUserKnownHosts yes
+
+# To enable empty passwords, change to yes (NOT RECOMMENDED)
+PermitEmptyPasswords <%= scope.lookupvar('::sshd::permit_empty_passwords') %>
+
+# Change to yes to enable challenge-response passwords (beware issues with
+# some PAM modules and threads)
+ChallengeResponseAuthentication <%= scope.lookupvar('::sshd::challenge_response_authentication') %>
+
+# To disable tunneled clear text passwords, change to no here!
+PasswordAuthentication <%= scope.lookupvar('::sshd::password_authentication') %>
+
+# Kerberos options
+KerberosAuthentication <%= scope.lookupvar('::sshd::kerberos_authentication') %>
+KerberosOrLocalPasswd <%= scope.lookupvar('::sshd::kerberos_orlocalpasswd') %>
+KerberosTicketCleanup <%= scope.lookupvar('::sshd::kerberos_ticketcleanup') %>
+
+# GSSAPI options
+GSSAPIAuthentication <%= scope.lookupvar('::sshd::gssapi_authentication') %>
+GSSAPICleanupCredentials <%= scope.lookupvar('::sshd::gssapi_cleanupcredentials') %>
+
+X11Forwarding <%= scope.lookupvar('::sshd::x11_forwarding') %>
+X11DisplayOffset 10
+PrintMotd <%= scope.lookupvar('::sshd::print_motd') %>
+PrintLastLog yes
+TCPKeepAlive yes
+
+#UseLogin no
+
+#MaxStartups 10:30:60
+#Banner /etc/issue.net
+# do not reveal debian version (default is yes)
+DebianBanner no
+
+# Allow client to pass locale environment variables
+AcceptEnv LANG LC_*
+
+Subsystem sftp <%= (s=scope.lookupvar('::sshd::sftp_subsystem')).empty? ? '/usr/lib/openssh/sftp-server' : s %>
+
+# Set this to 'yes' to enable PAM authentication, account processing,
+# and session processing. If this is enabled, PAM authentication will
+# be allowed through the ChallengeResponseAuthentication and
+# PasswordAuthentication. Depending on your PAM configuration,
+# PAM authentication via ChallengeResponseAuthentication may bypass
+# the setting of "PermitRootLogin without-password".
+# If you just want the PAM account and session checks to run without
+# PAM authentication, then enable this but set PasswordAuthentication
+# and ChallengeResponseAuthentication to 'no'.
+UsePAM <%= scope.lookupvar('::sshd::use_pam') %>
+
+AllowTcpForwarding <%= scope.lookupvar('::sshd::tcp_forwarding') %>
+
+AllowAgentForwarding <%= scope.lookupvar('::sshd::agent_forwarding') %>
+
+<% unless (s=scope.lookupvar('::sshd::allowed_users')).empty? -%>
+AllowUsers <%= s %>
+<% end -%>
+<% unless (s=scope.lookupvar('::sshd::allowed_groups')).empty? -%>
+AllowGroups <%= s %>
+<%- end -%>
+
+<% if scope.lookupvar('::sshd::hardened') == 'yes' -%>
+<% if (scope.function_versioncmp([scope.lookupvar('::ssh_version'),'6.5'])) >= 0 -%>
+KexAlgorithms curve25519-sha256@libssh.org
+Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr
+MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-ripemd160-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,hmac-ripemd160,umac-128@openssh.com
+<% else -%>
+Ciphers aes256-ctr
+MACs hmac-sha1
+<% end -%>
+<% end -%>
+
+<% unless (s=scope.lookupvar('::sshd::tail_additional_options')).empty? -%>
+<%= s %>
+<% end -%>
diff --git a/puppet/modules/sshd/templates/sshd_config/Ubuntu_lucid.erb b/puppet/modules/sshd/templates/sshd_config/Ubuntu_lucid.erb
new file mode 100644
index 00000000..be7c56d0
--- /dev/null
+++ b/puppet/modules/sshd/templates/sshd_config/Ubuntu_lucid.erb
@@ -0,0 +1,136 @@
+# Package generated configuration file
+# See the sshd(8) manpage for details
+
+<% unless (s=scope.lookupvar('::sshd::head_additional_options')).empty? -%>
+<%= s %>
+<% end -%>
+
+# What ports, IPs and protocols we listen for
+<% scope.lookupvar('::sshd::ports').to_a.each do |port| -%>
+<% if port == 'off' -%>
+#Port -- disabled by puppet
+<% else -%>
+Port <%= port %>
+<% end -%>
+<% end -%>
+
+# Use these options to restrict which interfaces/protocols sshd will bind to
+<% scope.lookupvar('::sshd::listen_address').to_a.each do |address| -%>
+ListenAddress <%= address %>
+<% end -%>
+Protocol 2
+# HostKeys for protocol version 2
+<% scope.lookupvar('::sshd::hostkey_type').to_a.each do |hostkey_type| -%>
+HostKey /etc/ssh/ssh_host_<%=hostkey_type %>_key
+<% end -%>
+
+#Privilege Separation is turned on for security
+UsePrivilegeSeparation yes
+
+# ...but breaks Pam auth via kbdint, so we have to turn it off
+# Use PAM authentication via keyboard-interactive so PAM modules can
+# properly interface with the user (off due to PrivSep)
+#PAMAuthenticationViaKbdInt no
+# Lifetime and size of ephemeral version 1 server key
+KeyRegenerationInterval 3600
+ServerKeyBits 768
+
+# Logging
+SyslogFacility AUTH
+LogLevel INFO
+
+# Authentication:
+LoginGraceTime 600
+PermitRootLogin <%= scope.lookupvar('::sshd::permit_root_login') %>
+
+StrictModes <%= scope.lookupvar('::sshd::strict_modes') %>
+
+RSAAuthentication <%= scope.lookupvar('::sshd::rsa_authentication') %>
+
+PubkeyAuthentication <%= scope.lookupvar('::sshd::pubkey_authentication') %>
+
+AuthorizedKeysFile <%= scope.lookupvar('::sshd::authorized_keys_file') %>
+
+# For this to work you will also need host keys in /etc/ssh_known_hosts
+RhostsRSAAuthentication <%= scope.lookupvar('::sshd::rhosts_rsa_authentication') %>
+
+# Don't read the user's ~/.rhosts and ~/.shosts files
+IgnoreRhosts <%= scope.lookupvar('::sshd::ignore_rhosts') %>
+
+# similar for protocol version 2
+HostbasedAuthentication <%= scope.lookupvar('::sshd::hostbased_authentication') %>
+
+# Uncomment if you don't trust ~/.ssh/known_hosts for RhostsRSAAuthentication
+#IgnoreUserKnownHosts yes
+
+# To enable empty passwords, change to yes (NOT RECOMMENDED)
+PermitEmptyPasswords <%= scope.lookupvar('::sshd::permit_empty_passwords') %>
+
+# Change to no to disable s/key passwords
+ChallengeResponseAuthentication <%= scope.lookupvar('::sshd::challenge_response_authentication') %>
+
+# To disable tunneled clear text passwords, change to no here!
+PasswordAuthentication <%= scope.lookupvar('::sshd::password_authentication') %>
+
+# To change Kerberos options
+#KerberosAuthentication no
+#KerberosOrLocalPasswd yes
+#AFSTokenPassing no
+#KerberosTicketCleanup no
+
+# Kerberos TGT Passing does only work with the AFS kaserver
+#KerberosTgtPassing yes
+
+X11Forwarding <%= scope.lookupvar('::sshd::x11_forwarding') %>
+X11DisplayOffset 10
+KeepAlive yes
+#UseLogin no
+
+#MaxStartups 10:30:60
+#Banner /etc/issue.net
+# do not reveal debian version (default is yes)
+DebianBanner no
+#ReverseMappingCheck yes
+
+Subsystem sftp <%= (s=scope.lookupvar('::sshd::sftp_subsystem')).empty? ? '/usr/lib/openssh/sftp-server' : s %>
+
+# Set this to 'yes' to enable PAM authentication, account processing,
+# and session processing. If this is enabled, PAM authentication will
+# be allowed through the ChallengeResponseAuthentication and
+# PasswordAuthentication. Depending on your PAM configuration,
+# PAM authentication via ChallengeResponseAuthentication may bypass
+# the setting of "PermitRootLogin without-password".
+# If you just want the PAM account and session checks to run without
+# PAM authentication, then enable this but set PasswordAuthentication
+# and ChallengeResponseAuthentication to 'no'.
+UsePAM <%= scope.lookupvar('::sshd::use_pam') %>
+
+HostbasedUsesNameFromPacketOnly yes
+
+AllowTcpForwarding <%= scope.lookupvar('::sshd::tcp_forwarding') %>
+
+AllowAgentForwarding <%= scope.lookupvar('::sshd::agent_forwarding') %>
+
+<% unless (s=scope.lookupvar('::sshd::allowed_users')).empty? -%>
+AllowUsers <%= s %>
+<% end -%>
+<% unless (s=scope.lookupvar('::sshd::allowed_groups')).empty? -%>
+AllowGroups <%= s %>
+<%- end -%>
+
+PrintMotd <%= scope.lookupvar('::sshd::print_motd') %>
+
+<% if scope.lookupvar('::sshd::hardened') == 'yes' -%>
+<% if (scope.function_versioncmp([scope.lookupvar('::ssh_version'),'6.5'])) >= 0 -%>
+KexAlgorithms curve25519-sha256@libssh.org
+Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr
+MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-ripemd160-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,hmac-ripemd160,umac-128@openssh.com
+<% else -%>
+Ciphers aes256-ctr
+MACs hmac-sha1
+<% end -%>
+<% end -%>
+
+<% unless (s=scope.lookupvar('::sshd::tail_additional_options')).empty? -%>
+<%= s %>
+<% end -%>
diff --git a/puppet/modules/sshd/templates/sshd_config/Ubuntu_oneiric.erb b/puppet/modules/sshd/templates/sshd_config/Ubuntu_oneiric.erb
new file mode 120000
index 00000000..ccfb67c8
--- /dev/null
+++ b/puppet/modules/sshd/templates/sshd_config/Ubuntu_oneiric.erb
@@ -0,0 +1 @@
+Ubuntu_lucid.erb \ No newline at end of file
diff --git a/puppet/modules/sshd/templates/sshd_config/Ubuntu_precise.erb b/puppet/modules/sshd/templates/sshd_config/Ubuntu_precise.erb
new file mode 120000
index 00000000..6502bfce
--- /dev/null
+++ b/puppet/modules/sshd/templates/sshd_config/Ubuntu_precise.erb
@@ -0,0 +1 @@
+Ubuntu.erb \ No newline at end of file
diff --git a/puppet/modules/sshd/templates/sshd_config/XenServer_xenenterprise.erb b/puppet/modules/sshd/templates/sshd_config/XenServer_xenenterprise.erb
new file mode 120000
index 00000000..71b767a5
--- /dev/null
+++ b/puppet/modules/sshd/templates/sshd_config/XenServer_xenenterprise.erb
@@ -0,0 +1 @@
+CentOS_6.erb \ No newline at end of file
diff --git a/puppet/modules/stdlib/.fixtures.yml b/puppet/modules/stdlib/.fixtures.yml
new file mode 100644
index 00000000..37b73775
--- /dev/null
+++ b/puppet/modules/stdlib/.fixtures.yml
@@ -0,0 +1,3 @@
+fixtures:
+ symlinks:
+ stdlib: "#{source_dir}"
diff --git a/puppet/modules/stdlib/.gemspec b/puppet/modules/stdlib/.gemspec
new file mode 100644
index 00000000..e2749509
--- /dev/null
+++ b/puppet/modules/stdlib/.gemspec
@@ -0,0 +1,40 @@
+#
+# -*- encoding: utf-8 -*-
+
+Gem::Specification.new do |s|
+ s.name = "puppetmodule-stdlib"
+
+ s.version = "4.0.2"
+
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
+ s.authors = ["Puppet Labs"]
+ s.date = "2013-04-12"
+ s.description = [ 'This Gem format of the stdlib module is intended to make',
+ 'it easier for _module authors_ to resolve dependencies',
+ 'using a Gemfile when running automated testing jobs like',
+ 'Travis or Jenkins. The recommended best practice for',
+ 'installation by end users is to use the `puppet module',
+ 'install` command to install stdlib from the [Puppet',
+ 'Forge](http://forge.puppetlabs.com/puppetlabs/stdlib).' ].join(' ')
+ s.email = "puppet-dev@puppetlabs.com"
+ s.executables = []
+ s.files = [ 'CHANGELOG', 'CONTRIBUTING.md', 'Gemfile', 'LICENSE', 'Modulefile',
+ 'README.markdown', 'README_DEVELOPER.markdown', 'RELEASE_PROCESS.markdown',
+ 'Rakefile', 'spec/spec.opts' ]
+ s.files += Dir['lib/**/*.rb'] + Dir['manifests/**/*.pp'] + Dir['tests/**/*.pp'] + Dir['spec/**/*.rb']
+ s.homepage = "http://forge.puppetlabs.com/puppetlabs/stdlib"
+ s.rdoc_options = ["--title", "Puppet Standard Library Development Gem", "--main", "README.markdown", "--line-numbers"]
+ s.require_paths = ["lib"]
+ s.rubyforge_project = "puppetmodule-stdlib"
+ s.rubygems_version = "1.8.24"
+ s.summary = "This gem provides a way to make the standard library available for other module spec testing tasks."
+
+ if s.respond_to? :specification_version then
+ s.specification_version = 3
+
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
+ else
+ end
+ else
+ end
+end
diff --git a/puppet/modules/stdlib/.gitignore b/puppet/modules/stdlib/.gitignore
new file mode 100644
index 00000000..b5db85e0
--- /dev/null
+++ b/puppet/modules/stdlib/.gitignore
@@ -0,0 +1,9 @@
+pkg/
+Gemfile.lock
+vendor/
+spec/fixtures/
+.vagrant/
+.bundle/
+coverage/
+.idea/
+*.iml
diff --git a/puppet/modules/stdlib/.project b/puppet/modules/stdlib/.project
new file mode 100644
index 00000000..4e2c033a
--- /dev/null
+++ b/puppet/modules/stdlib/.project
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>stdlib</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.cloudsmith.geppetto.pp.dsl.ui.modulefileBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.xtext.ui.shared.xtextBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.cloudsmith.geppetto.pp.dsl.ui.puppetNature</nature>
+ <nature>org.eclipse.xtext.ui.shared.xtextNature</nature>
+ </natures>
+</projectDescription>
diff --git a/puppet/modules/stdlib/.rspec b/puppet/modules/stdlib/.rspec
new file mode 100644
index 00000000..7ab5f55c
--- /dev/null
+++ b/puppet/modules/stdlib/.rspec
@@ -0,0 +1,4 @@
+--color
+--format
+progress
+--backtrace
diff --git a/puppet/modules/stdlib/.sync.yml b/puppet/modules/stdlib/.sync.yml
new file mode 100644
index 00000000..21e872e0
--- /dev/null
+++ b/puppet/modules/stdlib/.sync.yml
@@ -0,0 +1,9 @@
+---
+.travis.yml:
+ script: "\"bundle exec rake validate && bundle exec rake lint && bundle exec rake spec SPEC_OPTS='--color --format documentation'\""
+Rakefile:
+ unmanaged: true
+Gemfile:
+ unmanaged: true
+spec/spec_helper.rb:
+ unmanaged: true
diff --git a/puppet/modules/stdlib/.travis.yml b/puppet/modules/stdlib/.travis.yml
new file mode 100644
index 00000000..503e1844
--- /dev/null
+++ b/puppet/modules/stdlib/.travis.yml
@@ -0,0 +1,18 @@
+---
+sudo: false
+language: ruby
+bundler_args: --without system_tests
+script: "bundle exec rake validate && bundle exec rake lint && bundle exec rake spec SPEC_OPTS='--color --format documentation'"
+matrix:
+ fast_finish: true
+ include:
+ - rvm: 1.8.7
+ env: PUPPET_GEM_VERSION="~> 2.7.0" FACTER_GEM_VERSION="~> 1.6.0"
+ - rvm: 1.8.7
+ env: PUPPET_GEM_VERSION="~> 2.7.0" FACTER_GEM_VERSION="~> 1.7.0"
+ - rvm: 1.9.3
+ env: PUPPET_GEM_VERSION="~> 3.0"
+ - rvm: 2.0.0
+ env: PUPPET_GEM_VERSION="~> 3.0"
+notifications:
+ email: false
diff --git a/puppet/modules/stdlib/CHANGELOG.md b/puppet/modules/stdlib/CHANGELOG.md
new file mode 100644
index 00000000..13856950
--- /dev/null
+++ b/puppet/modules/stdlib/CHANGELOG.md
@@ -0,0 +1,500 @@
+##2015-01-14 - Supported Release 4.6.0
+###Summary
+
+Improved functionality and preparing for Puppet Next with new parser
+
+####Features
+- MODULES-444: concat can now take more than two arrays
+- basename function added to have Ruby File.basename functionality
+- delete function can now take an array of items to remove
+- MODULES-1473: deprecate type function in favor of type_of
+
+####Bugfixes
+- Several test case fixes
+- Ensure_resource is more verbose on debug mode
+
+##2015-01-14 - Supported Release 4.5.1
+###Summary
+
+This release changes the temporary facter_dot_d cache locations outside of the /tmp directory due to a possible security vunerability. CVE-2015-1029
+
+####Bugfixes
+- Facter_dot_d cache will now be stored in puppet libdir instead of tmp
+
+##2014-12-15 - Supported Release 4.5.0
+###Summary
+
+This release improves functionality of the member function and adds improved future parser support.
+
+####Features
+- MODULES-1329: Update member() to allow the variable to be an array.
+- Sync .travis.yml, Gemfile, Rakefile, and CONTRIBUTING.md via modulesync
+
+####Bugfixes
+- Fix range() to work with numeric ranges with the future parser
+- Accurately express SLES support in metadata.json (was missing 10SP4 and 12)
+- Don't require `line` to match the `match` parameter
+
+##2014-11-10 - Supported Release 4.4.0
+###Summary
+This release has an overhauled readme, new private manifest function, and fixes many future parser bugs.
+
+####Features
+- All new shiny README
+- New `private()` function for making private manifests (yay!)
+
+####Bugfixes
+- Code reuse in `bool2num()` and `zip()`
+- Fix many functions to handle `generate()` no longer returning a string on new puppets
+- `concat()` no longer modifies the first argument (whoops)
+- strict variable support for `getvar()`, `member()`, `values_at`, and `has_interface_with()`
+- `to_bytes()` handles PB and EB now
+- Fix `tempfile` ruby requirement for `validate_augeas()` and `validate_cmd()`
+- Fix `validate_cmd()` for windows
+- Correct `validate_string()` docs to reflect non-handling of `undef`
+- Fix `file_line` matching on older rubies
+
+
+##2014-07-15 - Supported Release 4.3.2
+###Summary
+
+This release merely updates metadata.json so the module can be uninstalled and
+upgraded via the puppet module command.
+
+##2014-07-14 - Supported Release 4.3.1
+### Summary
+This supported release updates the metadata.json to work around upgrade behavior of the PMT.
+
+#### Bugfixes
+- Synchronize metadata.json with PMT-generated metadata to pass checksums
+
+##2014-06-27 - Supported Release 4.3.0
+### Summary
+This release is the first supported release of the stdlib 4 series. It remains
+backwards-compatible with the stdlib 3 series. It adds two new functions, one bugfix, and many testing updates.
+
+#### Features
+- New `bool2str()` function
+- New `camalcase()` function
+
+#### Bugfixes
+- Fix `has_interface_with()` when interfaces fact is nil
+
+##2014-06-04 - Release 4.2.2
+### Summary
+
+This release adds PE3.3 support in the metadata and fixes a few tests.
+
+## 2014-05-08 - Release - 4.2.1
+### Summary
+This release moves a stray symlink that can cause problems.
+
+## 2014-05-08 - Release - 4.2.0
+### Summary
+This release adds many new functions and fixes, and continues to be backwards compatible with stdlib 3.x
+
+#### Features
+- New `base64()` function
+- New `deep_merge()` function
+- New `delete_undef_values()` function
+- New `delete_values()` function
+- New `difference()` function
+- New `intersection()` function
+- New `is_bool()` function
+- New `pick_default()` function
+- New `union()` function
+- New `validate_ipv4_address` function
+- New `validate_ipv6_address` function
+- Update `ensure_packages()` to take an option hash as a second parameter.
+- Update `range()` to take an optional third argument for range step
+- Update `validate_slength()` to take an optional third argument for minimum length
+- Update `file_line` resource to take `after` and `multiple` attributes
+
+#### Bugfixes
+- Correct `is_string`, `is_domain_name`, `is_array`, `is_float`, and `is_function_available` for parsing odd types such as bools and hashes.
+- Allow facts.d facts to contain `=` in the value
+- Fix `root_home` fact on darwin systems
+- Fix `concat()` to work with a second non-array argument
+- Fix `floor()` to work with integer strings
+- Fix `is_integer()` to return true if passed integer strings
+- Fix `is_numeric()` to return true if passed integer strings
+- Fix `merge()` to work with empty strings
+- Fix `pick()` to raise the correct error type
+- Fix `uriescape()` to use the default URI.escape list
+- Add/update unit & acceptance tests.
+
+
+##2014-03-04 - Supported Release - 3.2.1
+###Summary
+This is a supported release
+
+####Bugfixes
+- Fixed `is_integer`/`is_float`/`is_numeric` for checking the value of arithmatic expressions.
+
+####Known bugs
+* No known bugs
+
+---
+
+##### 2013-05-06 - Jeff McCune <jeff@puppetlabs.com> - 4.1.0
+
+ * (#20582) Restore facter\_dot\_d to stdlib for PE users (3b887c8)
+ * (maint) Update Gemfile with GEM\_FACTER\_VERSION (f44d535)
+
+##### 2013-05-06 - Alex Cline <acline@us.ibm.com> - 4.1.0
+
+ * Terser method of string to array conversion courtesy of ethooz. (d38bce0)
+
+##### 2013-05-06 - Alex Cline <acline@us.ibm.com> 4.1.0
+
+ * Refactor ensure\_resource expectations (b33cc24)
+
+##### 2013-05-06 - Alex Cline <acline@us.ibm.com> 4.1.0
+
+ * Changed str-to-array conversion and removed abbreviation. (de253db)
+
+##### 2013-05-03 - Alex Cline <acline@us.ibm.com> 4.1.0
+
+ * (#20548) Allow an array of resource titles to be passed into the ensure\_resource function (e08734a)
+
+##### 2013-05-02 - Raphaël Pinson <raphael.pinson@camptocamp.com> - 4.1.0
+
+ * Add a dirname function (2ba9e47)
+
+##### 2013-04-29 - Mark Smith-Guerrero <msmithgu@gmail.com> - 4.1.0
+
+ * (maint) Fix a small typo in hash() description (928036a)
+
+##### 2013-04-12 - Jeff McCune <jeff@puppetlabs.com> - 4.0.2
+
+ * Update user information in gemspec to make the intent of the Gem clear.
+
+##### 2013-04-11 - Jeff McCune <jeff@puppetlabs.com> - 4.0.1
+
+ * Fix README function documentation (ab3e30c)
+
+##### 2013-04-11 - Jeff McCune <jeff@puppetlabs.com> - 4.0.0
+
+ * stdlib 4.0 drops support with Puppet 2.7
+ * stdlib 4.0 preserves support with Puppet 3
+
+##### 2013-04-11 - Jeff McCune <jeff@puppetlabs.com> - 4.0.0
+
+ * Add ability to use puppet from git via bundler (9c5805f)
+
+##### 2013-04-10 - Jeff McCune <jeff@puppetlabs.com> - 4.0.0
+
+ * (maint) Make stdlib usable as a Ruby GEM (e81a45e)
+
+##### 2013-04-10 - Erik Dalén <dalen@spotify.com> - 4.0.0
+
+ * Add a count function (f28550e)
+
+##### 2013-03-31 - Amos Shapira <ashapira@atlassian.com> - 4.0.0
+
+ * (#19998) Implement any2array (7a2fb80)
+
+##### 2013-03-29 - Steve Huff <shuff@vecna.org> - 4.0.0
+
+ * (19864) num2bool match fix (8d217f0)
+
+##### 2013-03-20 - Erik Dalén <dalen@spotify.com> - 4.0.0
+
+ * Allow comparisons of Numeric and number as String (ff5dd5d)
+
+##### 2013-03-26 - Richard Soderberg <rsoderberg@mozilla.com> - 4.0.0
+
+ * add suffix function to accompany the prefix function (88a93ac)
+
+##### 2013-03-19 - Kristof Willaert <kristof.willaert@gmail.com> - 4.0.0
+
+ * Add floor function implementation and unit tests (0527341)
+
+##### 2012-04-03 - Eric Shamow <eric@puppetlabs.com> - 4.0.0
+
+ * (#13610) Add is\_function\_available to stdlib (961dcab)
+
+##### 2012-12-17 - Justin Lambert <jlambert@eml.cc> - 4.0.0
+
+ * str2bool should return a boolean if called with a boolean (5d5a4d4)
+
+##### 2012-10-23 - Uwe Stuehler <ustuehler@team.mobile.de> - 4.0.0
+
+ * Fix number of arguments check in flatten() (e80207b)
+
+##### 2013-03-11 - Jeff McCune <jeff@puppetlabs.com> - 4.0.0
+
+ * Add contributing document (96e19d0)
+
+##### 2013-03-04 - Raphaël Pinson <raphael.pinson@camptocamp.com> - 4.0.0
+
+ * Add missing documentation for validate\_augeas and validate\_cmd to README.markdown (a1510a1)
+
+##### 2013-02-14 - Joshua Hoblitt <jhoblitt@cpan.org> - 4.0.0
+
+ * (#19272) Add has\_element() function (95cf3fe)
+
+##### 2013-02-07 - Raphaël Pinson <raphael.pinson@camptocamp.com> - 4.0.0
+
+ * validate\_cmd(): Use Puppet::Util::Execution.execute when available (69248df)
+
+##### 2012-12-06 - Raphaël Pinson <raphink@gmail.com> - 4.0.0
+
+ * Add validate\_augeas function (3a97c23)
+
+##### 2012-12-06 - Raphaël Pinson <raphink@gmail.com> - 4.0.0
+
+ * Add validate\_cmd function (6902cc5)
+
+##### 2013-01-14 - David Schmitt <david@dasz.at> - 4.0.0
+
+ * Add geppetto project definition (b3fc0a3)
+
+##### 2013-01-02 - Jaka Hudoklin <jakahudoklin@gmail.com> - 4.0.0
+
+ * Add getparam function to get defined resource parameters (20e0e07)
+
+##### 2013-01-05 - Jeff McCune <jeff@puppetlabs.com> - 4.0.0
+
+ * (maint) Add Travis CI Support (d082046)
+
+##### 2012-12-04 - Jeff McCune <jeff@puppetlabs.com> - 4.0.0
+
+ * Clarify that stdlib 3 supports Puppet 3 (3a6085f)
+
+##### 2012-11-30 - Erik Dalén <dalen@spotify.com> - 4.0.0
+
+ * maint: style guideline fixes (7742e5f)
+
+##### 2012-11-09 - James Fryman <james@frymanet.com> - 4.0.0
+
+ * puppet-lint cleanup (88acc52)
+
+##### 2012-11-06 - Joe Julian <me@joejulian.name> - 4.0.0
+
+ * Add function, uriescape, to URI.escape strings. Redmine #17459 (fd52b8d)
+
+##### 2012-09-18 - Chad Metcalf <chad@wibidata.com> - 3.2.0
+
+ * Add an ensure\_packages function. (8a8c09e)
+
+##### 2012-11-23 - Erik Dalén <dalen@spotify.com> - 3.2.0
+
+ * (#17797) min() and max() functions (9954133)
+
+##### 2012-05-23 - Peter Meier <peter.meier@immerda.ch> - 3.2.0
+
+ * (#14670) autorequire a file\_line resource's path (dfcee63)
+
+##### 2012-11-19 - Joshua Harlan Lifton <lifton@puppetlabs.com> - 3.2.0
+
+ * Add join\_keys\_to\_values function (ee0f2b3)
+
+##### 2012-11-17 - Joshua Harlan Lifton <lifton@puppetlabs.com> - 3.2.0
+
+ * Extend delete function for strings and hashes (7322e4d)
+
+##### 2012-08-03 - Gary Larizza <gary@puppetlabs.com> - 3.2.0
+
+ * Add the pick() function (ba6dd13)
+
+##### 2012-03-20 - Wil Cooley <wcooley@pdx.edu> - 3.2.0
+
+ * (#13974) Add predicate functions for interface facts (f819417)
+
+##### 2012-11-06 - Joe Julian <me@joejulian.name> - 3.2.0
+
+ * Add function, uriescape, to URI.escape strings. Redmine #17459 (70f4a0e)
+
+##### 2012-10-25 - Jeff McCune <jeff@puppetlabs.com> - 3.1.1
+
+ * (maint) Fix spec failures resulting from Facter API changes (97f836f)
+
+##### 2012-10-23 - Matthaus Owens <matthaus@puppetlabs.com> - 3.1.0
+
+ * Add PE facts to stdlib (cdf3b05)
+
+##### 2012-08-16 - Jeff McCune <jeff@puppetlabs.com> - 3.0.1
+
+ * Fix accidental removal of facts\_dot\_d.rb in 3.0.0 release
+
+##### 2012-08-16 - Jeff McCune <jeff@puppetlabs.com> - 3.0.0
+
+ * stdlib 3.0 drops support with Puppet 2.6
+ * stdlib 3.0 preserves support with Puppet 2.7
+
+##### 2012-08-07 - Dan Bode <dan@puppetlabs.com> - 3.0.0
+
+ * Add function ensure\_resource and defined\_with\_params (ba789de)
+
+##### 2012-07-10 - Hailee Kenney <hailee@puppetlabs.com> - 3.0.0
+
+ * (#2157) Remove facter\_dot\_d for compatibility with external facts (f92574f)
+
+##### 2012-04-10 - Chris Price <chris@puppetlabs.com> - 3.0.0
+
+ * (#13693) moving logic from local spec\_helper to puppetlabs\_spec\_helper (85f96df)
+
+##### 2012-10-25 - Jeff McCune <jeff@puppetlabs.com> - 2.5.1
+
+ * (maint) Fix spec failures resulting from Facter API changes (97f836f)
+
+##### 2012-10-23 - Matthaus Owens <matthaus@puppetlabs.com> - 2.5.0
+
+ * Add PE facts to stdlib (cdf3b05)
+
+##### 2012-08-15 - Dan Bode <dan@puppetlabs.com> - 2.5.0
+
+ * Explicitly load functions used by ensure\_resource (9fc3063)
+
+##### 2012-08-13 - Dan Bode <dan@puppetlabs.com> - 2.5.0
+
+ * Add better docs about duplicate resource failures (97d327a)
+
+##### 2012-08-13 - Dan Bode <dan@puppetlabs.com> - 2.5.0
+
+ * Handle undef for parameter argument (4f8b133)
+
+##### 2012-08-07 - Dan Bode <dan@puppetlabs.com> - 2.5.0
+
+ * Add function ensure\_resource and defined\_with\_params (a0cb8cd)
+
+##### 2012-08-20 - Jeff McCune <jeff@puppetlabs.com> - 2.5.0
+
+ * Disable tests that fail on 2.6.x due to #15912 (c81496e)
+
+##### 2012-08-20 - Jeff McCune <jeff@puppetlabs.com> - 2.5.0
+
+ * (Maint) Fix mis-use of rvalue functions as statements (4492913)
+
+##### 2012-08-20 - Jeff McCune <jeff@puppetlabs.com> - 2.5.0
+
+ * Add .rspec file to repo root (88789e8)
+
+##### 2012-06-07 - Chris Price <chris@puppetlabs.com> - 2.4.0
+
+ * Add support for a 'match' parameter to file\_line (a06c0d8)
+
+##### 2012-08-07 - Erik Dalén <dalen@spotify.com> - 2.4.0
+
+ * (#15872) Add to\_bytes function (247b69c)
+
+##### 2012-07-19 - Jeff McCune <jeff@puppetlabs.com> - 2.4.0
+
+ * (Maint) use PuppetlabsSpec::PuppetInternals.scope (master) (deafe88)
+
+##### 2012-07-10 - Hailee Kenney <hailee@puppetlabs.com> - 2.4.0
+
+ * (#2157) Make facts\_dot\_d compatible with external facts (5fb0ddc)
+
+##### 2012-03-16 - Steve Traylen <steve.traylen@cern.ch> - 2.4.0
+
+ * (#13205) Rotate array/string randomley based on fqdn, fqdn\_rotate() (fef247b)
+
+##### 2012-05-22 - Peter Meier <peter.meier@immerda.ch> - 2.3.3
+
+ * fix regression in #11017 properly (f0a62c7)
+
+##### 2012-05-10 - Jeff McCune <jeff@puppetlabs.com> - 2.3.3
+
+ * Fix spec tests using the new spec\_helper (7d34333)
+
+##### 2012-05-10 - Puppet Labs <support@puppetlabs.com> - 2.3.2
+
+ * Make file\_line default to ensure => present (1373e70)
+ * Memoize file\_line spec instance variables (20aacc5)
+ * Fix spec tests using the new spec\_helper (1ebfa5d)
+ * (#13595) initialize\_everything\_for\_tests couples modules Puppet ver (3222f35)
+ * (#13439) Fix MRI 1.9 issue with spec\_helper (15c5fd1)
+ * (#13439) Fix test failures with Puppet 2.6.x (665610b)
+ * (#13439) refactor spec helper for compatibility with both puppet 2.7 and master (82194ca)
+ * (#13494) Specify the behavior of zero padded strings (61891bb)
+
+##### 2012-03-29 Puppet Labs <support@puppetlabs.com> - 2.1.3
+
+* (#11607) Add Rakefile to enable spec testing
+* (#12377) Avoid infinite loop when retrying require json
+
+##### 2012-03-13 Puppet Labs <support@puppetlabs.com> - 2.3.1
+
+* (#13091) Fix LoadError bug with puppet apply and puppet\_vardir fact
+
+##### 2012-03-12 Puppet Labs <support@puppetlabs.com> - 2.3.0
+
+* Add a large number of new Puppet functions
+* Backwards compatibility preserved with 2.2.x
+
+##### 2011-12-30 Puppet Labs <support@puppetlabs.com> - 2.2.1
+
+* Documentation only release for the Forge
+
+##### 2011-12-30 Puppet Labs <support@puppetlabs.com> - 2.1.2
+
+* Documentation only release for PE 2.0.x
+
+##### 2011-11-08 Puppet Labs <support@puppetlabs.com> - 2.2.0
+
+* #10285 - Refactor json to use pson instead.
+* Maint - Add watchr autotest script
+* Maint - Make rspec tests work with Puppet 2.6.4
+* #9859 - Add root\_home fact and tests
+
+##### 2011-08-18 Puppet Labs <support@puppetlabs.com> - 2.1.1
+
+* Change facts.d paths to match Facter 2.0 paths.
+* /etc/facter/facts.d
+* /etc/puppetlabs/facter/facts.d
+
+##### 2011-08-17 Puppet Labs <support@puppetlabs.com> - 2.1.0
+
+* Add R.I. Pienaar's facts.d custom facter fact
+* facts defined in /etc/facts.d and /etc/puppetlabs/facts.d are
+ automatically loaded now.
+
+##### 2011-08-04 Puppet Labs <support@puppetlabs.com> - 2.0.0
+
+* Rename whole\_line to file\_line
+* This is an API change and as such motivating a 2.0.0 release according to semver.org.
+
+##### 2011-08-04 Puppet Labs <support@puppetlabs.com> - 1.1.0
+
+* Rename append\_line to whole\_line
+* This is an API change and as such motivating a 1.1.0 release.
+
+##### 2011-08-04 Puppet Labs <support@puppetlabs.com> - 1.0.0
+
+* Initial stable release
+* Add validate\_array and validate\_string functions
+* Make merge() function work with Ruby 1.8.5
+* Add hash merging function
+* Add has\_key function
+* Add loadyaml() function
+* Add append\_line native
+
+##### 2011-06-21 Jeff McCune <jeff@puppetlabs.com> - 0.1.7
+
+* Add validate\_hash() and getvar() functions
+
+##### 2011-06-15 Jeff McCune <jeff@puppetlabs.com> - 0.1.6
+
+* Add anchor resource type to provide containment for composite classes
+
+##### 2011-06-03 Jeff McCune <jeff@puppetlabs.com> - 0.1.5
+
+* Add validate\_bool() function to stdlib
+
+##### 0.1.4 2011-05-26 Jeff McCune <jeff@puppetlabs.com>
+
+* Move most stages after main
+
+##### 0.1.3 2011-05-25 Jeff McCune <jeff@puppetlabs.com>
+
+* Add validate\_re() function
+
+##### 0.1.2 2011-05-24 Jeff McCune <jeff@puppetlabs.com>
+
+* Update to add annotated tag
+
+##### 0.1.1 2011-05-24 Jeff McCune <jeff@puppetlabs.com>
+
+* Add stdlib::stages class with a standard set of stages
diff --git a/puppet/modules/stdlib/CONTRIBUTING.md b/puppet/modules/stdlib/CONTRIBUTING.md
new file mode 100644
index 00000000..f1cbde4b
--- /dev/null
+++ b/puppet/modules/stdlib/CONTRIBUTING.md
@@ -0,0 +1,220 @@
+Checklist (and a short version for the impatient)
+=================================================
+
+ * Commits:
+
+ - Make commits of logical units.
+
+ - Check for unnecessary whitespace with "git diff --check" before
+ committing.
+
+ - Commit using Unix line endings (check the settings around "crlf" in
+ git-config(1)).
+
+ - Do not check in commented out code or unneeded files.
+
+ - The first line of the commit message should be a short
+ description (50 characters is the soft limit, excluding ticket
+ number(s)), and should skip the full stop.
+
+ - Associate the issue in the message. The first line should include
+ the issue number in the form "(#XXXX) Rest of message".
+
+ - The body should provide a meaningful commit message, which:
+
+ - uses the imperative, present tense: "change", not "changed" or
+ "changes".
+
+ - includes motivation for the change, and contrasts its
+ implementation with the previous behavior.
+
+ - Make sure that you have tests for the bug you are fixing, or
+ feature you are adding.
+
+ - Make sure the test suites passes after your commit:
+ `bundle exec rspec spec/acceptance` More information on [testing](#Testing) below
+
+ - When introducing a new feature, make sure it is properly
+ documented in the README.md
+
+ * Submission:
+
+ * Pre-requisites:
+
+ - Make sure you have a [GitHub account](https://github.com/join)
+
+ - [Create a ticket](https://tickets.puppetlabs.com/secure/CreateIssue!default.jspa), or [watch the ticket](https://tickets.puppetlabs.com/browse/) you are patching for.
+
+ * Preferred method:
+
+ - Fork the repository on GitHub.
+
+ - Push your changes to a topic branch in your fork of the
+ repository. (the format ticket/1234-short_description_of_change is
+ usually preferred for this project).
+
+ - Submit a pull request to the repository in the puppetlabs
+ organization.
+
+The long version
+================
+
+ 1. Make separate commits for logically separate changes.
+
+ Please break your commits down into logically consistent units
+ which include new or changed tests relevant to the rest of the
+ change. The goal of doing this is to make the diff easier to
+ read for whoever is reviewing your code. In general, the easier
+ your diff is to read, the more likely someone will be happy to
+ review it and get it into the code base.
+
+ If you are going to refactor a piece of code, please do so as a
+ separate commit from your feature or bug fix changes.
+
+ We also really appreciate changes that include tests to make
+ sure the bug is not re-introduced, and that the feature is not
+ accidentally broken.
+
+ Describe the technical detail of the change(s). If your
+ description starts to get too long, that is a good sign that you
+ probably need to split up your commit into more finely grained
+ pieces.
+
+ Commits which plainly describe the things which help
+ reviewers check the patch and future developers understand the
+ code are much more likely to be merged in with a minimum of
+ bike-shedding or requested changes. Ideally, the commit message
+ would include information, and be in a form suitable for
+ inclusion in the release notes for the version of Puppet that
+ includes them.
+
+ Please also check that you are not introducing any trailing
+ whitespace or other "whitespace errors". You can do this by
+ running "git diff --check" on your changes before you commit.
+
+ 2. Sending your patches
+
+ To submit your changes via a GitHub pull request, we _highly_
+ recommend that you have them on a topic branch, instead of
+ directly on "master".
+ It makes things much easier to keep track of, especially if
+ you decide to work on another thing before your first change
+ is merged in.
+
+ GitHub has some pretty good
+ [general documentation](http://help.github.com/) on using
+ their site. They also have documentation on
+ [creating pull requests](http://help.github.com/send-pull-requests/).
+
+ In general, after pushing your topic branch up to your
+ repository on GitHub, you can switch to the branch in the
+ GitHub UI and click "Pull Request" towards the top of the page
+ in order to open a pull request.
+
+
+ 3. Update the related GitHub issue.
+
+ If there is a GitHub issue associated with the change you
+ submitted, then you should update the ticket to include the
+ location of your branch, along with any other commentary you
+ may wish to make.
+
+Testing
+=======
+
+Getting Started
+---------------
+
+Our puppet modules provide [`Gemfile`](./Gemfile)s which can tell a ruby
+package manager such as [bundler](http://bundler.io/) what Ruby packages,
+or Gems, are required to build, develop, and test this software.
+
+Please make sure you have [bundler installed](http://bundler.io/#getting-started)
+on your system, then use it to install all dependencies needed for this project,
+by running
+
+```shell
+% bundle install
+Fetching gem metadata from https://rubygems.org/........
+Fetching gem metadata from https://rubygems.org/..
+Using rake (10.1.0)
+Using builder (3.2.2)
+-- 8><-- many more --><8 --
+Using rspec-system-puppet (2.2.0)
+Using serverspec (0.6.3)
+Using rspec-system-serverspec (1.0.0)
+Using bundler (1.3.5)
+Your bundle is complete!
+Use `bundle show [gemname]` to see where a bundled gem is installed.
+```
+
+NOTE some systems may require you to run this command with sudo.
+
+If you already have those gems installed, make sure they are up-to-date:
+
+```shell
+% bundle update
+```
+
+With all dependencies in place and up-to-date we can now run the tests:
+
+```shell
+% rake spec
+```
+
+This will execute all the [rspec tests](http://rspec-puppet.com/) tests
+under [spec/defines](./spec/defines), [spec/classes](./spec/classes),
+and so on. rspec tests may have the same kind of dependencies as the
+module they are testing. While the module defines in its [Modulefile](./Modulefile),
+rspec tests define them in [.fixtures.yml](./fixtures.yml).
+
+Some puppet modules also come with [beaker](https://github.com/puppetlabs/beaker)
+tests. These tests spin up a virtual machine under
+[VirtualBox](https://www.virtualbox.org/)) with, controlling it with
+[Vagrant](http://www.vagrantup.com/) to actually simulate scripted test
+scenarios. In order to run these, you will need both of those tools
+installed on your system.
+
+You can run them by issuing the following command
+
+```shell
+% rake spec_clean
+% rspec spec/acceptance
+```
+
+This will now download a pre-fabricated image configured in the [default node-set](./spec/acceptance/nodesets/default.yml),
+install puppet, copy this module and install its dependencies per [spec/spec_helper_acceptance.rb](./spec/spec_helper_acceptance.rb)
+and then run all the tests under [spec/acceptance](./spec/acceptance).
+
+Writing Tests
+-------------
+
+XXX getting started writing tests.
+
+If you have commit access to the repository
+===========================================
+
+Even if you have commit access to the repository, you will still need to
+go through the process above, and have someone else review and merge
+in your changes. The rule is that all changes must be reviewed by a
+developer on the project (that did not write the code) to ensure that
+all changes go through a code review process.
+
+Having someone other than the author of the topic branch recorded as
+performing the merge is the record that they performed the code
+review.
+
+
+Additional Resources
+====================
+
+* [Getting additional help](http://puppetlabs.com/community/get-help)
+
+* [Writing tests](http://projects.puppetlabs.com/projects/puppet/wiki/Development_Writing_Tests)
+
+* [Patchwork](https://patchwork.puppetlabs.com)
+
+* [General GitHub documentation](http://help.github.com/)
+
+* [GitHub pull request documentation](http://help.github.com/send-pull-requests/)
+
diff --git a/puppet/modules/stdlib/Gemfile b/puppet/modules/stdlib/Gemfile
new file mode 100644
index 00000000..74a16f38
--- /dev/null
+++ b/puppet/modules/stdlib/Gemfile
@@ -0,0 +1,35 @@
+source ENV['GEM_SOURCE'] || 'https://rubygems.org'
+
+def location_for(place, fake_version = nil)
+ if place =~ /^(git[:@][^#]*)#(.*)/
+ [fake_version, { :git => $1, :branch => $2, :require => false }].compact
+ elsif place =~ /^file:\/\/(.*)/
+ ['>= 0', { :path => File.expand_path($1), :require => false }]
+ else
+ [place, { :require => false }]
+ end
+end
+
+group :development, :unit_tests do
+ gem 'rake', '~> 10.1.0', :require => false
+ gem 'rspec-puppet', :require => false
+ gem 'puppetlabs_spec_helper', :require => false
+ gem 'puppet-lint', :require => false
+ gem 'pry', :require => false
+ gem 'simplecov', :require => false
+end
+
+group :system_tests do
+ gem 'beaker-rspec', :require => false
+ gem 'serverspec', :require => false
+end
+
+ENV['GEM_PUPPET_VERSION'] ||= ENV['PUPPET_GEM_VERSION']
+puppetversion = ENV['GEM_PUPPET_VERSION']
+if puppetversion
+ gem 'puppet', *location_for(puppetversion)
+else
+ gem 'puppet', :require => false
+end
+
+# vim:ft=ruby
diff --git a/puppet/modules/stdlib/LICENSE b/puppet/modules/stdlib/LICENSE
new file mode 100644
index 00000000..ec0587c0
--- /dev/null
+++ b/puppet/modules/stdlib/LICENSE
@@ -0,0 +1,19 @@
+Copyright (C) 2011 Puppet Labs Inc
+
+and some parts:
+
+Copyright (C) 2011 Krzysztof Wilczynski
+
+Puppet Labs can be contacted at: info@puppetlabs.com
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/puppet/modules/stdlib/README.markdown b/puppet/modules/stdlib/README.markdown
new file mode 100644
index 00000000..e9a1f4e4
--- /dev/null
+++ b/puppet/modules/stdlib/README.markdown
@@ -0,0 +1,741 @@
+#stdlib
+
+[![Build Status](https://travis-ci.org/puppetlabs/puppetlabs-stdlib.png?branch=master)](https://travis-ci.org/puppetlabs/puppetlabs-stdlib)
+
+####Table of Contents
+
+1. [Overview](#overview)
+2. [Module Description - What the module does and why it is useful](#module-description)
+3. [Setup - The basics of getting started with stdlib](#setup)
+4. [Usage - Configuration options and additional functionality](#usage)
+5. [Reference - An under-the-hood peek at what the module is doing and how](#reference)
+5. [Limitations - OS compatibility, etc.](#limitations)
+6. [Development - Guide for contributing to the module](#development)
+
+##Overview
+
+Adds a standard library of resources for Puppet modules.
+
+##Module Description
+
+This module provides a standard library of resources for the development of Puppet
+modules. Puppet modules make heavy use of this standard library. The stdlib module adds the following resources to Puppet:
+
+ * Stages
+ * Facts
+ * Functions
+ * Defined resource types
+ * Types
+ * Providers
+
+> *Note:* As of version 3.7, Puppet Enterprise no longer includes the stdlib module. If you're running Puppet Enterprise, you should install the most recent release of stdlib for compatibility with Puppet modules.
+
+##Setup
+
+Installing the stdlib module adds the functions, facts, and resources of this standard library to Puppet.
+
+##Usage
+
+After you've installed stdlib, all of its functions, facts, and resources are available for module use or development.
+
+If you want to use a standardized set of run stages for Puppet, `include stdlib` in your manifest.
+
+## Reference
+
+### Classes
+
+#### Public Classes
+
+* `stdlib`: Most of stdlib's features are automatically loaded by Puppet. To use standardized run stages in Puppet, declare this class in your manifest with `include stdlib`.
+
+ When declared, stdlib declares all other classes in the module. The only other class currently included in the module is `stdlib::stages`.
+
+ The stdlib class has no parameters.
+
+#### Private Classes
+
+* `stdlib::stages`: This class manages a standard set of run stages for Puppet. It is managed by the stdlib class and should not be declared independently.
+
+ The `stdlib::stages` class declares various run stages for deploying infrastructure, language runtimes, and application layers. The high level stages are (in order):
+
+ * setup
+ * main
+ * runtime
+ * setup_infra
+ * deploy_infra
+ * setup_app
+ * deploy_app
+ * deploy
+
+ Sample usage:
+
+ ```
+ node default {
+ include stdlib
+ class { java: stage => 'runtime' }
+ }
+ ```
+
+### Resources
+
+* `file_line`: This resource ensures that a given line, including whitespace at the beginning and end, is contained within a file. If the line is not contained in the given file, Puppet will add the line. Multiple resources can be declared to manage multiple lines in the same file. You can also use match to replace existing lines.
+
+ ```
+ file_line { 'sudo_rule':
+ path => '/etc/sudoers',
+ line => '%sudo ALL=(ALL) ALL',
+ }
+ file_line { 'sudo_rule_nopw':
+ path => '/etc/sudoers',
+ line => '%sudonopw ALL=(ALL) NOPASSWD: ALL',
+ }
+ ```
+
+ * `after`: Specify the line after which Puppet will add any new lines. (Existing lines are added in place.) Optional.
+ * `ensure`: Ensures whether the resource is present. Valid values are 'present', 'absent'.
+ * `line`: The line to be added to the file located by the `path` parameter.
+ * `match`: A regular expression to run against existing lines in the file; if a match is found, we replace that line rather than adding a new line. Optional.
+ * `multiple`: Determine if match can change multiple lines. Valid values are 'true', 'false'. Optional.
+ * `name`: An arbitrary name used as the identity of the resource.
+ * `path`: The file in which Puppet will ensure the line specified by the line parameter.
+
+### Functions
+
+* `abs`: Returns the absolute value of a number; for example, '-34.56' becomes '34.56'. Takes a single integer and float value as an argument. *Type*: rvalue
+
+* `any2array`: This converts any object to an array containing that object. Empty argument lists are converted to an empty array. Arrays are left untouched. Hashes are converted to arrays of alternating keys and values. *Type*: rvalue
+
+* `base64`: Converts a string to and from base64 encoding.
+Requires an action ('encode', 'decode') and either a plain or base64-encoded
+string. *Type*: rvalue
+
+* `basename`: Returns the `basename` of a path (optionally stripping an extension). For example:
+ * ('/path/to/a/file.ext') returns 'file.ext'
+ * ('relative/path/file.ext') returns 'file.ext'
+ * ('/path/to/a/file.ext', '.ext') returns 'file'
+
+ *Type*: rvalue
+
+* `bool2num`: Converts a boolean to a number. Converts values:
+ * 'false', 'f', '0', 'n', and 'no' to 0.
+ * 'true', 't', '1', 'y', and 'yes' to 1.
+ Requires a single boolean or string as an input. *Type*: rvalue
+
+* `capitalize`: Capitalizes the first letter of a string or array of strings.
+Requires either a single string or an array as an input. *Type*: rvalue
+
+* `chomp`: Removes the record separator from the end of a string or an array of
+strings; for example, 'hello\n' becomes 'hello'. Requires a single string or array as an input. *Type*: rvalue
+
+* `chop`: Returns a new string with the last character removed. If the string ends with '\r\n', both characters are removed. Applying `chop` to an empty string returns an empty string. If you want to merely remove record separators, then you should use the `chomp` function. Requires a string or an array of strings as input. *Type*: rvalue
+
+* `concat`: Appends the contents of multiple arrays onto array 1. For example:
+ * `concat(['1','2','3'],'4')` results in: ['1','2','3','4'].
+ * `concat(['1','2','3'],'4',['5','6','7'])` results in: ['1','2','3','4','5','6','7'].
+
+* `count`: Takes an array as first argument and an optional second argument. Count the number of elements in array that matches second argument. If called with only an array, it counts the number of elements that are **not** nil/undef. *Type*: rvalue
+
+* `defined_with_params`: Takes a resource reference and an optional hash of attributes. Returns 'true' if a resource with the specified attributes has already been added to the catalog. Returns 'false' otherwise.
+
+ ```
+ user { 'dan':
+ ensure => present,
+ }
+
+ if ! defined_with_params(User[dan], {'ensure' => 'present' }) {
+ user { 'dan': ensure => present, }
+ }
+ ```
+
+ *Type*: rvalue
+
+* `delete`: Deletes all instances of a given element from an array, substring from a
+string, or key from a hash. For example, `delete(['a','b','c','b'], 'b')` returns ['a','c']; `delete('abracadabra', 'bra')` returns 'acada'. `delete({'a' => 1,'b' => 2,'c' => 3},['b','c'])` returns {'a'=> 1} *Type*: rvalue
+
+* `delete_at`: Deletes a determined indexed value from an array. For example, `delete_at(['a','b','c'], 1)` returns ['a','c']. *Type*: rvalue
+
+* `delete_values`: Deletes all instances of a given value from a hash. For example, `delete_values({'a'=>'A','b'=>'B','c'=>'C','B'=>'D'}, 'B')` returns {'a'=>'A','c'=>'C','B'=>'D'} *Type*: rvalue
+
+* `delete_undef_values`: Deletes all instances of the undef value from an array or hash. For example, `$hash = delete_undef_values({a=>'A', b=>'', c=>undef, d => false})` returns {a => 'A', b => '', d => false}. *Type*: rvalue
+
+* `difference`: Returns the difference between two arrays.
+The returned array is a copy of the original array, removing any items that
+also appear in the second array. For example, `difference(["a","b","c"],["b","c","d"])` returns ["a"].
+
+* `dirname`: Returns the `dirname` of a path. For example, `dirname('/path/to/a/file.ext')` returns '/path/to/a'.
+
+* `downcase`: Converts the case of a string or of all strings in an array to lowercase. *Type*: rvalue
+
+* `empty`: Returns 'true' if the variable is empty. *Type*: rvalue
+
+* `ensure_packages`: Takes a list of packages and only installs them if they don't already exist. It optionally takes a hash as a second parameter to be passed as the third argument to the `ensure_resource()` function. *Type*: statement
+
+* `ensure_resource`: Takes a resource type, title, and a list of attributes that describe a resource.
+
+ ```
+ user { 'dan':
+ ensure => present,
+ }
+ ```
+
+ This example only creates the resource if it does not already exist:
+
+ `ensure_resource('user', 'dan', {'ensure' => 'present' })`
+
+ If the resource already exists, but does not match the specified parameters, this function attempts to recreate the resource, leading to a duplicate resource definition error.
+
+ An array of resources can also be passed in, and each will be created with the type and parameters specified if it doesn't already exist.
+
+ `ensure_resource('user', ['dan','alex'], {'ensure' => 'present'})`
+
+ *Type*: statement
+
+* `flatten`: This function flattens any deeply nested arrays and returns a single flat array as a result. For example, `flatten(['a', ['b', ['c']]])` returns ['a','b','c']. *Type*: rvalue
+
+* `floor`: Returns the largest integer less than or equal to the argument.
+Takes a single numeric value as an argument. *Type*: rvalue
+
+* `fqdn_rotate`: Rotates an array a random number of times based on a node's fqdn. *Type*: rvalue
+
+* `get_module_path`: Returns the absolute path of the specified module for the current environment.
+
+ `$module_path = get_module_path('stdlib')`
+
+ *Type*: rvalue
+
+* `getparam`: Takes a resource reference and the name of the parameter and
+returns the value of the resource's parameter. For example, the following code returns 'param_value'.
+
+ *Example:*
+
+ ```
+ define example_resource($param) {
+ }
+
+ example_resource { "example_resource_instance":
+ param => "param_value"
+ }
+
+ getparam(Example_resource["example_resource_instance"], "param")
+ ```
+
+ *Type*: rvalue
+
+* `getvar`: Lookup a variable in a remote namespace.
+
+ For example:
+
+ ```
+ $foo = getvar('site::data::foo')
+ # Equivalent to $foo = $site::data::foo
+ ```
+
+ This is useful if the namespace itself is stored in a string:
+
+ ```
+ $datalocation = 'site::data'
+ $bar = getvar("${datalocation}::bar")
+ # Equivalent to $bar = $site::data::bar
+ ```
+
+ *Type*: rvalue
+
+* `grep`: This function searches through an array and returns any elements that match the provided regular expression. For example, `grep(['aaa','bbb','ccc','aaaddd'], 'aaa')` returns ['aaa','aaaddd']. *Type*: rvalue
+
+* `has_interface_with`: Returns boolean based on kind and value:
+ * macaddress
+ * netmask
+ * ipaddress
+ * network
+
+ *Examples:*
+
+ ```
+ has_interface_with("macaddress", "x:x:x:x:x:x")
+ has_interface_with("ipaddress", "127.0.0.1") => true
+ ```
+
+ If no kind is given, then the presence of the interface is checked:
+
+ ```
+ has_interface_with("lo") => true
+ ```
+
+ *Type*: rvalue
+
+* `has_ip_address`: Returns true if the client has the requested IP address on some interface. This function iterates through the `interfaces` fact and checks the `ipaddress_IFACE` facts, performing a simple string comparison. *Type*: rvalue
+
+* `has_ip_network`: Returns true if the client has an IP address within the requested network. This function iterates through the 'interfaces' fact and checks the 'network_IFACE' facts, performing a simple string comparision. *Type*: rvalue
+
+* `has_key`: Determine if a hash has a certain key value.
+
+ *Example*:
+
+ ```
+ $my_hash = {'key_one' => 'value_one'}
+ if has_key($my_hash, 'key_two') {
+ notice('we will not reach here')
+ }
+ if has_key($my_hash, 'key_one') {
+ notice('this will be printed')
+ }
+ ```
+
+ *Type*: rvalue
+
+* `hash`: This function converts an array into a hash. For example, `hash(['a',1,'b',2,'c',3])` returns {'a'=>1,'b'=>2,'c'=>3}. *Type*: rvalue
+
+* `intersection`: This function returns an array an intersection of two. For example, `intersection(["a","b","c"],["b","c","d"])` returns ["b","c"].
+
+* `is_array`: Returns 'true' if the variable passed to this function is an array. *Type*: rvalue
+
+* `is_bool`: Returns 'true' if the variable passed to this function is a boolean. *Type*: rvalue
+
+* `is_domain_name`: Returns 'true' if the string passed to this function is a syntactically correct domain name. *Type*: rvalue
+
+* `is_float`: Returns 'true' if the variable passed to this function is a float. *Type*: rvalue
+
+* `is_function_available`: This function accepts a string as an argument and determines whether the Puppet runtime has access to a function by that name. It returns 'true' if the function exists, 'false' if not. *Type*: rvalue
+
+* `is_hash`: Returns 'true' if the variable passed to this function is a hash. *Type*: rvalue
+
+* `is_integer`: Returns 'true' if the variable returned to this string is an integer. *Type*: rvalue
+
+* `is_ip_address`: Returns 'true' if the string passed to this function is a valid IP address. *Type*: rvalue
+
+* `is_mac_address`: Returns 'true' if the string passed to this function is a valid MAC address. *Type*: rvalue
+
+* `is_numeric`: Returns 'true' if the variable passed to this function is a number. *Type*: rvalue
+
+* `is_string`: Returns 'true' if the variable passed to this function is a string. *Type*: rvalue
+
+* `join`: This function joins an array into a string using a separator. For example, `join(['a','b','c'], ",")` results in: "a,b,c". *Type*: rvalue
+
+* `join_keys_to_values`: This function joins each key of a hash to that key's corresponding value with a separator. Keys and values are cast to strings. The return value is an array in which each element is one joined key/value pair. For example, `join_keys_to_values({'a'=>1,'b'=>2}, " is ")` results in ["a is 1","b is 2"]. *Type*: rvalue
+
+* `keys`: Returns the keys of a hash as an array. *Type*: rvalue
+
+* `loadyaml`: Load a YAML file containing an array, string, or hash, and return the data in the corresponding native data type. For example:
+
+ ```
+ $myhash = loadyaml('/etc/puppet/data/myhash.yaml')
+ ```
+
+ *Type*: rvalue
+
+* `lstrip`: Strips leading spaces to the left of a string. *Type*: rvalue
+
+* `max`: Returns the highest value of all arguments. Requires at least one argument. *Type*: rvalue
+
+* `member`: This function determines if a variable is a member of an array. The variable can be either a string, array, or fixnum. For example, `member(['a','b'], 'b')` and `member(['a','b','c'], ['b','c'])` return 'true', while `member(['a','b'], 'c')` and `member(['a','b','c'], ['c','d'])` return 'false'. *Type*: rvalue
+
+* `merge`: Merges two or more hashes together and returns the resulting hash.
+
+ *Example*:
+
+ ```
+ $hash1 = {'one' => 1, 'two' => 2}
+ $hash2 = {'two' => 'dos', 'three' => 'tres'}
+ $merged_hash = merge($hash1, $hash2)
+ # The resulting hash is equivalent to:
+ # $merged_hash = {'one' => 1, 'two' => 'dos', 'three' => 'tres'}
+ ```
+
+ When there is a duplicate key, the key in the rightmost hash "wins." *Type*: rvalue
+
+* `min`: Returns the lowest value of all arguments. Requires at least one argument. *Type*: rvalue
+
+* `num2bool`: This function converts a number or a string representation of a number into a true boolean. Zero or anything non-numeric becomes 'false'. Numbers greater than 0 become 'true'. *Type*: rvalue
+
+* `parsejson`: This function accepts JSON as a string and converts into the correct Puppet structure. *Type*: rvalue
+
+* `parseyaml`: This function accepts YAML as a string and converts it into the correct Puppet structure. *Type*: rvalue
+
+* `pick`: From a list of values, returns the first value that is not undefined or an empty string. Takes any number of arguments, and raises an error if all values are undefined or empty.
+
+ ```
+ $real_jenkins_version = pick($::jenkins_version, '1.449')
+ ```
+
+ *Type*: rvalue
+
+* `prefix`: This function applies a prefix to all elements in an array. For example, `prefix(['a','b','c'], 'p')` returns ['pa','pb','pc']. *Type*: rvalue
+
+
+* `private`: This function sets the current class or definition as private.
+Calling the class or definition from outside the current module will fail. For example, `private()` called in class `foo::bar` outputs the following message if class is called from outside module `foo`:
+
+ ```
+ Class foo::bar is private
+ ```
+
+ You can specify the error message you want to use:
+
+ ```
+ private("You're not supposed to do that!")
+ ```
+
+ *Type*: statement
+
+* `range`: When given range in the form of '(start, stop)', `range` extrapolates a range as an array. For example, `range("0", "9")` returns [0,1,2,3,4,5,6,7,8,9]. Zero-padded strings are converted to integers automatically, so `range("00", "09")` returns [0,1,2,3,4,5,6,7,8,9].
+
+ Non-integer strings are accepted; `range("a", "c")` returns ["a","b","c"], and `range("host01", "host10")` returns ["host01", "host02", ..., "host09", "host10"].
+
+ *Type*: rvalue
+
+* `reject`: This function searches through an array and rejects all elements that match the provided regular expression. For example, `reject(['aaa','bbb','ccc','aaaddd'], 'aaa')` returns ['bbb','ccc']. *Type*: rvalue
+
+* `reverse`: Reverses the order of a string or array. *Type*: rvalue
+
+* `rstrip`: Strips leading spaces to the right of the string.*Type*: rvalue
+
+* `shuffle`: Randomizes the order of a string or array elements. *Type*: rvalue
+
+* `size`: Returns the number of elements in a string or array. *Type*: rvalue
+
+* `sort`: Sorts strings and arrays lexically. *Type*: rvalue
+
+* `squeeze`: Returns a new string where runs of the same character that occur in this set are replaced by a single character. *Type*: rvalue
+
+* `str2bool`: This converts a string to a boolean. This attempts to convert strings that contain values such as '1', 't', 'y', and 'yes' to 'true' and strings that contain values such as '0', 'f', 'n', and 'no' to 'false'. *Type*: rvalue
+
+* `str2saltedsha512`: This converts a string to a salted-SHA512 password hash, used for OS X versions >= 10.7. Given any string, this function returns a hex version of a salted-SHA512 password hash, which can be inserted into your Puppet
+manifests as a valid password attribute. *Type*: rvalue
+
+* `strftime`: This function returns formatted time. For example, `strftime("%s")` returns the time since epoch, and `strftime("%Y=%m-%d")` returns the date. *Type*: rvalue
+
+ *Format:*
+
+ * `%a`: The abbreviated weekday name ('Sun')
+ * `%A`: The full weekday name ('Sunday')
+ * `%b`: The abbreviated month name ('Jan')
+ * `%B`: The full month name ('January')
+ * `%c`: The preferred local date and time representation
+ * `%C`: Century (20 in 2009)
+ * `%d`: Day of the month (01..31)
+ * `%D`: Date (%m/%d/%y)
+ * `%e`: Day of the month, blank-padded ( 1..31)
+ * `%F`: Equivalent to %Y-%m-%d (the ISO 8601 date format)
+ * `%h`: Equivalent to %b
+ * `%H`: Hour of the day, 24-hour clock (00..23)
+ * `%I`: Hour of the day, 12-hour clock (01..12)
+ * `%j`: Day of the year (001..366)
+ * `%k`: Hour, 24-hour clock, blank-padded ( 0..23)
+ * `%l`: Hour, 12-hour clock, blank-padded ( 0..12)
+ * `%L`: Millisecond of the second (000..999)
+ * `%m`: Month of the year (01..12)
+ * `%M`: Minute of the hour (00..59)
+ * `%n`: Newline (\n)
+ * `%N`: Fractional seconds digits, default is 9 digits (nanosecond)
+ * `%3N`: Millisecond (3 digits)
+ * `%6N`: Microsecond (6 digits)
+ * `%9N`: Nanosecond (9 digits)
+ * `%p`: Meridian indicator ('AM' or 'PM')
+ * `%P`: Meridian indicator ('am' or 'pm')
+ * `%r`: Time, 12-hour (same as %I:%M:%S %p)
+ * `%R`: Time, 24-hour (%H:%M)
+ * `%s`: Number of seconds since 1970-01-01 00:00:00 UTC.
+ * `%S`: Second of the minute (00..60)
+ * `%t`: Tab character ( )
+ * `%T`: Time, 24-hour (%H:%M:%S)
+ * `%u`: Day of the week as a decimal, Monday being 1. (1..7)
+ * `%U`: Week number of the current year, starting with the first Sunday as the first day of the first week (00..53)
+ * `%v`: VMS date (%e-%b-%Y)
+ * `%V`: Week number of year according to ISO 8601 (01..53)
+ * `%W`: Week number of the current year, starting with the first Monday as the first day of the first week (00..53)
+ * `%w`: Day of the week (Sunday is 0, 0..6)
+ * `%x`: Preferred representation for the date alone, no time
+ * `%X`: Preferred representation for the time alone, no date
+ * `%y`: Year without a century (00..99)
+ * `%Y`: Year with century
+ * `%z`: Time zone as hour offset from UTC (e.g. +0900)
+ * `%Z`: Time zone name
+ * `%%`: Literal '%' character
+
+
+* `strip`: This function removes leading and trailing whitespace from a string or from every string inside an array. For example, `strip(" aaa ")` results in "aaa". *Type*: rvalue
+
+* `suffix`: This function applies a suffix to all elements in an array. For example, `suffix(['a','b','c'], 'p')` returns ['ap','bp','cp']. *Type*: rvalue
+
+* `swapcase`: This function swaps the existing case of a string. For example, `swapcase("aBcD")` results in "AbCd". *Type*: rvalue
+
+* `time`: This function returns the current time since epoch as an integer. For example, `time()` returns something like '1311972653'. *Type*: rvalue
+
+* `to_bytes`: Converts the argument into bytes, for example 4 kB becomes 4096.
+Takes a single string value as an argument. *Type*: rvalue
+
+* `type3x`: Returns a string description of the type when passed a value. Type can be a string, array, hash, float, integer, or boolean. This function will be removed when puppet 3 support is dropped and the new type system may be used. *Type*: rvalue
+
+* `type_of`: Returns the literal type when passed a value. Requires the new
+ parser. Useful for comparison of types with `<=` such as in `if
+ type_of($some_value) <= Array[String] { ... }` (which is equivalent to `if
+ $some_value =~ Array[String] { ... }`) *Type*: rvalue
+
+* `union`: This function returns a union of two arrays. For example, `union(["a","b","c"],["b","c","d"])` returns ["a","b","c","d"].
+
+* `unique`: This function removes duplicates from strings and arrays. For example, `unique("aabbcc")` returns 'abc'.
+
+You can also use this with arrays. For example, `unique(["a","a","b","b","c","c"])` returns ["a","b","c"]. *Type*: rvalue
+
+* `upcase`: Converts a string or an array of strings to uppercase. For example, `upcase("abcd")` returns 'ABCD'. *Type*: rvalue
+
+* `uriescape`: Urlencodes a string or array of strings. Requires either a single string or an array as an input. *Type*: rvalue
+
+* `validate_absolute_path`: Validate the string represents an absolute path in the filesystem. This function works for Windows and Unix style paths.
+
+ The following values will pass:
+
+ ```
+ $my_path = 'C:/Program Files (x86)/Puppet Labs/Puppet'
+ validate_absolute_path($my_path)
+ $my_path2 = '/var/lib/puppet'
+ validate_absolute_path($my_path2)
+ $my_path3 = ['C:/Program Files (x86)/Puppet Labs/Puppet','C:/Program Files/Puppet Labs/Puppet']
+ validate_absolute_path($my_path3)
+ $my_path4 = ['/var/lib/puppet','/usr/share/puppet']
+ validate_absolute_path($my_path4)
+ ```
+
+ The following values will fail, causing compilation to abort:
+
+ ```
+ validate_absolute_path(true)
+ validate_absolute_path('../var/lib/puppet')
+ validate_absolute_path('var/lib/puppet')
+ validate_absolute_path([ 'var/lib/puppet', '/var/foo' ])
+ validate_absolute_path([ '/var/lib/puppet', 'var/foo' ])
+ $undefined = undef
+ validate_absolute_path($undefined)
+ ```
+
+ *Type*: statement
+
+* `validate_array`: Validate that all passed values are array data structures. Abort catalog compilation if any value fails this check.
+
+ The following values will pass:
+
+ ```
+ $my_array = [ 'one', 'two' ]
+ validate_array($my_array)
+ ```
+
+ The following values will fail, causing compilation to abort:
+
+ ```
+ validate_array(true)
+ validate_array('some_string')
+ $undefined = undef
+ validate_array($undefined)
+ ```
+
+ *Type*: statement
+
+* `validate_augeas`: Performs validation of a string using an Augeas lens.
+The first argument of this function should be the string to test, and the second argument should be the name of the Augeas lens to use. If Augeas fails to parse the string with the lens, the compilation aborts with a parse error.
+
+ A third optional argument lists paths which should **not** be found in the file. The `$file` variable points to the location of the temporary file being tested in the Augeas tree.
+
+ For example, to make sure your passwd content never contains user `foo`:
+
+ ```
+ validate_augeas($passwdcontent, 'Passwd.lns', ['$file/foo'])
+ ```
+
+ To ensure that no users use the '/bin/barsh' shell:
+
+ ```
+ validate_augeas($passwdcontent, 'Passwd.lns', ['$file/*[shell="/bin/barsh"]']
+ ```
+
+ You can pass a fourth argument as the error message raised and shown to the user:
+
+ ```
+ validate_augeas($sudoerscontent, 'Sudoers.lns', [], 'Failed to validate sudoers content with Augeas')
+ ```
+
+ *Type*: statement
+
+* `validate_bool`: Validate that all passed values are either true or false. Abort catalog compilation if any value fails this check.
+
+ The following values will pass:
+
+ ```
+ $iamtrue = true
+ validate_bool(true)
+ validate_bool(true, true, false, $iamtrue)
+ ```
+
+ The following values will fail, causing compilation to abort:
+
+ ```
+ $some_array = [ true ]
+ validate_bool("false")
+ validate_bool("true")
+ validate_bool($some_array)
+ ```
+
+ *Type*: statement
+
+* `validate_cmd`: Performs validation of a string with an external command. The first argument of this function should be a string to test, and the second argument should be a path to a test command taking a % as a placeholder for the file path (will default to the end of the command if no % placeholder given). If the command, launched against a tempfile containing the passed string, returns a non-null value, compilation will abort with a parse error.
+
+If a third argument is specified, this will be the error message raised and seen by the user.
+
+ ```
+ # Defaults to end of path
+ validate_cmd($sudoerscontent, '/usr/sbin/visudo -c -f', 'Visudo failed to validate sudoers content')
+ ```
+ ```
+ # % as file location
+ validate_cmd($haproxycontent, '/usr/sbin/haproxy -f % -c', 'Haproxy failed to validate config content')
+ ```
+
+ *Type*: statement
+
+* `validate_hash`: Validates that all passed values are hash data structures. Abort catalog compilation if any value fails this check.
+
+ The following values will pass:
+
+ ```
+ $my_hash = { 'one' => 'two' }
+ validate_hash($my_hash)
+ ```
+
+ The following values will fail, causing compilation to abort:
+
+ ```
+ validate_hash(true)
+ validate_hash('some_string')
+ $undefined = undef
+ validate_hash($undefined)
+ ```
+
+ *Type*: statement
+
+* `validate_re`: Performs simple validation of a string against one or more regular expressions. The first argument of this function should be the string to
+test, and the second argument should be a stringified regular expression
+(without the // delimiters) or an array of regular expressions. If none
+of the regular expressions match the string passed in, compilation aborts with a parse error.
+
+ You can pass a third argument as the error message raised and shown to the user.
+
+ The following strings validate against the regular expressions:
+
+ ```
+ validate_re('one', '^one$')
+ validate_re('one', [ '^one', '^two' ])
+ ```
+
+ The following string fails to validate, causing compilation to abort:
+
+ ```
+ validate_re('one', [ '^two', '^three' ])
+ ```
+
+ To set the error message:
+
+ ```
+ validate_re($::puppetversion, '^2.7', 'The $puppetversion fact value does not match 2.7')
+ ```
+
+ *Type*: statement
+
+* `validate_slength`: Validates that the first argument is a string (or an array of strings), and is less than or equal to the length of the second argument. It fails if the first argument is not a string or array of strings, or if arg 2 is not convertable to a number.
+
+ The following values pass:
+
+ ```
+ validate_slength("discombobulate",17)
+ validate_slength(["discombobulate","moo"],17)
+ ```
+
+ The following values fail:
+
+ ```
+ validate_slength("discombobulate",1)
+ validate_slength(["discombobulate","thermometer"],5)
+ ```
+
+ *Type*: statement
+
+* `validate_string`: Validates that all passed values are string data structures. Aborts catalog compilation if any value fails this check.
+
+ The following values pass:
+
+ ```
+ $my_string = "one two"
+ validate_string($my_string, 'three')
+ ```
+
+ The following values fail, causing compilation to abort:
+
+ ```
+ validate_string(true)
+ validate_string([ 'some', 'array' ])
+ $undefined = undef
+ validate_string($undefined)
+ ```
+
+ *Type*: statement
+
+* `values`: When given a hash, this function returns the values of that hash.
+
+ *Examples:*
+
+ ```
+ $hash = {
+ 'a' => 1,
+ 'b' => 2,
+ 'c' => 3,
+ }
+ values($hash)
+ ```
+
+ The example above returns [1,2,3].
+
+ *Type*: rvalue
+
+* `values_at`: Finds value inside an array based on location. The first argument is the array you want to analyze, and the second element can be a combination of:
+
+ * A single numeric index
+ * A range in the form of 'start-stop' (eg. 4-9)
+ * An array combining the above
+
+ For example, `values_at(['a','b','c'], 2)` returns ['c']; `values_at(['a','b','c'], ["0-1"])` returns ['a','b']; and `values_at(['a','b','c','d','e'], [0, "2-3"])` returns ['a','c','d'].
+
+ *Type*: rvalue
+
+* `zip`: Takes one element from first array and merges corresponding elements from second array. This generates a sequence of n-element arrays, where n is one more than the count of arguments. For example, `zip(['1','2','3'],['4','5','6'])` results in ["1", "4"], ["2", "5"], ["3", "6"]. *Type*: rvalue
+
+##Limitations
+
+As of Puppet Enterprise version 3.7, the stdlib module is no longer included in PE. PE users should install the most recent release of stdlib for compatibility with Puppet modules.
+
+###Version Compatibility
+
+Versions | Puppet 2.6 | Puppet 2.7 | Puppet 3.x | Puppet 4.x |
+:---------------|:-----:|:---:|:---:|:----:
+**stdlib 2.x** | **yes** | **yes** | no | no
+**stdlib 3.x** | no | **yes** | **yes** | no
+**stdlib 4.x** | no | **yes** | **yes** | no
+**stdlib 5.x** | no | no | **yes** | **yes**
+
+**stdlib 5.x**: When released, stdlib 5.x will drop support for Puppet 2.7.x. Please see [this discussion](https://github.com/puppetlabs/puppetlabs-stdlib/pull/176#issuecomment-30251414).
+
+##Development
+
+Puppet Labs modules on the Puppet Forge are open projects, and community contributions are essential for keeping them great. We can’t access the huge number of platforms and myriad of hardware, software, and deployment configurations that Puppet is intended to serve.
+
+We want to keep it as easy as possible to contribute changes so that our modules work in your environment. There are a few guidelines that we need contributors to follow so that we can have a chance of keeping on top of things.
+
+You can read the complete module contribution guide on the [Puppet Labs wiki](http://projects.puppetlabs.com/projects/module-site/wiki/Module_contributing).
+
+To report or research a bug with any part of this module, please go to
+[http://tickets.puppetlabs.com/browse/PUP](http://tickets.puppetlabs.com/browse/PUP).
+
+##Contributors
+
+The list of contributors can be found at: https://github.com/puppetlabs/puppetlabs-stdlib/graphs/contributors
+
+
+
+
diff --git a/puppet/modules/stdlib/README_DEVELOPER.markdown b/puppet/modules/stdlib/README_DEVELOPER.markdown
new file mode 100644
index 00000000..04349ed7
--- /dev/null
+++ b/puppet/modules/stdlib/README_DEVELOPER.markdown
@@ -0,0 +1,35 @@
+Puppet Specific Facts
+=====================
+
+Facter is meant to stand alone and apart from Puppet. However, Facter often
+runs inside Puppet and all custom facts included in the stdlib module will
+almost always be evaluated in the context of Puppet and Facter working
+together.
+
+Still, we don't want to write custom facts that blow up in the users face if
+Puppet is not loaded in memory. This is often the case if the user runs
+`facter` without also supplying the `--puppet` flag.
+
+Ah! But Jeff, the custom fact won't be in the `$LOAD_PATH` unless the user
+supplies `--facter`! You might say...
+
+Not (always) true I say! If the user happens to have a CWD of
+`<modulepath>/stdlib/lib` then the facts will automatically be evaluated and
+blow up.
+
+In any event, it's pretty easy to write a fact that has no value if Puppet is
+not loaded. Simply do it like this:
+
+ Facter.add(:node_vardir) do
+ setcode do
+ # This will be nil if Puppet is not available.
+ Facter::Util::PuppetSettings.with_puppet do
+ Puppet[:vardir]
+ end
+ end
+ end
+
+The `Facter::Util::PuppetSettings.with_puppet` method accepts a block and
+yields to it only if the Puppet library is loaded. If the Puppet library is
+not loaded, then the method silently returns `nil` which Facter interprets as
+an undefined fact value. The net effect is that the fact won't be set.
diff --git a/puppet/modules/stdlib/README_SPECS.markdown b/puppet/modules/stdlib/README_SPECS.markdown
new file mode 100644
index 00000000..917b6310
--- /dev/null
+++ b/puppet/modules/stdlib/README_SPECS.markdown
@@ -0,0 +1,7 @@
+NOTE
+====
+
+This project's specs depend on puppet core, and thus they require the
+`puppetlabs_spec_helper` project. For more information please see the README
+in that project, which can be found here: [puppetlabs spec
+helper](https://github.com/puppetlabs/puppetlabs_spec_helper)
diff --git a/puppet/modules/stdlib/RELEASE_PROCESS.markdown b/puppet/modules/stdlib/RELEASE_PROCESS.markdown
new file mode 100644
index 00000000..0f9328ed
--- /dev/null
+++ b/puppet/modules/stdlib/RELEASE_PROCESS.markdown
@@ -0,0 +1,24 @@
+# Contributing to this module #
+
+ * Work in a topic branch
+ * Submit a github pull request
+ * Address any comments / feeback
+ * Merge into master using --no-ff
+
+# Releasing this module #
+
+ * This module adheres to http://semver.org/
+ * Look for API breaking changes using git diff vX.Y.Z..master
+ * If no API breaking changes, the minor version may be bumped.
+ * If there are API breaking changes, the major version must be bumped.
+ * If there are only small minor changes, the patch version may be bumped.
+ * Update the CHANGELOG
+ * Update the Modulefile
+ * Commit these changes with a message along the lines of "Update CHANGELOG and
+ Modulefile for release"
+ * Create an annotated tag with git tag -a vX.Y.Z -m 'version X.Y.Z' (NOTE the
+ leading v as per semver.org)
+ * Push the tag with git push origin --tags
+ * Build a new package with puppet-module or the rake build task if it exists
+ * Publish the new package to the forge
+ * Bonus points for an announcement to puppet-users.
diff --git a/puppet/modules/stdlib/Rakefile b/puppet/modules/stdlib/Rakefile
new file mode 100644
index 00000000..4ed1327a
--- /dev/null
+++ b/puppet/modules/stdlib/Rakefile
@@ -0,0 +1,18 @@
+require 'rubygems'
+require 'puppetlabs_spec_helper/rake_tasks'
+require 'puppet-lint/tasks/puppet-lint'
+PuppetLint.configuration.send('disable_80chars')
+PuppetLint.configuration.ignore_paths = ["spec/**/*.pp", "pkg/**/*.pp"]
+
+desc "Validate manifests, templates, and ruby files in lib."
+task :validate do
+ Dir['manifests/**/*.pp'].each do |manifest|
+ sh "puppet parser validate --noop #{manifest}"
+ end
+ Dir['lib/**/*.rb'].each do |lib_file|
+ sh "ruby -c #{lib_file}"
+ end
+ Dir['templates/**/*.erb'].each do |template|
+ sh "erb -P -x -T '-' #{template} | ruby -c"
+ end
+end
diff --git a/puppet/modules/stdlib/lib/facter/facter_dot_d.rb b/puppet/modules/stdlib/lib/facter/facter_dot_d.rb
new file mode 100644
index 00000000..b0584370
--- /dev/null
+++ b/puppet/modules/stdlib/lib/facter/facter_dot_d.rb
@@ -0,0 +1,202 @@
+# A Facter plugin that loads facts from /etc/facter/facts.d
+# and /etc/puppetlabs/facter/facts.d.
+#
+# Facts can be in the form of JSON, YAML or Text files
+# and any executable that returns key=value pairs.
+#
+# In the case of scripts you can also create a file that
+# contains a cache TTL. For foo.sh store the ttl as just
+# a number in foo.sh.ttl
+#
+# The cache is stored in /tmp/facts_cache.yaml as a mode
+# 600 file and will have the end result of not calling your
+# fact scripts more often than is needed
+
+class Facter::Util::DotD
+ require 'yaml'
+
+ def initialize(dir="/etc/facts.d", cache_file=File.join(Puppet[:libdir], "facts_dot_d.cache"))
+ @dir = dir
+ @cache_file = cache_file
+ @cache = nil
+ @types = {".txt" => :txt, ".json" => :json, ".yaml" => :yaml}
+ end
+
+ def entries
+ Dir.entries(@dir).reject { |f| f =~ /^\.|\.ttl$/ }.sort.map { |f| File.join(@dir, f) }
+ rescue
+ []
+ end
+
+ def fact_type(file)
+ extension = File.extname(file)
+
+ type = @types[extension] || :unknown
+
+ type = :script if type == :unknown && File.executable?(file)
+
+ return type
+ end
+
+ def txt_parser(file)
+ File.readlines(file).each do |line|
+ if line =~ /^([^=]+)=(.+)$/
+ var = $1; val = $2
+
+ Facter.add(var) do
+ setcode { val }
+ end
+ end
+ end
+ rescue Exception => e
+ Facter.warn("Failed to handle #{file} as text facts: #{e.class}: #{e}")
+ end
+
+ def json_parser(file)
+ begin
+ require 'json'
+ rescue LoadError
+ retry if require 'rubygems'
+ raise
+ end
+
+ JSON.load(File.read(file)).each_pair do |f, v|
+ Facter.add(f) do
+ setcode { v }
+ end
+ end
+ rescue Exception => e
+ Facter.warn("Failed to handle #{file} as json facts: #{e.class}: #{e}")
+ end
+
+ def yaml_parser(file)
+ require 'yaml'
+
+ YAML.load_file(file).each_pair do |f, v|
+ Facter.add(f) do
+ setcode { v }
+ end
+ end
+ rescue Exception => e
+ Facter.warn("Failed to handle #{file} as yaml facts: #{e.class}: #{e}")
+ end
+
+ def script_parser(file)
+ result = cache_lookup(file)
+ ttl = cache_time(file)
+
+ unless result
+ result = Facter::Util::Resolution.exec(file)
+
+ if ttl > 0
+ Facter.debug("Updating cache for #{file}")
+ cache_store(file, result)
+ cache_save!
+ end
+ else
+ Facter.debug("Using cached data for #{file}")
+ end
+
+ result.split("\n").each do |line|
+ if line =~ /^(.+)=(.+)$/
+ var = $1; val = $2
+
+ Facter.add(var) do
+ setcode { val }
+ end
+ end
+ end
+ rescue Exception => e
+ Facter.warn("Failed to handle #{file} as script facts: #{e.class}: #{e}")
+ Facter.debug(e.backtrace.join("\n\t"))
+ end
+
+ def cache_save!
+ cache = load_cache
+ File.open(@cache_file, "w", 0600) { |f| f.write(YAML.dump(cache)) }
+ rescue
+ end
+
+ def cache_store(file, data)
+ load_cache
+
+ @cache[file] = {:data => data, :stored => Time.now.to_i}
+ rescue
+ end
+
+ def cache_lookup(file)
+ cache = load_cache
+
+ return nil if cache.empty?
+
+ ttl = cache_time(file)
+
+ if cache[file]
+ now = Time.now.to_i
+
+ return cache[file][:data] if ttl == -1
+ return cache[file][:data] if (now - cache[file][:stored]) <= ttl
+ return nil
+ else
+ return nil
+ end
+ rescue
+ return nil
+ end
+
+ def cache_time(file)
+ meta = file + ".ttl"
+
+ return File.read(meta).chomp.to_i
+ rescue
+ return 0
+ end
+
+ def load_cache
+ unless @cache
+ if File.exist?(@cache_file)
+ @cache = YAML.load_file(@cache_file)
+ else
+ @cache = {}
+ end
+ end
+
+ return @cache
+ rescue
+ @cache = {}
+ return @cache
+ end
+
+ def create
+ entries.each do |fact|
+ type = fact_type(fact)
+ parser = "#{type}_parser"
+
+ if respond_to?("#{type}_parser")
+ Facter.debug("Parsing #{fact} using #{parser}")
+
+ send(parser, fact)
+ end
+ end
+ end
+end
+
+
+mdata = Facter.version.match(/(\d+)\.(\d+)\.(\d+)/)
+if mdata
+ (major, minor, patch) = mdata.captures.map { |v| v.to_i }
+ if major < 2
+ # Facter 1.7 introduced external facts support directly
+ unless major == 1 and minor > 6
+ Facter::Util::DotD.new("/etc/facter/facts.d").create
+ Facter::Util::DotD.new("/etc/puppetlabs/facter/facts.d").create
+
+ # Windows has a different configuration directory that defaults to a vendor
+ # specific sub directory of the %COMMON_APPDATA% directory.
+ if Dir.const_defined? 'COMMON_APPDATA' then
+ windows_facts_dot_d = File.join(Dir::COMMON_APPDATA, 'PuppetLabs', 'facter', 'facts.d')
+ Facter::Util::DotD.new(windows_facts_dot_d).create
+ end
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/lib/facter/netmask_cidr_interface.rb b/puppet/modules/stdlib/lib/facter/netmask_cidr_interface.rb
new file mode 100644
index 00000000..d628d08c
--- /dev/null
+++ b/puppet/modules/stdlib/lib/facter/netmask_cidr_interface.rb
@@ -0,0 +1,22 @@
+# adds netmask facts for each interface in cidr notation
+# i.e.:
+# ...
+# netmask_cidr_eth2 => 24
+# netmask_cidr_lo => 8
+# netmask_cidr_tun0 => 32
+# netmask_cidr_virbr0 => 24
+# ...
+
+require 'facter/util/ip'
+
+Facter::Util::IP.get_interfaces.each do |interface|
+ netmask = Facter.value("netmask_#{interface}")
+ if netmask != nil
+ Facter.add("netmask_cidr_" + interface ) do
+ setcode do
+ cidr_netmask=IPAddr.new(netmask).to_i.to_s(2).count("1")
+ cidr_netmask
+ end
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/lib/facter/pe_version.rb b/puppet/modules/stdlib/lib/facter/pe_version.rb
new file mode 100644
index 00000000..0cc0f64e
--- /dev/null
+++ b/puppet/modules/stdlib/lib/facter/pe_version.rb
@@ -0,0 +1,53 @@
+# Fact: is_pe, pe_version, pe_major_version, pe_minor_version, pe_patch_version
+#
+# Purpose: Return various facts about the PE state of the system
+#
+# Resolution: Uses a regex match against puppetversion to determine whether the
+# machine has Puppet Enterprise installed, and what version (overall, major,
+# minor, patch) is installed.
+#
+# Caveats:
+#
+Facter.add("pe_version") do
+ setcode do
+ pe_ver = Facter.value("puppetversion").match(/Puppet Enterprise (\d+\.\d+\.\d+)/)
+ pe_ver[1] if pe_ver
+ end
+end
+
+Facter.add("is_pe") do
+ setcode do
+ if Facter.value(:pe_version).to_s.empty? then
+ false
+ else
+ true
+ end
+ end
+end
+
+Facter.add("pe_major_version") do
+ confine :is_pe => true
+ setcode do
+ if pe_version = Facter.value(:pe_version)
+ pe_version.to_s.split('.')[0]
+ end
+ end
+end
+
+Facter.add("pe_minor_version") do
+ confine :is_pe => true
+ setcode do
+ if pe_version = Facter.value(:pe_version)
+ pe_version.to_s.split('.')[1]
+ end
+ end
+end
+
+Facter.add("pe_patch_version") do
+ confine :is_pe => true
+ setcode do
+ if pe_version = Facter.value(:pe_version)
+ pe_version.to_s.split('.')[2]
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/lib/facter/puppet_vardir.rb b/puppet/modules/stdlib/lib/facter/puppet_vardir.rb
new file mode 100644
index 00000000..0e6af40e
--- /dev/null
+++ b/puppet/modules/stdlib/lib/facter/puppet_vardir.rb
@@ -0,0 +1,26 @@
+# This facter fact returns the value of the Puppet vardir setting for the node
+# running puppet or puppet agent. The intent is to enable Puppet modules to
+# automatically have insight into a place where they can place variable data,
+# regardless of the node's platform.
+#
+# The value should be directly usable in a File resource path attribute.
+
+
+begin
+ require 'facter/util/puppet_settings'
+rescue LoadError => e
+ # puppet apply does not add module lib directories to the $LOAD_PATH (See
+ # #4248). It should (in the future) but for the time being we need to be
+ # defensive which is what this rescue block is doing.
+ rb_file = File.join(File.dirname(__FILE__), 'util', 'puppet_settings.rb')
+ load rb_file if File.exists?(rb_file) or raise e
+end
+
+Facter.add(:puppet_vardir) do
+ setcode do
+ # This will be nil if Puppet is not available.
+ Facter::Util::PuppetSettings.with_puppet do
+ Puppet[:vardir]
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/lib/facter/root_home.rb b/puppet/modules/stdlib/lib/facter/root_home.rb
new file mode 100644
index 00000000..b4f87ff2
--- /dev/null
+++ b/puppet/modules/stdlib/lib/facter/root_home.rb
@@ -0,0 +1,32 @@
+# A facter fact to determine the root home directory.
+# This varies on PE supported platforms and may be
+# reconfigured by the end user.
+
+module Facter::Util::RootHome
+ class << self
+ def get_root_home
+ root_ent = Facter::Util::Resolution.exec("getent passwd root")
+ # The home directory is the sixth element in the passwd entry
+ # If the platform doesn't have getent, root_ent will be nil and we should
+ # return it straight away.
+ root_ent && root_ent.split(":")[5]
+ end
+ end
+end
+
+Facter.add(:root_home) do
+ setcode { Facter::Util::RootHome.get_root_home }
+end
+
+Facter.add(:root_home) do
+ confine :kernel => :darwin
+ setcode do
+ str = Facter::Util::Resolution.exec("dscacheutil -q user -a name root")
+ hash = {}
+ str.split("\n").each do |pair|
+ key,value = pair.split(/:/)
+ hash[key] = value
+ end
+ hash['dir'].strip
+ end
+end
diff --git a/puppet/modules/stdlib/lib/facter/util/puppet_settings.rb b/puppet/modules/stdlib/lib/facter/util/puppet_settings.rb
new file mode 100644
index 00000000..1ad94521
--- /dev/null
+++ b/puppet/modules/stdlib/lib/facter/util/puppet_settings.rb
@@ -0,0 +1,21 @@
+module Facter
+ module Util
+ module PuppetSettings
+ # This method is intended to provide a convenient way to evaluate a
+ # Facter code block only if Puppet is loaded. This is to account for the
+ # situation where the fact happens to be in the load path, but Puppet is
+ # not loaded for whatever reason. Perhaps the user is simply running
+ # facter without the --puppet flag and they happen to be working in a lib
+ # directory of a module.
+ def self.with_puppet
+ begin
+ Module.const_get("Puppet")
+ rescue NameError
+ nil
+ else
+ yield
+ end
+ end
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/lib/puppet/functions/type_of.rb b/puppet/modules/stdlib/lib/puppet/functions/type_of.rb
new file mode 100644
index 00000000..02cdd4db
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/functions/type_of.rb
@@ -0,0 +1,17 @@
+# Returns the type when passed a value.
+#
+# @example how to compare values' types
+# # compare the types of two values
+# if type_of($first_value) != type_of($second_value) { fail("first_value and second_value are different types") }
+# @example how to compare against an abstract type
+# unless type_of($first_value) <= Numeric { fail("first_value must be Numeric") }
+# unless type_of{$first_value) <= Collection[1] { fail("first_value must be an Array or Hash, and contain at least one element") }
+#
+# See the documentation for "The Puppet Type System" for more information about types.
+# See the `assert_type()` function for flexible ways to assert the type of a value.
+#
+Puppet::Functions.create_function(:type_of) do
+ def type_of(value)
+ Puppet::Pops::Types::TypeCalculator.infer_set(value)
+ end
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/abs.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/abs.rb
new file mode 100644
index 00000000..11d2d7fe
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/abs.rb
@@ -0,0 +1,36 @@
+#
+# abs.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:abs, :type => :rvalue, :doc => <<-EOS
+ Returns the absolute value of a number, for example -34.56 becomes
+ 34.56. Takes a single integer and float value as an argument.
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "abs(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ value = arguments[0]
+
+ # Numbers in Puppet are often string-encoded which is troublesome ...
+ if value.is_a?(String)
+ if value.match(/^-?(?:\d+)(?:\.\d+){1}$/)
+ value = value.to_f
+ elsif value.match(/^-?\d+$/)
+ value = value.to_i
+ else
+ raise(Puppet::ParseError, 'abs(): Requires float or ' +
+ 'integer to work with')
+ end
+ end
+
+ # We have numeric value to handle ...
+ result = value.abs
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/any2array.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/any2array.rb
new file mode 100644
index 00000000..e71407e8
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/any2array.rb
@@ -0,0 +1,33 @@
+#
+# any2array.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:any2array, :type => :rvalue, :doc => <<-EOS
+This converts any object to an array containing that object. Empty argument
+lists are converted to an empty array. Arrays are left untouched. Hashes are
+converted to arrays of alternating keys and values.
+ EOS
+ ) do |arguments|
+
+ if arguments.empty?
+ return []
+ end
+
+ if arguments.length == 1
+ if arguments[0].kind_of?(Array)
+ return arguments[0]
+ elsif arguments[0].kind_of?(Hash)
+ result = []
+ arguments[0].each do |key, value|
+ result << key << value
+ end
+ return result
+ end
+ end
+
+ return arguments
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/base64.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/base64.rb
new file mode 100644
index 00000000..617ba31b
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/base64.rb
@@ -0,0 +1,37 @@
+module Puppet::Parser::Functions
+
+ newfunction(:base64, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args|
+
+ Base64 encode or decode a string based on the command and the string submitted
+
+ Usage:
+
+ $encodestring = base64('encode','thestring')
+ $decodestring = base64('decode','dGhlc3RyaW5n')
+
+ ENDHEREDOC
+
+ require 'base64'
+
+ raise Puppet::ParseError, ("base64(): Wrong number of arguments (#{args.length}; must be = 2)") unless args.length == 2
+
+ actions = ['encode','decode']
+
+ unless actions.include?(args[0])
+ raise Puppet::ParseError, ("base64(): the first argument must be one of 'encode' or 'decode'")
+ end
+
+ unless args[1].is_a?(String)
+ raise Puppet::ParseError, ("base64(): the second argument must be a string to base64")
+ end
+
+ case args[0]
+ when 'encode'
+ result = Base64.encode64(args[1])
+ when 'decode'
+ result = Base64.decode64(args[1])
+ end
+
+ return result
+ end
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/basename.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/basename.rb
new file mode 100644
index 00000000..f7e44384
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/basename.rb
@@ -0,0 +1,34 @@
+module Puppet::Parser::Functions
+ newfunction(:basename, :type => :rvalue, :doc => <<-EOS
+ Strips directory (and optional suffix) from a filename
+ EOS
+ ) do |arguments|
+
+ if arguments.size < 1 then
+ raise(Puppet::ParseError, "basename(): No arguments given")
+ elsif arguments.size > 2 then
+ raise(Puppet::ParseError, "basename(): Too many arguments given (#{arguments.size})")
+ else
+
+ unless arguments[0].is_a?(String)
+ raise(Puppet::ParseError, 'basename(): Requires string as first argument')
+ end
+
+ if arguments.size == 1 then
+ rv = File.basename(arguments[0])
+ elsif arguments.size == 2 then
+
+ unless arguments[1].is_a?(String)
+ raise(Puppet::ParseError, 'basename(): Requires string as second argument')
+ end
+
+ rv = File.basename(arguments[0], arguments[1])
+ end
+
+ end
+
+ return rv
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/bool2num.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/bool2num.rb
new file mode 100644
index 00000000..6ad6cf4e
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/bool2num.rb
@@ -0,0 +1,26 @@
+#
+# bool2num.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:bool2num, :type => :rvalue, :doc => <<-EOS
+ Converts a boolean to a number. Converts the values:
+ false, f, 0, n, and no to 0
+ true, t, 1, y, and yes to 1
+ Requires a single boolean or string as an input.
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "bool2num(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ value = function_str2bool([arguments[0]])
+
+ # We have real boolean values as well ...
+ result = value ? 1 : 0
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/bool2str.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/bool2str.rb
new file mode 100644
index 00000000..fcd37917
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/bool2str.rb
@@ -0,0 +1,27 @@
+#
+# bool2str.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:bool2str, :type => :rvalue, :doc => <<-EOS
+ Converts a boolean to a string.
+ Requires a single boolean as an input.
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "bool2str(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ value = arguments[0]
+ klass = value.class
+
+ # We can have either true or false, and nothing else
+ unless [FalseClass, TrueClass].include?(klass)
+ raise(Puppet::ParseError, 'bool2str(): Requires a boolean to work with')
+ end
+
+ return value.to_s
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/camelcase.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/camelcase.rb
new file mode 100644
index 00000000..d7f43f7a
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/camelcase.rb
@@ -0,0 +1,33 @@
+#
+# camelcase.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:camelcase, :type => :rvalue, :doc => <<-EOS
+Converts the case of a string or all strings in an array to camel case.
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "camelcase(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ value = arguments[0]
+ klass = value.class
+
+ unless [Array, String].include?(klass)
+ raise(Puppet::ParseError, 'camelcase(): Requires either ' +
+ 'array or string to work with')
+ end
+
+ if value.is_a?(Array)
+ # Numbers in Puppet are often string-encoded which is troublesome ...
+ result = value.collect { |i| i.is_a?(String) ? i.split('_').map{|e| e.capitalize}.join : i }
+ else
+ result = value.split('_').map{|e| e.capitalize}.join
+ end
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/capitalize.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/capitalize.rb
new file mode 100644
index 00000000..98b2d16c
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/capitalize.rb
@@ -0,0 +1,33 @@
+#
+# capitalize.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:capitalize, :type => :rvalue, :doc => <<-EOS
+ Capitalizes the first letter of a string or array of strings.
+ Requires either a single string or an array as an input.
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "capitalize(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ value = arguments[0]
+
+ unless value.is_a?(Array) || value.is_a?(String)
+ raise(Puppet::ParseError, 'capitalize(): Requires either ' +
+ 'array or string to work with')
+ end
+
+ if value.is_a?(Array)
+ # Numbers in Puppet are often string-encoded which is troublesome ...
+ result = value.collect { |i| i.is_a?(String) ? i.capitalize : i }
+ else
+ result = value.capitalize
+ end
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/chomp.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/chomp.rb
new file mode 100644
index 00000000..c55841e3
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/chomp.rb
@@ -0,0 +1,34 @@
+#
+# chomp.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:chomp, :type => :rvalue, :doc => <<-'EOS'
+ Removes the record separator from the end of a string or an array of
+ strings, for example `hello\n` becomes `hello`.
+ Requires a single string or array as an input.
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "chomp(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ value = arguments[0]
+
+ unless value.is_a?(Array) || value.is_a?(String)
+ raise(Puppet::ParseError, 'chomp(): Requires either ' +
+ 'array or string to work with')
+ end
+
+ if value.is_a?(Array)
+ # Numbers in Puppet are often string-encoded which is troublesome ...
+ result = value.collect { |i| i.is_a?(String) ? i.chomp : i }
+ else
+ result = value.chomp
+ end
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/chop.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/chop.rb
new file mode 100644
index 00000000..b24ab785
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/chop.rb
@@ -0,0 +1,36 @@
+#
+# chop.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:chop, :type => :rvalue, :doc => <<-'EOS'
+ Returns a new string with the last character removed. If the string ends
+ with `\r\n`, both characters are removed. Applying chop to an empty
+ string returns an empty string. If you wish to merely remove record
+ separators then you should use the `chomp` function.
+ Requires a string or array of strings as input.
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "chop(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ value = arguments[0]
+
+ unless value.is_a?(Array) || value.is_a?(String)
+ raise(Puppet::ParseError, 'chop(): Requires either an ' +
+ 'array or string to work with')
+ end
+
+ if value.is_a?(Array)
+ # Numbers in Puppet are often string-encoded which is troublesome ...
+ result = value.collect { |i| i.is_a?(String) ? i.chop : i }
+ else
+ result = value.chop
+ end
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/concat.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/concat.rb
new file mode 100644
index 00000000..618e62d4
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/concat.rb
@@ -0,0 +1,41 @@
+#
+# concat.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:concat, :type => :rvalue, :doc => <<-EOS
+Appends the contents of multiple arrays into array 1.
+
+*Example:*
+
+ concat(['1','2','3'],['4','5','6'],['7','8','9'])
+
+Would result in:
+
+ ['1','2','3','4','5','6','7','8','9']
+ EOS
+ ) do |arguments|
+
+ # Check that more than 2 arguments have been given ...
+ raise(Puppet::ParseError, "concat(): Wrong number of arguments " +
+ "given (#{arguments.size} for < 2)") if arguments.size < 2
+
+ a = arguments[0]
+
+ # Check that the first parameter is an array
+ unless a.is_a?(Array)
+ raise(Puppet::ParseError, 'concat(): Requires array to work with')
+ end
+
+ result = a
+ arguments.shift
+
+ arguments.each do |x|
+ result = result + Array(x)
+ end
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/count.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/count.rb
new file mode 100644
index 00000000..52de1b8a
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/count.rb
@@ -0,0 +1,22 @@
+module Puppet::Parser::Functions
+ newfunction(:count, :type => :rvalue, :arity => -2, :doc => <<-EOS
+Takes an array as first argument and an optional second argument.
+Count the number of elements in array that matches second argument.
+If called with only an array it counts the number of elements that are not nil/undef.
+ EOS
+ ) do |args|
+
+ if (args.size > 2) then
+ raise(ArgumentError, "count(): Wrong number of arguments "+
+ "given #{args.size} for 1 or 2.")
+ end
+
+ collection, item = args
+
+ if item then
+ collection.count item
+ else
+ collection.count { |obj| obj != nil && obj != :undef && obj != '' }
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/deep_merge.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/deep_merge.rb
new file mode 100644
index 00000000..6df32e9c
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/deep_merge.rb
@@ -0,0 +1,44 @@
+module Puppet::Parser::Functions
+ newfunction(:deep_merge, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args|
+ Recursively merges two or more hashes together and returns the resulting hash.
+
+ For example:
+
+ $hash1 = {'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } }
+ $hash2 = {'two' => 'dos', 'three' => { 'five' => 5 } }
+ $merged_hash = deep_merge($hash1, $hash2)
+ # The resulting hash is equivalent to:
+ # $merged_hash = { 'one' => 1, 'two' => 'dos', 'three' => { 'four' => 4, 'five' => 5 } }
+
+ When there is a duplicate key that is a hash, they are recursively merged.
+ When there is a duplicate key that is not a hash, the key in the rightmost hash will "win."
+
+ ENDHEREDOC
+
+ if args.length < 2
+ raise Puppet::ParseError, ("deep_merge(): wrong number of arguments (#{args.length}; must be at least 2)")
+ end
+
+ deep_merge = Proc.new do |hash1,hash2|
+ hash1.merge(hash2) do |key,old_value,new_value|
+ if old_value.is_a?(Hash) && new_value.is_a?(Hash)
+ deep_merge.call(old_value, new_value)
+ else
+ new_value
+ end
+ end
+ end
+
+ result = Hash.new
+ args.each do |arg|
+ next if arg.is_a? String and arg.empty? # empty string is synonym for puppet's undef
+ # If the argument was not a hash, skip it.
+ unless arg.is_a?(Hash)
+ raise Puppet::ParseError, "deep_merge: unexpected argument type #{arg.class}, only expects hash arguments"
+ end
+
+ result = deep_merge.call(result, arg)
+ end
+ return( result )
+ end
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/defined_with_params.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/defined_with_params.rb
new file mode 100644
index 00000000..d7df306c
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/defined_with_params.rb
@@ -0,0 +1,35 @@
+# Test whether a given class or definition is defined
+require 'puppet/parser/functions'
+
+Puppet::Parser::Functions.newfunction(:defined_with_params,
+ :type => :rvalue,
+ :doc => <<-'ENDOFDOC'
+Takes a resource reference and an optional hash of attributes.
+
+Returns true if a resource with the specified attributes has already been added
+to the catalog, and false otherwise.
+
+ user { 'dan':
+ ensure => present,
+ }
+
+ if ! defined_with_params(User[dan], {'ensure' => 'present' }) {
+ user { 'dan': ensure => present, }
+ }
+ENDOFDOC
+) do |vals|
+ reference, params = vals
+ raise(ArgumentError, 'Must specify a reference') unless reference
+ if (! params) || params == ''
+ params = {}
+ end
+ ret = false
+ if resource = findresource(reference.to_s)
+ matches = params.collect do |key, value|
+ resource[key] == value
+ end
+ ret = params.empty? || !matches.include?(false)
+ end
+ Puppet.debug("Resource #{reference} was not determined to be defined")
+ ret
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/delete.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/delete.rb
new file mode 100644
index 00000000..f548b444
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/delete.rb
@@ -0,0 +1,49 @@
+#
+# delete.rb
+#
+
+# TODO(Krzysztof Wilczynski): We need to add support for regular expression ...
+
+module Puppet::Parser::Functions
+ newfunction(:delete, :type => :rvalue, :doc => <<-EOS
+Deletes all instances of a given element from an array, substring from a
+string, or key from a hash.
+
+*Examples:*
+
+ delete(['a','b','c','b'], 'b')
+ Would return: ['a','c']
+
+ delete({'a'=>1,'b'=>2,'c'=>3}, 'b')
+ Would return: {'a'=>1,'c'=>3}
+
+ delete({'a'=>1,'b'=>2,'c'=>3}, ['b','c'])
+ Would return: {'a'=>1}
+
+ delete('abracadabra', 'bra')
+ Would return: 'acada'
+ EOS
+ ) do |arguments|
+
+ if (arguments.size != 2) then
+ raise(Puppet::ParseError, "delete(): Wrong number of arguments "+
+ "given #{arguments.size} for 2.")
+ end
+
+ collection = arguments[0].dup
+ Array(arguments[1]).each do |item|
+ case collection
+ when Array, Hash
+ collection.delete item
+ when String
+ collection.gsub! item, ''
+ else
+ raise(TypeError, "delete(): First argument must be an Array, " +
+ "String, or Hash. Given an argument of class #{collection.class}.")
+ end
+ end
+ collection
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/delete_at.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/delete_at.rb
new file mode 100644
index 00000000..3eb4b537
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/delete_at.rb
@@ -0,0 +1,49 @@
+#
+# delete_at.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:delete_at, :type => :rvalue, :doc => <<-EOS
+Deletes a determined indexed value from an array.
+
+*Examples:*
+
+ delete_at(['a','b','c'], 1)
+
+Would return: ['a','c']
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "delete_at(): Wrong number of arguments " +
+ "given (#{arguments.size} for 2)") if arguments.size < 2
+
+ array = arguments[0]
+
+ unless array.is_a?(Array)
+ raise(Puppet::ParseError, 'delete_at(): Requires array to work with')
+ end
+
+ index = arguments[1]
+
+ if index.is_a?(String) and not index.match(/^\d+$/)
+ raise(Puppet::ParseError, 'delete_at(): You must provide ' +
+ 'non-negative numeric index')
+ end
+
+ result = array.clone
+
+ # Numbers in Puppet are often string-encoded which is troublesome ...
+ index = index.to_i
+
+ if index > result.size - 1 # First element is at index 0 is it not?
+ raise(Puppet::ParseError, 'delete_at(): Given index ' +
+ 'exceeds size of array given')
+ end
+
+ result.delete_at(index) # We ignore the element that got deleted ...
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/delete_undef_values.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/delete_undef_values.rb
new file mode 100644
index 00000000..f94d4da8
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/delete_undef_values.rb
@@ -0,0 +1,34 @@
+module Puppet::Parser::Functions
+ newfunction(:delete_undef_values, :type => :rvalue, :doc => <<-EOS
+Returns a copy of input hash or array with all undefs deleted.
+
+*Examples:*
+
+ $hash = delete_undef_values({a=>'A', b=>'', c=>undef, d => false})
+
+Would return: {a => 'A', b => '', d => false}
+
+ $array = delete_undef_values(['A','',undef,false])
+
+Would return: ['A','',false]
+
+ EOS
+ ) do |args|
+
+ raise(Puppet::ParseError,
+ "delete_undef_values(): Wrong number of arguments given " +
+ "(#{args.size})") if args.size < 1
+
+ unless args[0].is_a? Array or args[0].is_a? Hash
+ raise(Puppet::ParseError,
+ "delete_undef_values(): expected an array or hash, got #{args[0]} type #{args[0].class} ")
+ end
+ result = args[0].dup
+ if result.is_a?(Hash)
+ result.delete_if {|key, val| val.equal? :undef}
+ elsif result.is_a?(Array)
+ result.delete :undef
+ end
+ result
+ end
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/delete_values.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/delete_values.rb
new file mode 100644
index 00000000..f6c8c0e6
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/delete_values.rb
@@ -0,0 +1,26 @@
+module Puppet::Parser::Functions
+ newfunction(:delete_values, :type => :rvalue, :doc => <<-EOS
+Deletes all instances of a given value from a hash.
+
+*Examples:*
+
+ delete_values({'a'=>'A','b'=>'B','c'=>'C','B'=>'D'}, 'B')
+
+Would return: {'a'=>'A','c'=>'C','B'=>'D'}
+
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError,
+ "delete_values(): Wrong number of arguments given " +
+ "(#{arguments.size} of 2)") if arguments.size != 2
+
+ hash, item = arguments
+
+ if not hash.is_a?(Hash)
+ raise(TypeError, "delete_values(): First argument must be a Hash. " + \
+ "Given an argument of class #{hash.class}.")
+ end
+ hash.dup.delete_if { |key, val| item == val }
+ end
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/difference.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/difference.rb
new file mode 100644
index 00000000..cd258f75
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/difference.rb
@@ -0,0 +1,36 @@
+#
+# difference.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:difference, :type => :rvalue, :doc => <<-EOS
+This function returns the difference between two arrays.
+The returned array is a copy of the original array, removing any items that
+also appear in the second array.
+
+*Examples:*
+
+ difference(["a","b","c"],["b","c","d"])
+
+Would return: ["a"]
+ EOS
+ ) do |arguments|
+
+ # Two arguments are required
+ raise(Puppet::ParseError, "difference(): Wrong number of arguments " +
+ "given (#{arguments.size} for 2)") if arguments.size != 2
+
+ first = arguments[0]
+ second = arguments[1]
+
+ unless first.is_a?(Array) && second.is_a?(Array)
+ raise(Puppet::ParseError, 'difference(): Requires 2 arrays')
+ end
+
+ result = first - second
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/dirname.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/dirname.rb
new file mode 100644
index 00000000..ea8cc1e0
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/dirname.rb
@@ -0,0 +1,15 @@
+module Puppet::Parser::Functions
+ newfunction(:dirname, :type => :rvalue, :doc => <<-EOS
+ Returns the dirname of a path.
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "dirname(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ path = arguments[0]
+ return File.dirname(path)
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/downcase.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/downcase.rb
new file mode 100644
index 00000000..040b84f5
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/downcase.rb
@@ -0,0 +1,32 @@
+#
+# downcase.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:downcase, :type => :rvalue, :doc => <<-EOS
+Converts the case of a string or all strings in an array to lower case.
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "downcase(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ value = arguments[0]
+
+ unless value.is_a?(Array) || value.is_a?(String)
+ raise(Puppet::ParseError, 'downcase(): Requires either ' +
+ 'array or string to work with')
+ end
+
+ if value.is_a?(Array)
+ # Numbers in Puppet are often string-encoded which is troublesome ...
+ result = value.collect { |i| i.is_a?(String) ? i.downcase : i }
+ else
+ result = value.downcase
+ end
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/empty.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/empty.rb
new file mode 100644
index 00000000..cca620fa
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/empty.rb
@@ -0,0 +1,27 @@
+#
+# empty.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:empty, :type => :rvalue, :doc => <<-EOS
+Returns true if the variable is empty.
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "empty(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ value = arguments[0]
+
+ unless value.is_a?(Array) || value.is_a?(Hash) || value.is_a?(String)
+ raise(Puppet::ParseError, 'empty(): Requires either ' +
+ 'array, hash or string to work with')
+ end
+
+ result = value.empty?
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/ensure_packages.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/ensure_packages.rb
new file mode 100644
index 00000000..f1da4aaa
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/ensure_packages.rb
@@ -0,0 +1,35 @@
+#
+# ensure_packages.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:ensure_packages, :type => :statement, :doc => <<-EOS
+Takes a list of packages and only installs them if they don't already exist.
+It optionally takes a hash as a second parameter that will be passed as the
+third argument to the ensure_resource() function.
+ EOS
+ ) do |arguments|
+
+ if arguments.size > 2 or arguments.size == 0
+ raise(Puppet::ParseError, "ensure_packages(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1 or 2)")
+ elsif arguments.size == 2 and !arguments[1].is_a?(Hash)
+ raise(Puppet::ParseError, 'ensure_packages(): Requires second argument to be a Hash')
+ end
+
+ packages = Array(arguments[0])
+
+ if arguments[1]
+ defaults = { 'ensure' => 'present' }.merge(arguments[1])
+ else
+ defaults = { 'ensure' => 'present' }
+ end
+
+ Puppet::Parser::Functions.function(:ensure_resource)
+ packages.each { |package_name|
+ function_ensure_resource(['package', package_name, defaults ])
+ }
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/ensure_resource.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/ensure_resource.rb
new file mode 100644
index 00000000..1ba6a447
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/ensure_resource.rb
@@ -0,0 +1,46 @@
+# Test whether a given class or definition is defined
+require 'puppet/parser/functions'
+
+Puppet::Parser::Functions.newfunction(:ensure_resource,
+ :type => :statement,
+ :doc => <<-'ENDOFDOC'
+Takes a resource type, title, and a list of attributes that describe a
+resource.
+
+ user { 'dan':
+ ensure => present,
+ }
+
+This example only creates the resource if it does not already exist:
+
+ ensure_resource('user', 'dan', {'ensure' => 'present' })
+
+If the resource already exists but does not match the specified parameters,
+this function will attempt to recreate the resource leading to a duplicate
+resource definition error.
+
+An array of resources can also be passed in and each will be created with
+the type and parameters specified if it doesn't already exist.
+
+ ensure_resource('user', ['dan','alex'], {'ensure' => 'present'})
+
+ENDOFDOC
+) do |vals|
+ type, title, params = vals
+ raise(ArgumentError, 'Must specify a type') unless type
+ raise(ArgumentError, 'Must specify a title') unless title
+ params ||= {}
+
+ items = [title].flatten
+
+ items.each do |item|
+ Puppet::Parser::Functions.function(:defined_with_params)
+ if function_defined_with_params(["#{type}[#{item}]", params])
+ Puppet.debug("Resource #{type}[#{item}] with params #{params} not created because it already exists")
+ else
+ Puppet.debug("Create new resource #{type}[#{item}] with params #{params}")
+ Puppet::Parser::Functions.function(:create_resources)
+ function_create_resources([type.capitalize, { item => params }])
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/flatten.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/flatten.rb
new file mode 100644
index 00000000..a1ed1832
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/flatten.rb
@@ -0,0 +1,33 @@
+#
+# flatten.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:flatten, :type => :rvalue, :doc => <<-EOS
+This function flattens any deeply nested arrays and returns a single flat array
+as a result.
+
+*Examples:*
+
+ flatten(['a', ['b', ['c']]])
+
+Would return: ['a','b','c']
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "flatten(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size != 1
+
+ array = arguments[0]
+
+ unless array.is_a?(Array)
+ raise(Puppet::ParseError, 'flatten(): Requires array to work with')
+ end
+
+ result = array.flatten
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/floor.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/floor.rb
new file mode 100644
index 00000000..9a6f014d
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/floor.rb
@@ -0,0 +1,25 @@
+module Puppet::Parser::Functions
+ newfunction(:floor, :type => :rvalue, :doc => <<-EOS
+ Returns the largest integer less or equal to the argument.
+ Takes a single numeric value as an argument.
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "floor(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size != 1
+
+ begin
+ arg = Float(arguments[0])
+ rescue TypeError, ArgumentError => e
+ raise(Puppet::ParseError, "floor(): Wrong argument type " +
+ "given (#{arguments[0]} for Numeric)")
+ end
+
+ raise(Puppet::ParseError, "floor(): Wrong argument type " +
+ "given (#{arg.class} for Numeric)") if arg.is_a?(Numeric) == false
+
+ arg.floor
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/fqdn_rotate.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/fqdn_rotate.rb
new file mode 100644
index 00000000..7f4d37d0
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/fqdn_rotate.rb
@@ -0,0 +1,45 @@
+#
+# fqdn_rotate.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:fqdn_rotate, :type => :rvalue, :doc => <<-EOS
+Rotates an array a random number of times based on a nodes fqdn.
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "fqdn_rotate(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ value = arguments[0]
+ require 'digest/md5'
+
+ unless value.is_a?(Array) || value.is_a?(String)
+ raise(Puppet::ParseError, 'fqdn_rotate(): Requires either ' +
+ 'array or string to work with')
+ end
+
+ result = value.clone
+
+ string = value.is_a?(String) ? true : false
+
+ # Check whether it makes sense to rotate ...
+ return result if result.size <= 1
+
+ # We turn any string value into an array to be able to rotate ...
+ result = string ? result.split('') : result
+
+ elements = result.size
+
+ srand(Digest::MD5.hexdigest([lookupvar('::fqdn'),arguments].join(':')).hex)
+ rand(elements).times {
+ result.push result.shift
+ }
+
+ result = string ? result.join : result
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/get_module_path.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/get_module_path.rb
new file mode 100644
index 00000000..1421b91f
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/get_module_path.rb
@@ -0,0 +1,17 @@
+module Puppet::Parser::Functions
+ newfunction(:get_module_path, :type =>:rvalue, :doc => <<-EOT
+ Returns the absolute path of the specified module for the current
+ environment.
+
+ Example:
+ $module_path = get_module_path('stdlib')
+ EOT
+ ) do |args|
+ raise(Puppet::ParseError, "get_module_path(): Wrong number of arguments, expects one") unless args.size == 1
+ if module_path = Puppet::Module.find(args[0], compiler.environment.to_s)
+ module_path.path
+ else
+ raise(Puppet::ParseError, "Could not find module #{args[0]} in environment #{compiler.environment}")
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/getparam.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/getparam.rb
new file mode 100644
index 00000000..6d510069
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/getparam.rb
@@ -0,0 +1,35 @@
+# Test whether a given class or definition is defined
+require 'puppet/parser/functions'
+
+Puppet::Parser::Functions.newfunction(:getparam,
+ :type => :rvalue,
+ :doc => <<-'ENDOFDOC'
+Takes a resource reference and name of the parameter and
+returns value of resource's parameter.
+
+*Examples:*
+
+ define example_resource($param) {
+ }
+
+ example_resource { "example_resource_instance":
+ param => "param_value"
+ }
+
+ getparam(Example_resource["example_resource_instance"], "param")
+
+Would return: param_value
+ENDOFDOC
+) do |vals|
+ reference, param = vals
+ raise(ArgumentError, 'Must specify a reference') unless reference
+ raise(ArgumentError, 'Must specify name of a parameter') unless param and param.instance_of? String
+
+ return '' if param.empty?
+
+ if resource = findresource(reference.to_s)
+ return resource[param] if resource[param]
+ end
+
+ return ''
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/getvar.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/getvar.rb
new file mode 100644
index 00000000..fb336b6a
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/getvar.rb
@@ -0,0 +1,29 @@
+module Puppet::Parser::Functions
+
+ newfunction(:getvar, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args|
+ Lookup a variable in a remote namespace.
+
+ For example:
+
+ $foo = getvar('site::data::foo')
+ # Equivalent to $foo = $site::data::foo
+
+ This is useful if the namespace itself is stored in a string:
+
+ $datalocation = 'site::data'
+ $bar = getvar("${datalocation}::bar")
+ # Equivalent to $bar = $site::data::bar
+ ENDHEREDOC
+
+ unless args.length == 1
+ raise Puppet::ParseError, ("getvar(): wrong number of arguments (#{args.length}; must be 1)")
+ end
+
+ begin
+ self.lookupvar("#{args[0]}")
+ rescue Puppet::ParseError # Eat the exception if strict_variables = true is set
+ end
+
+ end
+
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/grep.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/grep.rb
new file mode 100644
index 00000000..ceba9ecc
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/grep.rb
@@ -0,0 +1,33 @@
+#
+# grep.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:grep, :type => :rvalue, :doc => <<-EOS
+This function searches through an array and returns any elements that match
+the provided regular expression.
+
+*Examples:*
+
+ grep(['aaa','bbb','ccc','aaaddd'], 'aaa')
+
+Would return:
+
+ ['aaa','aaaddd']
+ EOS
+ ) do |arguments|
+
+ if (arguments.size != 2) then
+ raise(Puppet::ParseError, "grep(): Wrong number of arguments "+
+ "given #{arguments.size} for 2")
+ end
+
+ a = arguments[0]
+ pattern = Regexp.new(arguments[1])
+
+ a.grep(pattern)
+
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/has_interface_with.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/has_interface_with.rb
new file mode 100644
index 00000000..36915246
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/has_interface_with.rb
@@ -0,0 +1,68 @@
+#
+# has_interface_with
+#
+
+module Puppet::Parser::Functions
+ newfunction(:has_interface_with, :type => :rvalue, :doc => <<-EOS
+Returns boolean based on kind and value:
+ * macaddress
+ * netmask
+ * ipaddress
+ * network
+
+has_interface_with("macaddress", "x:x:x:x:x:x")
+has_interface_with("ipaddress", "127.0.0.1") => true
+etc.
+
+If no "kind" is given, then the presence of the interface is checked:
+has_interface_with("lo") => true
+ EOS
+ ) do |args|
+
+ raise(Puppet::ParseError, "has_interface_with(): Wrong number of arguments " +
+ "given (#{args.size} for 1 or 2)") if args.size < 1 or args.size > 2
+
+ interfaces = lookupvar('interfaces')
+
+ # If we do not have any interfaces, then there are no requested attributes
+ return false if (interfaces == :undefined || interfaces.nil?)
+
+ interfaces = interfaces.split(',')
+
+ if args.size == 1
+ return interfaces.member?(args[0])
+ end
+
+ kind, value = args
+
+ # Bug with 3.7.1 - 3.7.3 when using future parser throws :undefined_variable
+ # https://tickets.puppetlabs.com/browse/PUP-3597
+ factval = nil
+ catch :undefined_variable do
+ factval = lookupvar(kind)
+ end
+ if factval == value
+ return true
+ end
+
+ result = false
+ interfaces.each do |iface|
+ iface.downcase!
+ factval = nil
+ begin
+ # Bug with 3.7.1 - 3.7.3 when using future parser throws :undefined_variable
+ # https://tickets.puppetlabs.com/browse/PUP-3597
+ catch :undefined_variable do
+ factval = lookupvar("#{kind}_#{iface}")
+ end
+ rescue Puppet::ParseError # Eat the exception if strict_variables = true is set
+ end
+ if value == factval
+ result = true
+ break
+ end
+ end
+
+ result
+ end
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/has_ip_address.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/has_ip_address.rb
new file mode 100644
index 00000000..842c8ec6
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/has_ip_address.rb
@@ -0,0 +1,25 @@
+#
+# has_ip_address
+#
+
+module Puppet::Parser::Functions
+ newfunction(:has_ip_address, :type => :rvalue, :doc => <<-EOS
+Returns true if the client has the requested IP address on some interface.
+
+This function iterates through the 'interfaces' fact and checks the
+'ipaddress_IFACE' facts, performing a simple string comparison.
+ EOS
+ ) do |args|
+
+ raise(Puppet::ParseError, "has_ip_address(): Wrong number of arguments " +
+ "given (#{args.size} for 1)") if args.size != 1
+
+ Puppet::Parser::Functions.autoloader.load(:has_interface_with) \
+ unless Puppet::Parser::Functions.autoloader.loaded?(:has_interface_with)
+
+ function_has_interface_with(['ipaddress', args[0]])
+
+ end
+end
+
+# vim:sts=2 sw=2
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/has_ip_network.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/has_ip_network.rb
new file mode 100644
index 00000000..9ccf9024
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/has_ip_network.rb
@@ -0,0 +1,25 @@
+#
+# has_ip_network
+#
+
+module Puppet::Parser::Functions
+ newfunction(:has_ip_network, :type => :rvalue, :doc => <<-EOS
+Returns true if the client has an IP address within the requested network.
+
+This function iterates through the 'interfaces' fact and checks the
+'network_IFACE' facts, performing a simple string comparision.
+ EOS
+ ) do |args|
+
+ raise(Puppet::ParseError, "has_ip_network(): Wrong number of arguments " +
+ "given (#{args.size} for 1)") if args.size != 1
+
+ Puppet::Parser::Functions.autoloader.load(:has_interface_with) \
+ unless Puppet::Parser::Functions.autoloader.loaded?(:has_interface_with)
+
+ function_has_interface_with(['network', args[0]])
+
+ end
+end
+
+# vim:sts=2 sw=2
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/has_key.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/has_key.rb
new file mode 100644
index 00000000..4657cc29
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/has_key.rb
@@ -0,0 +1,28 @@
+module Puppet::Parser::Functions
+
+ newfunction(:has_key, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args|
+ Determine if a hash has a certain key value.
+
+ Example:
+
+ $my_hash = {'key_one' => 'value_one'}
+ if has_key($my_hash, 'key_two') {
+ notice('we will not reach here')
+ }
+ if has_key($my_hash, 'key_one') {
+ notice('this will be printed')
+ }
+
+ ENDHEREDOC
+
+ unless args.length == 2
+ raise Puppet::ParseError, ("has_key(): wrong number of arguments (#{args.length}; must be 2)")
+ end
+ unless args[0].is_a?(Hash)
+ raise Puppet::ParseError, "has_key(): expects the first argument to be a hash, got #{args[0].inspect} which is of type #{args[0].class}"
+ end
+ args[0].has_key?(args[1])
+
+ end
+
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/hash.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/hash.rb
new file mode 100644
index 00000000..8cc4823b
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/hash.rb
@@ -0,0 +1,41 @@
+#
+# hash.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:hash, :type => :rvalue, :doc => <<-EOS
+This function converts an array into a hash.
+
+*Examples:*
+
+ hash(['a',1,'b',2,'c',3])
+
+Would return: {'a'=>1,'b'=>2,'c'=>3}
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "hash(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ array = arguments[0]
+
+ unless array.is_a?(Array)
+ raise(Puppet::ParseError, 'hash(): Requires array to work with')
+ end
+
+ result = {}
+
+ begin
+ # This is to make it compatible with older version of Ruby ...
+ array = array.flatten
+ result = Hash[*array]
+ rescue Exception
+ raise(Puppet::ParseError, 'hash(): Unable to compute ' +
+ 'hash from array given')
+ end
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/intersection.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/intersection.rb
new file mode 100644
index 00000000..48f02e9d
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/intersection.rb
@@ -0,0 +1,34 @@
+#
+# intersection.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:intersection, :type => :rvalue, :doc => <<-EOS
+This function returns an array an intersection of two.
+
+*Examples:*
+
+ intersection(["a","b","c"],["b","c","d"])
+
+Would return: ["b","c"]
+ EOS
+ ) do |arguments|
+
+ # Two arguments are required
+ raise(Puppet::ParseError, "intersection(): Wrong number of arguments " +
+ "given (#{arguments.size} for 2)") if arguments.size != 2
+
+ first = arguments[0]
+ second = arguments[1]
+
+ unless first.is_a?(Array) && second.is_a?(Array)
+ raise(Puppet::ParseError, 'intersection(): Requires 2 arrays')
+ end
+
+ result = first & second
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/is_array.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/is_array.rb
new file mode 100644
index 00000000..b39e184a
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/is_array.rb
@@ -0,0 +1,22 @@
+#
+# is_array.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:is_array, :type => :rvalue, :doc => <<-EOS
+Returns true if the variable passed to this function is an array.
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "is_array(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ type = arguments[0]
+
+ result = type.is_a?(Array)
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/is_bool.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/is_bool.rb
new file mode 100644
index 00000000..8bbdbc8a
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/is_bool.rb
@@ -0,0 +1,22 @@
+#
+# is_bool.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:is_bool, :type => :rvalue, :doc => <<-EOS
+Returns true if the variable passed to this function is a boolean.
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "is_bool(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size != 1
+
+ type = arguments[0]
+
+ result = type.is_a?(TrueClass) || type.is_a?(FalseClass)
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/is_domain_name.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/is_domain_name.rb
new file mode 100644
index 00000000..b3fee965
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/is_domain_name.rb
@@ -0,0 +1,50 @@
+#
+# is_domain_name.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:is_domain_name, :type => :rvalue, :doc => <<-EOS
+Returns true if the string passed to this function is a syntactically correct domain name.
+ EOS
+ ) do |arguments|
+
+ if (arguments.size != 1) then
+ raise(Puppet::ParseError, "is_domain_name(): Wrong number of arguments "+
+ "given #{arguments.size} for 1")
+ end
+
+ domain = arguments[0]
+
+ # Limits (rfc1035, 3.1)
+ domain_max_length=255
+ label_min_length=1
+ label_max_length=63
+
+ # Only allow string types
+ return false unless domain.is_a?(String)
+
+ # Allow ".", it is the top level domain
+ return true if domain == '.'
+
+ # Remove the final dot, if present.
+ domain.chomp!('.')
+
+ # Check the whole domain
+ return false if domain.empty?
+ return false if domain.length > domain_max_length
+
+ # Check each label in the domain
+ labels = domain.split('.')
+ vlabels = labels.each do |label|
+ break if label.length < label_min_length
+ break if label.length > label_max_length
+ break if label[-1..-1] == '-'
+ break if label[0..0] == '-'
+ break unless /^[a-z\d-]+$/i.match(label)
+ end
+ return vlabels == labels
+
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/is_float.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/is_float.rb
new file mode 100644
index 00000000..a2da9438
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/is_float.rb
@@ -0,0 +1,30 @@
+#
+# is_float.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:is_float, :type => :rvalue, :doc => <<-EOS
+Returns true if the variable passed to this function is a float.
+ EOS
+ ) do |arguments|
+
+ if (arguments.size != 1) then
+ raise(Puppet::ParseError, "is_float(): Wrong number of arguments "+
+ "given #{arguments.size} for 1")
+ end
+
+ value = arguments[0]
+
+ # Only allow Numeric or String types
+ return false unless value.is_a?(Numeric) or value.is_a?(String)
+
+ if value != value.to_f.to_s and !value.is_a? Float then
+ return false
+ else
+ return true
+ end
+
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/is_function_available.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/is_function_available.rb
new file mode 100644
index 00000000..6da82c8c
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/is_function_available.rb
@@ -0,0 +1,26 @@
+#
+# is_function_available.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:is_function_available, :type => :rvalue, :doc => <<-EOS
+This function accepts a string as an argument, determines whether the
+Puppet runtime has access to a function by that name. It returns a
+true if the function exists, false if not.
+ EOS
+ ) do |arguments|
+
+ if (arguments.size != 1) then
+ raise(Puppet::ParseError, "is_function_available?(): Wrong number of arguments "+
+ "given #{arguments.size} for 1")
+ end
+
+ # Only allow String types
+ return false unless arguments[0].is_a?(String)
+
+ function = Puppet::Parser::Functions.function(arguments[0].to_sym)
+ function.is_a?(String) and not function.empty?
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/is_hash.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/is_hash.rb
new file mode 100644
index 00000000..ad907f08
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/is_hash.rb
@@ -0,0 +1,22 @@
+#
+# is_hash.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:is_hash, :type => :rvalue, :doc => <<-EOS
+Returns true if the variable passed to this function is a hash.
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "is_hash(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size != 1
+
+ type = arguments[0]
+
+ result = type.is_a?(Hash)
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/is_integer.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/is_integer.rb
new file mode 100644
index 00000000..c03d28df
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/is_integer.rb
@@ -0,0 +1,45 @@
+#
+# is_integer.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:is_integer, :type => :rvalue, :doc => <<-EOS
+Returns true if the variable passed to this function is an Integer or
+a decimal (base 10) integer in String form. The string may
+start with a '-' (minus). A value of '0' is allowed, but a leading '0' digit may not
+be followed by other digits as this indicates that the value is octal (base 8).
+
+If given any other argument `false` is returned.
+ EOS
+ ) do |arguments|
+
+ if (arguments.size != 1) then
+ raise(Puppet::ParseError, "is_integer(): Wrong number of arguments "+
+ "given #{arguments.size} for 1")
+ end
+
+ value = arguments[0]
+
+ # Regex is taken from the lexer of puppet
+ # puppet/pops/parser/lexer.rb but modified to match also
+ # negative values and disallow numbers prefixed with multiple
+ # 0's
+ #
+ # TODO these parameter should be a constant but I'm not sure
+ # if there is no risk to declare it inside of the module
+ # Puppet::Parser::Functions
+
+ # Integer numbers like
+ # -1234568981273
+ # 47291
+ numeric = %r{^-?(?:(?:[1-9]\d*)|0)$}
+
+ if value.is_a? Integer or (value.is_a? String and value.match numeric)
+ return true
+ else
+ return false
+ end
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/is_ip_address.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/is_ip_address.rb
new file mode 100644
index 00000000..a90adabe
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/is_ip_address.rb
@@ -0,0 +1,32 @@
+#
+# is_ip_address.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:is_ip_address, :type => :rvalue, :doc => <<-EOS
+Returns true if the string passed to this function is a valid IP address.
+ EOS
+ ) do |arguments|
+
+ require 'ipaddr'
+
+ if (arguments.size != 1) then
+ raise(Puppet::ParseError, "is_ip_address(): Wrong number of arguments "+
+ "given #{arguments.size} for 1")
+ end
+
+ begin
+ ip = IPAddr.new(arguments[0])
+ rescue ArgumentError
+ return false
+ end
+
+ if ip.ipv4? or ip.ipv6? then
+ return true
+ else
+ return false
+ end
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/is_mac_address.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/is_mac_address.rb
new file mode 100644
index 00000000..1b3088a2
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/is_mac_address.rb
@@ -0,0 +1,27 @@
+#
+# is_mac_address.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:is_mac_address, :type => :rvalue, :doc => <<-EOS
+Returns true if the string passed to this function is a valid mac address.
+ EOS
+ ) do |arguments|
+
+ if (arguments.size != 1) then
+ raise(Puppet::ParseError, "is_mac_address(): Wrong number of arguments "+
+ "given #{arguments.size} for 1")
+ end
+
+ mac = arguments[0]
+
+ if /^[a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2}$/.match(mac) then
+ return true
+ else
+ return false
+ end
+
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/is_numeric.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/is_numeric.rb
new file mode 100644
index 00000000..e7e1d2a7
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/is_numeric.rb
@@ -0,0 +1,75 @@
+#
+# is_numeric.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:is_numeric, :type => :rvalue, :doc => <<-EOS
+Returns true if the given argument is a Numeric (Integer or Float),
+or a String containing either a valid integer in decimal base 10 form, or
+a valid floating point string representation.
+
+The function recognizes only decimal (base 10) integers and float but not
+integers in hex (base 16) or octal (base 8) form.
+
+The string representation may start with a '-' (minus). If a decimal '.' is used,
+it must be followed by at least one digit.
+
+Valid examples:
+
+ 77435
+ 10e-12
+ -8475
+ 0.2343
+ -23.561e3
+ EOS
+ ) do |arguments|
+
+ if (arguments.size != 1) then
+ raise(Puppet::ParseError, "is_numeric(): Wrong number of arguments "+
+ "given #{arguments.size} for 1")
+ end
+
+ value = arguments[0]
+
+ # Regex is taken from the lexer of puppet
+ # puppet/pops/parser/lexer.rb but modified to match also
+ # negative values and disallow invalid octal numbers or
+ # numbers prefixed with multiple 0's (except in hex numbers)
+ #
+ # TODO these parameters should be constants but I'm not sure
+ # if there is no risk to declare them inside of the module
+ # Puppet::Parser::Functions
+
+ # TODO decide if this should be used
+ # HEX numbers like
+ # 0xaa230F
+ # 0X1234009C
+ # 0x0012
+ # -12FcD
+ #numeric_hex = %r{^-?0[xX][0-9A-Fa-f]+$}
+
+ # TODO decide if this should be used
+ # OCTAL numbers like
+ # 01234567
+ # -045372
+ #numeric_oct = %r{^-?0[1-7][0-7]*$}
+
+ # Integer/Float numbers like
+ # -0.1234568981273
+ # 47291
+ # 42.12345e-12
+ numeric = %r{^-?(?:(?:[1-9]\d*)|0)(?:\.\d+)?(?:[eE]-?\d+)?$}
+
+ if value.is_a? Numeric or (value.is_a? String and (
+ value.match(numeric) #or
+ # value.match(numeric_hex) or
+ # value.match(numeric_oct)
+ ))
+ return true
+ else
+ return false
+ end
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/is_string.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/is_string.rb
new file mode 100644
index 00000000..f5bef045
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/is_string.rb
@@ -0,0 +1,26 @@
+#
+# is_string.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:is_string, :type => :rvalue, :doc => <<-EOS
+Returns true if the variable passed to this function is a string.
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "is_string(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ type = arguments[0]
+
+ result = type.is_a?(String)
+
+ if result and (type == type.to_f.to_s or type == type.to_i.to_s) then
+ return false
+ end
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/join.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/join.rb
new file mode 100644
index 00000000..6c0a6ba0
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/join.rb
@@ -0,0 +1,41 @@
+#
+# join.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:join, :type => :rvalue, :doc => <<-EOS
+This function joins an array into a string using a separator.
+
+*Examples:*
+
+ join(['a','b','c'], ",")
+
+Would result in: "a,b,c"
+ EOS
+ ) do |arguments|
+
+ # Technically we support two arguments but only first is mandatory ...
+ raise(Puppet::ParseError, "join(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ array = arguments[0]
+
+ unless array.is_a?(Array)
+ raise(Puppet::ParseError, 'join(): Requires array to work with')
+ end
+
+ suffix = arguments[1] if arguments[1]
+
+ if suffix
+ unless suffix.is_a?(String)
+ raise(Puppet::ParseError, 'join(): Requires string to work with')
+ end
+ end
+
+ result = suffix ? array.join(suffix) : array.join
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/join_keys_to_values.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/join_keys_to_values.rb
new file mode 100644
index 00000000..e9924fe2
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/join_keys_to_values.rb
@@ -0,0 +1,47 @@
+#
+# join.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:join_keys_to_values, :type => :rvalue, :doc => <<-EOS
+This function joins each key of a hash to that key's corresponding value with a
+separator. Keys and values are cast to strings. The return value is an array in
+which each element is one joined key/value pair.
+
+*Examples:*
+
+ join_keys_to_values({'a'=>1,'b'=>2}, " is ")
+
+Would result in: ["a is 1","b is 2"]
+ EOS
+ ) do |arguments|
+
+ # Validate the number of arguments.
+ if arguments.size != 2
+ raise(Puppet::ParseError, "join_keys_to_values(): Takes exactly two " +
+ "arguments, but #{arguments.size} given.")
+ end
+
+ # Validate the first argument.
+ hash = arguments[0]
+ if not hash.is_a?(Hash)
+ raise(TypeError, "join_keys_to_values(): The first argument must be a " +
+ "hash, but a #{hash.class} was given.")
+ end
+
+ # Validate the second argument.
+ separator = arguments[1]
+ if not separator.is_a?(String)
+ raise(TypeError, "join_keys_to_values(): The second argument must be a " +
+ "string, but a #{separator.class} was given.")
+ end
+
+ # Join the keys to their values.
+ hash.map do |k,v|
+ String(k) + separator + String(v)
+ end
+
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/keys.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/keys.rb
new file mode 100644
index 00000000..f0d13b64
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/keys.rb
@@ -0,0 +1,26 @@
+#
+# keys.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:keys, :type => :rvalue, :doc => <<-EOS
+Returns the keys of a hash as an array.
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "keys(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ hash = arguments[0]
+
+ unless hash.is_a?(Hash)
+ raise(Puppet::ParseError, 'keys(): Requires hash to work with')
+ end
+
+ result = hash.keys
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/loadyaml.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/loadyaml.rb
new file mode 100644
index 00000000..10c40050
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/loadyaml.rb
@@ -0,0 +1,20 @@
+module Puppet::Parser::Functions
+
+ newfunction(:loadyaml, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args|
+ Load a YAML file containing an array, string, or hash, and return the data
+ in the corresponding native data type.
+
+ For example:
+
+ $myhash = loadyaml('/etc/puppet/data/myhash.yaml')
+ ENDHEREDOC
+
+ unless args.length == 1
+ raise Puppet::ParseError, ("loadyaml(): wrong number of arguments (#{args.length}; must be 1)")
+ end
+
+ YAML.load_file(args[0])
+
+ end
+
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/lstrip.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/lstrip.rb
new file mode 100644
index 00000000..624e4c84
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/lstrip.rb
@@ -0,0 +1,32 @@
+#
+# lstrip.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:lstrip, :type => :rvalue, :doc => <<-EOS
+Strips leading spaces to the left of a string.
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "lstrip(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ value = arguments[0]
+
+ unless value.is_a?(Array) || value.is_a?(String)
+ raise(Puppet::ParseError, 'lstrip(): Requires either ' +
+ 'array or string to work with')
+ end
+
+ if value.is_a?(Array)
+ # Numbers in Puppet are often string-encoded which is troublesome ...
+ result = value.collect { |i| i.is_a?(String) ? i.lstrip : i }
+ else
+ result = value.lstrip
+ end
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/max.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/max.rb
new file mode 100644
index 00000000..60fb94ac
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/max.rb
@@ -0,0 +1,21 @@
+module Puppet::Parser::Functions
+ newfunction(:max, :type => :rvalue, :doc => <<-EOS
+ Returns the highest value of all arguments.
+ Requires at least one argument.
+ EOS
+ ) do |args|
+
+ raise(Puppet::ParseError, "max(): Wrong number of arguments " +
+ "need at least one") if args.size == 0
+
+ # Sometimes we get numbers as numerics and sometimes as strings.
+ # We try to compare them as numbers when possible
+ return args.max do |a,b|
+ if a.to_s =~ /\A-?\d+(.\d+)?\z/ and b.to_s =~ /\A-?\d+(.\d+)?\z/ then
+ a.to_f <=> b.to_f
+ else
+ a.to_s <=> b.to_s
+ end
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/member.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/member.rb
new file mode 100644
index 00000000..88609ce5
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/member.rb
@@ -0,0 +1,62 @@
+#
+# member.rb
+#
+
+# TODO(Krzysztof Wilczynski): We need to add support for regular expression ...
+# TODO(Krzysztof Wilczynski): Support for strings and hashes too ...
+
+module Puppet::Parser::Functions
+ newfunction(:member, :type => :rvalue, :doc => <<-EOS
+This function determines if a variable is a member of an array.
+The variable can be a string, fixnum, or array.
+
+*Examples:*
+
+ member(['a','b'], 'b')
+
+Would return: true
+
+ member(['a', 'b', 'c'], ['a', 'b'])
+
+would return: true
+
+ member(['a','b'], 'c')
+
+Would return: false
+
+ member(['a', 'b', 'c'], ['d', 'b'])
+
+would return: false
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "member(): Wrong number of arguments " +
+ "given (#{arguments.size} for 2)") if arguments.size < 2
+
+ array = arguments[0]
+
+ unless array.is_a?(Array)
+ raise(Puppet::ParseError, 'member(): Requires array to work with')
+ end
+
+ unless arguments[1].is_a? String or arguments[1].is_a? Fixnum or arguments[1].is_a? Array
+ raise(Puppet::ParseError, 'member(): Item to search for must be a string, fixnum, or array')
+ end
+
+ if arguments[1].is_a? String or arguments[1].is_a? Fixnum
+ item = Array(arguments[1])
+ else
+ item = arguments[1]
+ end
+
+
+ raise(Puppet::ParseError, 'member(): You must provide item ' +
+ 'to search for within array given') if item.respond_to?('empty?') && item.empty?
+
+ result = (item - array).empty?
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/merge.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/merge.rb
new file mode 100644
index 00000000..1b39f206
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/merge.rb
@@ -0,0 +1,34 @@
+module Puppet::Parser::Functions
+ newfunction(:merge, :type => :rvalue, :doc => <<-'ENDHEREDOC') do |args|
+ Merges two or more hashes together and returns the resulting hash.
+
+ For example:
+
+ $hash1 = {'one' => 1, 'two', => 2}
+ $hash2 = {'two' => 'dos', 'three', => 'tres'}
+ $merged_hash = merge($hash1, $hash2)
+ # The resulting hash is equivalent to:
+ # $merged_hash = {'one' => 1, 'two' => 'dos', 'three' => 'tres'}
+
+ When there is a duplicate key, the key in the rightmost hash will "win."
+
+ ENDHEREDOC
+
+ if args.length < 2
+ raise Puppet::ParseError, ("merge(): wrong number of arguments (#{args.length}; must be at least 2)")
+ end
+
+ # The hash we accumulate into
+ accumulator = Hash.new
+ # Merge into the accumulator hash
+ args.each do |arg|
+ next if arg.is_a? String and arg.empty? # empty string is synonym for puppet's undef
+ unless arg.is_a?(Hash)
+ raise Puppet::ParseError, "merge: unexpected argument type #{arg.class}, only expects hash arguments"
+ end
+ accumulator.merge!(arg)
+ end
+ # Return the fully merged hash
+ accumulator
+ end
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/min.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/min.rb
new file mode 100644
index 00000000..6bd6ebf2
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/min.rb
@@ -0,0 +1,21 @@
+module Puppet::Parser::Functions
+ newfunction(:min, :type => :rvalue, :doc => <<-EOS
+ Returns the lowest value of all arguments.
+ Requires at least one argument.
+ EOS
+ ) do |args|
+
+ raise(Puppet::ParseError, "min(): Wrong number of arguments " +
+ "need at least one") if args.size == 0
+
+ # Sometimes we get numbers as numerics and sometimes as strings.
+ # We try to compare them as numbers when possible
+ return args.min do |a,b|
+ if a.to_s =~ /\A^-?\d+(.\d+)?\z/ and b.to_s =~ /\A-?\d+(.\d+)?\z/ then
+ a.to_f <=> b.to_f
+ else
+ a.to_s <=> b.to_s
+ end
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/num2bool.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/num2bool.rb
new file mode 100644
index 00000000..af0e6ed7
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/num2bool.rb
@@ -0,0 +1,43 @@
+#
+# num2bool.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:num2bool, :type => :rvalue, :doc => <<-EOS
+This function converts a number or a string representation of a number into a
+true boolean. Zero or anything non-numeric becomes false. Numbers higher then 0
+become true.
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "num2bool(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size != 1
+
+ number = arguments[0]
+
+ case number
+ when Numeric
+ # Yay, it's a number
+ when String
+ begin
+ number = Float(number)
+ rescue ArgumentError => ex
+ raise(Puppet::ParseError, "num2bool(): '#{number}' does not look like a number: #{ex.message}")
+ end
+ else
+ begin
+ number = number.to_s
+ rescue NoMethodError => ex
+ raise(Puppet::ParseError, "num2bool(): Unable to parse argument: #{ex.message}")
+ end
+ end
+
+ # Truncate Floats
+ number = number.to_i
+
+ # Return true for any positive number and false otherwise
+ return number > 0
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/obfuscate_email.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/obfuscate_email.rb
new file mode 100644
index 00000000..4e4cb826
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/obfuscate_email.rb
@@ -0,0 +1,16 @@
+module Puppet::Parser::Functions
+ newfunction(:obfuscate_email, :type => :rvalue, :doc => <<-EOS
+Given:
+ a comma seperated email string in form of 'john@doe.com, doe@john.com'
+
+This function will return all emails obfuscated in form of 'john {at} doe {dot} com, doe {at} john {dot} com'
+Works with multiple email adresses as well as with a single email adress.
+
+ EOS
+ ) do |args|
+ args[0].gsub('@', ' {at} ').gsub('.', ' {dot} ')
+ end
+end
+
+# vim: set ts=2 sw=2 et :
+# encoding: utf-8
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/parsejson.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/parsejson.rb
new file mode 100644
index 00000000..a9a16a45
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/parsejson.rb
@@ -0,0 +1,24 @@
+#
+# parsejson.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:parsejson, :type => :rvalue, :doc => <<-EOS
+This function accepts JSON as a string and converts into the correct Puppet
+structure.
+ EOS
+ ) do |arguments|
+
+ if (arguments.size != 1) then
+ raise(Puppet::ParseError, "parsejson(): Wrong number of arguments "+
+ "given #{arguments.size} for 1")
+ end
+
+ json = arguments[0]
+
+ # PSON is natively available in puppet
+ PSON.load(json)
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/parseyaml.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/parseyaml.rb
new file mode 100644
index 00000000..53d54faf
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/parseyaml.rb
@@ -0,0 +1,24 @@
+#
+# parseyaml.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:parseyaml, :type => :rvalue, :doc => <<-EOS
+This function accepts YAML as a string and converts it into the correct
+Puppet structure.
+ EOS
+ ) do |arguments|
+
+ if (arguments.size != 1) then
+ raise(Puppet::ParseError, "parseyaml(): Wrong number of arguments "+
+ "given #{arguments.size} for 1")
+ end
+
+ require 'yaml'
+
+ YAML::load(arguments[0])
+
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/pick.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/pick.rb
new file mode 100644
index 00000000..fdd0aefd
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/pick.rb
@@ -0,0 +1,29 @@
+module Puppet::Parser::Functions
+ newfunction(:pick, :type => :rvalue, :doc => <<-EOS
+
+This function is similar to a coalesce function in SQL in that it will return
+the first value in a list of values that is not undefined or an empty string
+(two things in Puppet that will return a boolean false value). Typically,
+this function is used to check for a value in the Puppet Dashboard/Enterprise
+Console, and failover to a default value like the following:
+
+ $real_jenkins_version = pick($::jenkins_version, '1.449')
+
+The value of $real_jenkins_version will first look for a top-scope variable
+called 'jenkins_version' (note that parameters set in the Puppet Dashboard/
+Enterprise Console are brought into Puppet as top-scope variables), and,
+failing that, will use a default value of 1.449.
+
+EOS
+) do |args|
+ args = args.compact
+ args.delete(:undef)
+ args.delete(:undefined)
+ args.delete("")
+ if args[0].to_s.empty? then
+ fail Puppet::ParseError, "pick(): must receive at least one non empty value"
+ else
+ return args[0]
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/pick_default.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/pick_default.rb
new file mode 100644
index 00000000..36e33abf
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/pick_default.rb
@@ -0,0 +1,35 @@
+module Puppet::Parser::Functions
+ newfunction(:pick_default, :type => :rvalue, :doc => <<-EOS
+
+This function is similar to a coalesce function in SQL in that it will return
+the first value in a list of values that is not undefined or an empty string
+(two things in Puppet that will return a boolean false value). If no value is
+found, it will return the last argument.
+
+Typically, this function is used to check for a value in the Puppet
+Dashboard/Enterprise Console, and failover to a default value like the
+following:
+
+ $real_jenkins_version = pick_default($::jenkins_version, '1.449')
+
+The value of $real_jenkins_version will first look for a top-scope variable
+called 'jenkins_version' (note that parameters set in the Puppet Dashboard/
+Enterprise Console are brought into Puppet as top-scope variables), and,
+failing that, will use a default value of 1.449.
+
+Note that, contrary to the pick() function, the pick_default does not fail if
+all arguments are empty. This allows pick_default to use an empty value as
+default.
+
+EOS
+) do |args|
+ fail "Must receive at least one argument." if args.empty?
+ default = args.last
+ args = args[0..-2].compact
+ args.delete(:undef)
+ args.delete(:undefined)
+ args.delete("")
+ args << default
+ return args[0]
+ end
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/prefix.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/prefix.rb
new file mode 100644
index 00000000..d02286af
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/prefix.rb
@@ -0,0 +1,45 @@
+#
+# prefix.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:prefix, :type => :rvalue, :doc => <<-EOS
+This function applies a prefix to all elements in an array.
+
+*Examples:*
+
+ prefix(['a','b','c'], 'p')
+
+Will return: ['pa','pb','pc']
+ EOS
+ ) do |arguments|
+
+ # Technically we support two arguments but only first is mandatory ...
+ raise(Puppet::ParseError, "prefix(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ array = arguments[0]
+
+ unless array.is_a?(Array)
+ raise Puppet::ParseError, "prefix(): expected first argument to be an Array, got #{array.inspect}"
+ end
+
+ prefix = arguments[1] if arguments[1]
+
+ if prefix
+ unless prefix.is_a?(String)
+ raise Puppet::ParseError, "prefix(): expected second argument to be a String, got #{prefix.inspect}"
+ end
+ end
+
+ # Turn everything into string same as join would do ...
+ result = array.collect do |i|
+ i = i.to_s
+ prefix ? prefix + i : i
+ end
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/private.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/private.rb
new file mode 100644
index 00000000..60210d33
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/private.rb
@@ -0,0 +1,29 @@
+#
+# private.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:private, :doc => <<-'EOS'
+ Sets the current class or definition as private.
+ Calling the class or definition from outside the current module will fail.
+ EOS
+ ) do |args|
+
+ raise(Puppet::ParseError, "private(): Wrong number of arguments "+
+ "given (#{args.size}}) for 0 or 1)") if args.size > 1
+
+ scope = self
+ if scope.lookupvar('module_name') != scope.lookupvar('caller_module_name')
+ message = nil
+ if args[0] and args[0].is_a? String
+ message = args[0]
+ else
+ manifest_name = scope.source.name
+ manifest_type = scope.source.type
+ message = (manifest_type.to_s == 'hostclass') ? 'Class' : 'Definition'
+ message += " #{manifest_name} is private"
+ end
+ raise(Puppet::ParseError, message)
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/range.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/range.rb
new file mode 100644
index 00000000..49fba21c
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/range.rb
@@ -0,0 +1,88 @@
+#
+# range.rb
+#
+
+# TODO(Krzysztof Wilczynski): We probably need to approach numeric values differently ...
+
+module Puppet::Parser::Functions
+ newfunction(:range, :type => :rvalue, :doc => <<-EOS
+When given range in the form of (start, stop) it will extrapolate a range as
+an array.
+
+*Examples:*
+
+ range("0", "9")
+
+Will return: [0,1,2,3,4,5,6,7,8,9]
+
+ range("00", "09")
+
+Will return: [0,1,2,3,4,5,6,7,8,9] (Zero padded strings are converted to
+integers automatically)
+
+ range("a", "c")
+
+Will return: ["a","b","c"]
+
+ range("host01", "host10")
+
+Will return: ["host01", "host02", ..., "host09", "host10"]
+
+Passing a third argument will cause the generated range to step by that
+interval, e.g.
+
+ range("0", "9", "2")
+
+Will return: [0,2,4,6,8]
+ EOS
+ ) do |arguments|
+
+ # We support more than one argument but at least one is mandatory ...
+ raise(Puppet::ParseError, "range(): Wrong number of " +
+ "arguments given (#{arguments.size} for 1)") if arguments.size < 1
+
+ if arguments.size > 1
+ start = arguments[0]
+ stop = arguments[1]
+ step = arguments[2].nil? ? 1 : arguments[2].to_i.abs
+
+ type = '..' # We select simplest type for Range available in Ruby ...
+
+ elsif arguments.size > 0
+ value = arguments[0]
+
+ if m = value.match(/^(\w+)(\.\.\.?|\-)(\w+)$/)
+ start = m[1]
+ stop = m[3]
+
+ type = m[2]
+
+ elsif value.match(/^.+$/)
+ raise(Puppet::ParseError, 'range(): Unable to compute range ' +
+ 'from the value given')
+ else
+ raise(Puppet::ParseError, 'range(): Unknown format of range given')
+ end
+ end
+
+ # Check whether we have integer value if so then make it so ...
+ if start.to_s.match(/^\d+$/)
+ start = start.to_i
+ stop = stop.to_i
+ else
+ start = start.to_s
+ stop = stop.to_s
+ end
+
+ range = case type
+ when /^(\.\.|\-)$/ then (start .. stop)
+ when /^(\.\.\.)$/ then (start ... stop) # Exclusive of last element ...
+ end
+
+ result = range.step(step).collect { |i| i } # Get them all ... Pokemon ...
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/reject.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/reject.rb
new file mode 100644
index 00000000..1953ffcf
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/reject.rb
@@ -0,0 +1,31 @@
+#
+# reject.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:reject, :type => :rvalue, :doc => <<-EOS) do |args|
+This function searches through an array and rejects all elements that match
+the provided regular expression.
+
+*Examples:*
+
+ reject(['aaa','bbb','ccc','aaaddd'], 'aaa')
+
+Would return:
+
+ ['bbb','ccc']
+EOS
+
+ if (args.size != 2)
+ raise Puppet::ParseError,
+ "reject(): Wrong number of arguments given #{args.size} for 2"
+ end
+
+ ary = args[0]
+ pattern = Regexp.new(args[1])
+
+ ary.reject { |e| e =~ pattern }
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/reverse.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/reverse.rb
new file mode 100644
index 00000000..7f1018f6
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/reverse.rb
@@ -0,0 +1,27 @@
+#
+# reverse.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:reverse, :type => :rvalue, :doc => <<-EOS
+Reverses the order of a string or array.
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "reverse(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ value = arguments[0]
+
+ unless value.is_a?(Array) || value.is_a?(String)
+ raise(Puppet::ParseError, 'reverse(): Requires either ' +
+ 'array or string to work with')
+ end
+
+ result = value.reverse
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/rstrip.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/rstrip.rb
new file mode 100644
index 00000000..0cf8d222
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/rstrip.rb
@@ -0,0 +1,31 @@
+#
+# rstrip.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:rstrip, :type => :rvalue, :doc => <<-EOS
+Strips leading spaces to the right of the string.
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "rstrip(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ value = arguments[0]
+
+ unless value.is_a?(Array) || value.is_a?(String)
+ raise(Puppet::ParseError, 'rstrip(): Requires either ' +
+ 'array or string to work with')
+ end
+
+ if value.is_a?(Array)
+ result = value.collect { |i| i.is_a?(String) ? i.rstrip : i }
+ else
+ result = value.rstrip
+ end
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/shuffle.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/shuffle.rb
new file mode 100644
index 00000000..30c663db
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/shuffle.rb
@@ -0,0 +1,45 @@
+#
+# shuffle.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:shuffle, :type => :rvalue, :doc => <<-EOS
+Randomizes the order of a string or array elements.
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "shuffle(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ value = arguments[0]
+
+ unless value.is_a?(Array) || value.is_a?(String)
+ raise(Puppet::ParseError, 'shuffle(): Requires either ' +
+ 'array or string to work with')
+ end
+
+ result = value.clone
+
+ string = value.is_a?(String) ? true : false
+
+ # Check whether it makes sense to shuffle ...
+ return result if result.size <= 1
+
+ # We turn any string value into an array to be able to shuffle ...
+ result = string ? result.split('') : result
+
+ elements = result.size
+
+ # Simple implementation of Fisher–Yates in-place shuffle ...
+ elements.times do |i|
+ j = rand(elements - i) + i
+ result[j], result[i] = result[i], result[j]
+ end
+
+ result = string ? result.join : result
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/size.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/size.rb
new file mode 100644
index 00000000..cc207e3f
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/size.rb
@@ -0,0 +1,48 @@
+#
+# size.rb
+#
+
+# TODO(Krzysztof Wilczynski): Support for hashes would be nice too ...
+
+module Puppet::Parser::Functions
+ newfunction(:size, :type => :rvalue, :doc => <<-EOS
+Returns the number of elements in a string or array.
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "size(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ item = arguments[0]
+
+ if item.is_a?(String)
+
+ begin
+ #
+ # Check whether your item is a numeric value or not ...
+ # This will take care about positive and/or negative numbers
+ # for both integer and floating-point values ...
+ #
+ # Please note that Puppet has no notion of hexadecimal
+ # nor octal numbers for its DSL at this point in time ...
+ #
+ Float(item)
+
+ raise(Puppet::ParseError, 'size(): Requires either ' +
+ 'string or array to work with')
+
+ rescue ArgumentError
+ result = item.size
+ end
+
+ elsif item.is_a?(Array)
+ result = item.size
+ else
+ raise(Puppet::ParseError, 'size(): Unknown type given')
+ end
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/sort.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/sort.rb
new file mode 100644
index 00000000..cefbe546
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/sort.rb
@@ -0,0 +1,27 @@
+#
+# sort.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:sort, :type => :rvalue, :doc => <<-EOS
+Sorts strings and arrays lexically.
+ EOS
+ ) do |arguments|
+
+ if (arguments.size != 1) then
+ raise(Puppet::ParseError, "sort(): Wrong number of arguments "+
+ "given #{arguments.size} for 1")
+ end
+
+ value = arguments[0]
+
+ if value.is_a?(Array) then
+ value.sort
+ elsif value.is_a?(String) then
+ value.split("").sort.join("")
+ end
+
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/squeeze.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/squeeze.rb
new file mode 100644
index 00000000..81fadfdb
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/squeeze.rb
@@ -0,0 +1,36 @@
+#
+# squeeze.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:squeeze, :type => :rvalue, :doc => <<-EOS
+Returns a new string where runs of the same character that occur in this set are replaced by a single character.
+ EOS
+ ) do |arguments|
+
+ if ((arguments.size != 2) and (arguments.size != 1)) then
+ raise(Puppet::ParseError, "squeeze(): Wrong number of arguments "+
+ "given #{arguments.size} for 2 or 1")
+ end
+
+ item = arguments[0]
+ squeezeval = arguments[1]
+
+ if item.is_a?(Array) then
+ if squeezeval then
+ item.collect { |i| i.squeeze(squeezeval) }
+ else
+ item.collect { |i| i.squeeze }
+ end
+ else
+ if squeezeval then
+ item.squeeze(squeezeval)
+ else
+ item.squeeze
+ end
+ end
+
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/str2bool.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/str2bool.rb
new file mode 100644
index 00000000..446732ec
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/str2bool.rb
@@ -0,0 +1,46 @@
+#
+# str2bool.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:str2bool, :type => :rvalue, :doc => <<-EOS
+This converts a string to a boolean. This attempt to convert strings that
+contain things like: y, 1, t, true to 'true' and strings that contain things
+like: 0, f, n, false, no to 'false'.
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "str2bool(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ string = arguments[0]
+
+ # If string is already Boolean, return it
+ if !!string == string
+ return string
+ end
+
+ unless string.is_a?(String)
+ raise(Puppet::ParseError, 'str2bool(): Requires either ' +
+ 'string to work with')
+ end
+
+ # We consider all the yes, no, y, n and so on too ...
+ result = case string
+ #
+ # This is how undef looks like in Puppet ...
+ # We yield false in this case.
+ #
+ when /^$/, '' then false # Empty string will be false ...
+ when /^(1|t|y|true|yes)$/ then true
+ when /^(0|f|n|false|no)$/ then false
+ when /^(undef|undefined)$/ then false # This is not likely to happen ...
+ else
+ raise(Puppet::ParseError, 'str2bool(): Unknown type of boolean given')
+ end
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/str2saltedsha1.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/str2saltedsha1.rb
new file mode 100644
index 00000000..e51a861a
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/str2saltedsha1.rb
@@ -0,0 +1,32 @@
+#
+# str2saltedsha1.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:str2saltedsha1, :type => :rvalue, :doc => <<-EOS
+This converts a string to a salted-SHA1 password hash (which is used for
+OS X versions >= 10.7). Given any simple string, you will get a hex version
+of a salted-SHA1 password hash that can be inserted into your Puppet
+manifests as a valid password attribute.
+ EOS
+ ) do |arguments|
+ require 'digest/sha2'
+
+ raise(Puppet::ParseError, "str2saltedsha1(): Wrong number of arguments " +
+ "passed (#{arguments.size} but we require 1)") if arguments.size != 1
+
+ password = arguments[0]
+
+ unless password.is_a?(String)
+ raise(Puppet::ParseError, 'str2saltedsha1(): Requires a ' +
+ "String argument, you passed: #{password.class}")
+ end
+
+ seedint = rand(2**31 - 1)
+ seedstring = Array(seedint).pack("L")
+ saltedpass = Digest::SHA1.digest(seedstring + password)
+ (seedstring + saltedpass).unpack('H*')[0]
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/str2saltedsha512.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/str2saltedsha512.rb
new file mode 100644
index 00000000..7fe7b012
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/str2saltedsha512.rb
@@ -0,0 +1,32 @@
+#
+# str2saltedsha512.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:str2saltedsha512, :type => :rvalue, :doc => <<-EOS
+This converts a string to a salted-SHA512 password hash (which is used for
+OS X versions >= 10.7). Given any simple string, you will get a hex version
+of a salted-SHA512 password hash that can be inserted into your Puppet
+manifests as a valid password attribute.
+ EOS
+ ) do |arguments|
+ require 'digest/sha2'
+
+ raise(Puppet::ParseError, "str2saltedsha512(): Wrong number of arguments " +
+ "passed (#{arguments.size} but we require 1)") if arguments.size != 1
+
+ password = arguments[0]
+
+ unless password.is_a?(String)
+ raise(Puppet::ParseError, 'str2saltedsha512(): Requires a ' +
+ "String argument, you passed: #{password.class}")
+ end
+
+ seedint = rand(2**31 - 1)
+ seedstring = Array(seedint).pack("L")
+ saltedpass = Digest::SHA512.digest(seedstring + password)
+ (seedstring + saltedpass).unpack('H*')[0]
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/str2sha1_and_salt.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/str2sha1_and_salt.rb
new file mode 100644
index 00000000..9ec382d0
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/str2sha1_and_salt.rb
@@ -0,0 +1,36 @@
+#
+# str2saltedsha1.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:str2sha1_and_salt, :type => :rvalue, :doc => <<-EOS
+This converts a string to an array containing the salted SHA1 password hash in
+the first field, and the salt itself in second field of the returned array.
+This combination is used i.e. for couchdb passwords.
+ EOS
+ ) do |arguments|
+ require 'digest/sha1'
+
+ raise(Puppet::ParseError, "str2saltedsha1(): Wrong number of arguments " +
+ "passed (#{arguments.size} but we require 1)") if arguments.size != 1
+
+ password = arguments[0]
+
+ unless password.is_a?(String)
+ raise(Puppet::ParseError, 'str2saltedsha1(): Requires a ' +
+ "String argument, you passed: #{password.class}")
+ end
+
+ seedint = rand(2**31 - 1)
+ seedstring = Array(seedint).pack("L")
+ salt = Digest::MD5.hexdigest(seedstring)
+ saltedpass = Digest::SHA1.hexdigest(password + salt)
+
+ array = Array.new
+ array << saltedpass
+ array << salt
+ return array
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/str_and_salt2sha1.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/str_and_salt2sha1.rb
new file mode 100644
index 00000000..71d69cf5
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/str_and_salt2sha1.rb
@@ -0,0 +1,32 @@
+#
+# str_and_salt2sha1.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:str_and_salt2sha1, :type => :rvalue, :doc => <<-EOS
+This converts a string to an array containing the salted SHA1 password hash in
+the first field, and the salt itself in second field of the returned array.
+This combination is used i.e. for couchdb passwords.
+ EOS
+ ) do |arguments|
+ require 'digest/sha1'
+
+ raise(Puppet::ParseError, "str_and_salt2sha1(): Wrong number of arguments " +
+ "passed (#{arguments.size} but we require 1)") if arguments.size != 1
+
+ str_and_salt = arguments[0]
+
+ unless str_and_salt.is_a?(Array)
+ raise(Puppet::ParseError, 'str_and_salt2sha1(): Requires a ' +
+ "Array argument, you passed: #{password.class}")
+ end
+
+ str = str_and_salt[0]
+ salt = str_and_salt[1]
+ sha1 = Digest::SHA1.hexdigest(str+ salt)
+
+ return sha1
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/strftime.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/strftime.rb
new file mode 100644
index 00000000..0b52adec
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/strftime.rb
@@ -0,0 +1,107 @@
+#
+# strftime.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:strftime, :type => :rvalue, :doc => <<-EOS
+This function returns formatted time.
+
+*Examples:*
+
+To return the time since epoch:
+
+ strftime("%s")
+
+To return the date:
+
+ strftime("%Y-%m-%d")
+
+*Format meaning:*
+
+ %a - The abbreviated weekday name (``Sun'')
+ %A - The full weekday name (``Sunday'')
+ %b - The abbreviated month name (``Jan'')
+ %B - The full month name (``January'')
+ %c - The preferred local date and time representation
+ %C - Century (20 in 2009)
+ %d - Day of the month (01..31)
+ %D - Date (%m/%d/%y)
+ %e - Day of the month, blank-padded ( 1..31)
+ %F - Equivalent to %Y-%m-%d (the ISO 8601 date format)
+ %h - Equivalent to %b
+ %H - Hour of the day, 24-hour clock (00..23)
+ %I - Hour of the day, 12-hour clock (01..12)
+ %j - Day of the year (001..366)
+ %k - hour, 24-hour clock, blank-padded ( 0..23)
+ %l - hour, 12-hour clock, blank-padded ( 0..12)
+ %L - Millisecond of the second (000..999)
+ %m - Month of the year (01..12)
+ %M - Minute of the hour (00..59)
+ %n - Newline (\n)
+ %N - Fractional seconds digits, default is 9 digits (nanosecond)
+ %3N millisecond (3 digits)
+ %6N microsecond (6 digits)
+ %9N nanosecond (9 digits)
+ %p - Meridian indicator (``AM'' or ``PM'')
+ %P - Meridian indicator (``am'' or ``pm'')
+ %r - time, 12-hour (same as %I:%M:%S %p)
+ %R - time, 24-hour (%H:%M)
+ %s - Number of seconds since 1970-01-01 00:00:00 UTC.
+ %S - Second of the minute (00..60)
+ %t - Tab character (\t)
+ %T - time, 24-hour (%H:%M:%S)
+ %u - Day of the week as a decimal, Monday being 1. (1..7)
+ %U - Week number of the current year,
+ starting with the first Sunday as the first
+ day of the first week (00..53)
+ %v - VMS date (%e-%b-%Y)
+ %V - Week number of year according to ISO 8601 (01..53)
+ %W - Week number of the current year,
+ starting with the first Monday as the first
+ day of the first week (00..53)
+ %w - Day of the week (Sunday is 0, 0..6)
+ %x - Preferred representation for the date alone, no time
+ %X - Preferred representation for the time alone, no date
+ %y - Year without a century (00..99)
+ %Y - Year with century
+ %z - Time zone as hour offset from UTC (e.g. +0900)
+ %Z - Time zone name
+ %% - Literal ``%'' character
+ EOS
+ ) do |arguments|
+
+ # Technically we support two arguments but only first is mandatory ...
+ raise(Puppet::ParseError, "strftime(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ format = arguments[0]
+
+ raise(Puppet::ParseError, 'strftime(): You must provide ' +
+ 'format for evaluation') if format.empty?
+
+ # The Time Zone argument is optional ...
+ time_zone = arguments[1] if arguments[1]
+
+ time = Time.new
+
+ # There is probably a better way to handle Time Zone ...
+ if time_zone and not time_zone.empty?
+ original_zone = ENV['TZ']
+
+ local_time = time.clone
+ local_time = local_time.utc
+
+ ENV['TZ'] = time_zone
+
+ time = local_time.localtime
+
+ ENV['TZ'] = original_zone
+ end
+
+ result = time.strftime(format)
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/strip.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/strip.rb
new file mode 100644
index 00000000..3fac47d5
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/strip.rb
@@ -0,0 +1,38 @@
+#
+# strip.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:strip, :type => :rvalue, :doc => <<-EOS
+This function removes leading and trailing whitespace from a string or from
+every string inside an array.
+
+*Examples:*
+
+ strip(" aaa ")
+
+Would result in: "aaa"
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "strip(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ value = arguments[0]
+
+ unless value.is_a?(Array) || value.is_a?(String)
+ raise(Puppet::ParseError, 'strip(): Requires either ' +
+ 'array or string to work with')
+ end
+
+ if value.is_a?(Array)
+ result = value.collect { |i| i.is_a?(String) ? i.strip : i }
+ else
+ result = value.strip
+ end
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/suffix.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/suffix.rb
new file mode 100644
index 00000000..f7792d6f
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/suffix.rb
@@ -0,0 +1,45 @@
+#
+# suffix.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:suffix, :type => :rvalue, :doc => <<-EOS
+This function applies a suffix to all elements in an array.
+
+*Examples:*
+
+ suffix(['a','b','c'], 'p')
+
+Will return: ['ap','bp','cp']
+ EOS
+ ) do |arguments|
+
+ # Technically we support two arguments but only first is mandatory ...
+ raise(Puppet::ParseError, "suffix(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ array = arguments[0]
+
+ unless array.is_a?(Array)
+ raise Puppet::ParseError, "suffix(): expected first argument to be an Array, got #{array.inspect}"
+ end
+
+ suffix = arguments[1] if arguments[1]
+
+ if suffix
+ unless suffix.is_a? String
+ raise Puppet::ParseError, "suffix(): expected second argument to be a String, got #{suffix.inspect}"
+ end
+ end
+
+ # Turn everything into string same as join would do ...
+ result = array.collect do |i|
+ i = i.to_s
+ suffix ? i + suffix : i
+ end
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/swapcase.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/swapcase.rb
new file mode 100644
index 00000000..eb7fe137
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/swapcase.rb
@@ -0,0 +1,38 @@
+#
+# swapcase.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:swapcase, :type => :rvalue, :doc => <<-EOS
+This function will swap the existing case of a string.
+
+*Examples:*
+
+ swapcase("aBcD")
+
+Would result in: "AbCd"
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "swapcase(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ value = arguments[0]
+
+ unless value.is_a?(Array) || value.is_a?(String)
+ raise(Puppet::ParseError, 'swapcase(): Requires either ' +
+ 'array or string to work with')
+ end
+
+ if value.is_a?(Array)
+ # Numbers in Puppet are often string-encoded which is troublesome ...
+ result = value.collect { |i| i.is_a?(String) ? i.swapcase : i }
+ else
+ result = value.swapcase
+ end
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/time.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/time.rb
new file mode 100644
index 00000000..0cddaf86
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/time.rb
@@ -0,0 +1,49 @@
+#
+# time.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:time, :type => :rvalue, :doc => <<-EOS
+This function will return the current time since epoch as an integer.
+
+*Examples:*
+
+ time()
+
+Will return something like: 1311972653
+ EOS
+ ) do |arguments|
+
+ # The Time Zone argument is optional ...
+ time_zone = arguments[0] if arguments[0]
+
+ if (arguments.size != 0) and (arguments.size != 1) then
+ raise(Puppet::ParseError, "time(): Wrong number of arguments "+
+ "given #{arguments.size} for 0 or 1")
+ end
+
+ time = Time.new
+
+ # There is probably a better way to handle Time Zone ...
+ if time_zone and not time_zone.empty?
+ original_zone = ENV['TZ']
+
+ local_time = time.clone
+ local_time = local_time.utc
+
+ ENV['TZ'] = time_zone
+
+ time = local_time.localtime
+
+ ENV['TZ'] = original_zone
+ end
+
+ # Calling Time#to_i on a receiver changes it. Trust me I am the Doctor.
+ result = time.strftime('%s')
+ result = result.to_i
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/to_bytes.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/to_bytes.rb
new file mode 100644
index 00000000..df490ea8
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/to_bytes.rb
@@ -0,0 +1,31 @@
+module Puppet::Parser::Functions
+ newfunction(:to_bytes, :type => :rvalue, :doc => <<-EOS
+ Converts the argument into bytes, for example 4 kB becomes 4096.
+ Takes a single string value as an argument.
+ These conversions reflect a layperson's understanding of
+ 1 MB = 1024 KB, when in fact 1 MB = 1000 KB, and 1 MiB = 1024 KiB.
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "to_bytes(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size != 1
+
+ arg = arguments[0]
+
+ return arg if arg.is_a? Numeric
+
+ value,prefix = */([0-9.e+-]*)\s*([^bB]?)/.match(arg)[1,2]
+
+ value = value.to_f
+ case prefix
+ when '' then return value.to_i
+ when 'k' then return (value*(1<<10)).to_i
+ when 'M' then return (value*(1<<20)).to_i
+ when 'G' then return (value*(1<<30)).to_i
+ when 'T' then return (value*(1<<40)).to_i
+ when 'P' then return (value*(1<<50)).to_i
+ when 'E' then return (value*(1<<60)).to_i
+ else raise Puppet::ParseError, "to_bytes(): Unknown prefix #{prefix}"
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/type.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/type.rb
new file mode 100644
index 00000000..016529b0
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/type.rb
@@ -0,0 +1,19 @@
+#
+# type.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:type, :type => :rvalue, :doc => <<-EOS
+ DEPRECATED: This function will cease to function on Puppet 4; please use type3x() before upgrading to puppet 4 for backwards-compatibility, or migrate to the new parser's typing system.
+ EOS
+ ) do |args|
+
+ warning("type() DEPRECATED: This function will cease to function on Puppet 4; please use type3x() before upgrading to puppet 4 for backwards-compatibility, or migrate to the new parser's typing system.")
+ if ! Puppet::Parser::Functions.autoloader.loaded?(:type3x)
+ Puppet::Parser::Functions.autoloader.load(:type3x)
+ end
+ function_type3x(args + [false])
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/type3x.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/type3x.rb
new file mode 100644
index 00000000..0800b4a3
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/type3x.rb
@@ -0,0 +1,51 @@
+#
+# type3x.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:type3x, :type => :rvalue, :doc => <<-EOS
+DEPRECATED: This function will be removed when puppet 3 support is dropped; please migrate to the new parser's typing system.
+
+Returns the type when passed a value. Type can be one of:
+
+* string
+* array
+* hash
+* float
+* integer
+* boolean
+ EOS
+ ) do |args|
+ raise(Puppet::ParseError, "type3x(): Wrong number of arguments " +
+ "given (#{args.size} for 1)") if args.size < 1
+
+ value = args[0]
+
+ klass = value.class
+
+ if not [TrueClass, FalseClass, Array, Bignum, Fixnum, Float, Hash, String].include?(klass)
+ raise(Puppet::ParseError, 'type3x(): Unknown type')
+ end
+
+ klass = klass.to_s # Ugly ...
+
+ # We note that Integer is the parent to Bignum and Fixnum ...
+ result = case klass
+ when /^(?:Big|Fix)num$/ then 'integer'
+ when /^(?:True|False)Class$/ then 'boolean'
+ else klass
+ end
+
+ if result == "String" then
+ if value == value.to_i.to_s then
+ result = "Integer"
+ elsif value == value.to_f.to_s then
+ result = "Float"
+ end
+ end
+
+ return result.downcase
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/union.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/union.rb
new file mode 100644
index 00000000..c91bb805
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/union.rb
@@ -0,0 +1,34 @@
+#
+# union.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:union, :type => :rvalue, :doc => <<-EOS
+This function returns a union of two arrays.
+
+*Examples:*
+
+ union(["a","b","c"],["b","c","d"])
+
+Would return: ["a","b","c","d"]
+ EOS
+ ) do |arguments|
+
+ # Two arguments are required
+ raise(Puppet::ParseError, "union(): Wrong number of arguments " +
+ "given (#{arguments.size} for 2)") if arguments.size != 2
+
+ first = arguments[0]
+ second = arguments[1]
+
+ unless first.is_a?(Array) && second.is_a?(Array)
+ raise(Puppet::ParseError, 'union(): Requires 2 arrays')
+ end
+
+ result = first | second
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/unique.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/unique.rb
new file mode 100644
index 00000000..cf770f3b
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/unique.rb
@@ -0,0 +1,50 @@
+#
+# unique.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:unique, :type => :rvalue, :doc => <<-EOS
+This function will remove duplicates from strings and arrays.
+
+*Examples:*
+
+ unique("aabbcc")
+
+Will return:
+
+ abc
+
+You can also use this with arrays:
+
+ unique(["a","a","b","b","c","c"])
+
+This returns:
+
+ ["a","b","c"]
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "unique(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ value = arguments[0]
+
+ unless value.is_a?(Array) || value.is_a?(String)
+ raise(Puppet::ParseError, 'unique(): Requires either ' +
+ 'array or string to work with')
+ end
+
+ result = value.clone
+
+ string = value.is_a?(String) ? true : false
+
+ # We turn any string value into an array to be able to shuffle ...
+ result = string ? result.split('') : result
+ result = result.uniq # Remove duplicates ...
+ result = string ? result.join : result
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/upcase.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/upcase.rb
new file mode 100644
index 00000000..4302b29e
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/upcase.rb
@@ -0,0 +1,40 @@
+#
+# upcase.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:upcase, :type => :rvalue, :doc => <<-EOS
+Converts a string or an array of strings to uppercase.
+
+*Examples:*
+
+ upcase("abcd")
+
+Will return:
+
+ ASDF
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "upcase(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ value = arguments[0]
+
+ unless value.is_a?(Array) || value.is_a?(String)
+ raise(Puppet::ParseError, 'upcase(): Requires either ' +
+ 'array or string to work with')
+ end
+
+ if value.is_a?(Array)
+ # Numbers in Puppet are often string-encoded which is troublesome ...
+ result = value.collect { |i| i.is_a?(String) ? i.upcase : i }
+ else
+ result = value.upcase
+ end
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/uriescape.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/uriescape.rb
new file mode 100644
index 00000000..a486eee5
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/uriescape.rb
@@ -0,0 +1,34 @@
+#
+# uriescape.rb
+#
+require 'uri'
+
+module Puppet::Parser::Functions
+ newfunction(:uriescape, :type => :rvalue, :doc => <<-EOS
+ Urlencodes a string or array of strings.
+ Requires either a single string or an array as an input.
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "uriescape(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ value = arguments[0]
+
+ unless value.is_a?(Array) || value.is_a?(String)
+ raise(Puppet::ParseError, 'uriescape(): Requires either ' +
+ 'array or string to work with')
+ end
+
+ if value.is_a?(Array)
+ # Numbers in Puppet are often string-encoded which is troublesome ...
+ result = value.collect { |i| i.is_a?(String) ? URI.escape(i,unsafe) : i }
+ else
+ result = URI.escape(value)
+ end
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/validate_absolute_path.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/validate_absolute_path.rb
new file mode 100644
index 00000000..b6966809
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/validate_absolute_path.rb
@@ -0,0 +1,69 @@
+module Puppet::Parser::Functions
+ newfunction(:validate_absolute_path, :doc => <<-'ENDHEREDOC') do |args|
+ Validate the string represents an absolute path in the filesystem. This function works
+ for windows and unix style paths.
+
+ The following values will pass:
+
+ $my_path = 'C:/Program Files (x86)/Puppet Labs/Puppet'
+ validate_absolute_path($my_path)
+ $my_path2 = '/var/lib/puppet'
+ validate_absolute_path($my_path2)
+ $my_path3 = ['C:/Program Files (x86)/Puppet Labs/Puppet','C:/Program Files/Puppet Labs/Puppet']
+ validate_absolute_path($my_path3)
+ $my_path4 = ['/var/lib/puppet','/usr/share/puppet']
+ validate_absolute_path($my_path4)
+
+ The following values will fail, causing compilation to abort:
+
+ validate_absolute_path(true)
+ validate_absolute_path('../var/lib/puppet')
+ validate_absolute_path('var/lib/puppet')
+ validate_absolute_path([ 'var/lib/puppet', '/var/foo' ])
+ validate_absolute_path([ '/var/lib/puppet', 'var/foo' ])
+ $undefined = undef
+ validate_absolute_path($undefined)
+
+ ENDHEREDOC
+
+ require 'puppet/util'
+
+ unless args.length > 0 then
+ raise Puppet::ParseError, ("validate_absolute_path(): wrong number of arguments (#{args.length}; must be > 0)")
+ end
+
+ args.each do |arg|
+ # put arg to candidate var to be able to replace it
+ candidates = arg
+ # if arg is just a string with a path to test, convert it to an array
+ # to avoid test code duplication
+ unless arg.is_a?(Array) then
+ candidates = Array.new(1,arg)
+ end
+ # iterate over all pathes within the candidates array
+ candidates.each do |path|
+ # This logic was borrowed from
+ # [lib/puppet/file_serving/base.rb](https://github.com/puppetlabs/puppet/blob/master/lib/puppet/file_serving/base.rb)
+ # Puppet 2.7 and beyond will have Puppet::Util.absolute_path? Fall back to a back-ported implementation otherwise.
+ if Puppet::Util.respond_to?(:absolute_path?) then
+ unless Puppet::Util.absolute_path?(path, :posix) or Puppet::Util.absolute_path?(path, :windows)
+ raise Puppet::ParseError, ("#{path.inspect} is not an absolute path.")
+ end
+ else
+ # This code back-ported from 2.7.x's lib/puppet/util.rb Puppet::Util.absolute_path?
+ # Determine in a platform-specific way whether a path is absolute. This
+ # defaults to the local platform if none is specified.
+ # Escape once for the string literal, and once for the regex.
+ slash = '[\\\\/]'
+ name = '[^\\\\/]+'
+ regexes = {
+ :windows => %r!^(([A-Z]:#{slash})|(#{slash}#{slash}#{name}#{slash}#{name})|(#{slash}#{slash}\?#{slash}#{name}))!i,
+ :posix => %r!^/!,
+ }
+ rval = (!!(path =~ regexes[:posix])) || (!!(path =~ regexes[:windows]))
+ rval or raise Puppet::ParseError, ("#{path.inspect} is not an absolute path.")
+ end
+ end
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/validate_array.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/validate_array.rb
new file mode 100644
index 00000000..34b51182
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/validate_array.rb
@@ -0,0 +1,33 @@
+module Puppet::Parser::Functions
+
+ newfunction(:validate_array, :doc => <<-'ENDHEREDOC') do |args|
+ Validate that all passed values are array data structures. Abort catalog
+ compilation if any value fails this check.
+
+ The following values will pass:
+
+ $my_array = [ 'one', 'two' ]
+ validate_array($my_array)
+
+ The following values will fail, causing compilation to abort:
+
+ validate_array(true)
+ validate_array('some_string')
+ $undefined = undef
+ validate_array($undefined)
+
+ ENDHEREDOC
+
+ unless args.length > 0 then
+ raise Puppet::ParseError, ("validate_array(): wrong number of arguments (#{args.length}; must be > 0)")
+ end
+
+ args.each do |arg|
+ unless arg.is_a?(Array)
+ raise Puppet::ParseError, ("#{arg.inspect} is not an Array. It looks to be a #{arg.class}")
+ end
+ end
+
+ end
+
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/validate_augeas.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/validate_augeas.rb
new file mode 100644
index 00000000..4ea4fe07
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/validate_augeas.rb
@@ -0,0 +1,83 @@
+require 'tempfile'
+
+module Puppet::Parser::Functions
+ newfunction(:validate_augeas, :doc => <<-'ENDHEREDOC') do |args|
+ Perform validation of a string using an Augeas lens
+ The first argument of this function should be a string to
+ test, and the second argument should be the name of the Augeas lens to use.
+ If Augeas fails to parse the string with the lens, the compilation will
+ abort with a parse error.
+
+ A third argument can be specified, listing paths which should
+ not be found in the file. The `$file` variable points to the location
+ of the temporary file being tested in the Augeas tree.
+
+ For example, if you want to make sure your passwd content never contains
+ a user `foo`, you could write:
+
+ validate_augeas($passwdcontent, 'Passwd.lns', ['$file/foo'])
+
+ Or if you wanted to ensure that no users used the '/bin/barsh' shell,
+ you could use:
+
+ validate_augeas($passwdcontent, 'Passwd.lns', ['$file/*[shell="/bin/barsh"]']
+
+ If a fourth argument is specified, this will be the error message raised and
+ seen by the user.
+
+ A helpful error message can be returned like this:
+
+ validate_augeas($sudoerscontent, 'Sudoers.lns', [], 'Failed to validate sudoers content with Augeas')
+
+ ENDHEREDOC
+ unless Puppet.features.augeas?
+ raise Puppet::ParseError, ("validate_augeas(): this function requires the augeas feature. See http://projects.puppetlabs.com/projects/puppet/wiki/Puppet_Augeas#Pre-requisites for how to activate it.")
+ end
+
+ if (args.length < 2) or (args.length > 4) then
+ raise Puppet::ParseError, ("validate_augeas(): wrong number of arguments (#{args.length}; must be 2, 3, or 4)")
+ end
+
+ msg = args[3] || "validate_augeas(): Failed to validate content against #{args[1].inspect}"
+
+ require 'augeas'
+ aug = Augeas::open(nil, nil, Augeas::NO_MODL_AUTOLOAD)
+ begin
+ content = args[0]
+
+ # Test content in a temporary file
+ tmpfile = Tempfile.new("validate_augeas")
+ begin
+ tmpfile.write(content)
+ ensure
+ tmpfile.close
+ end
+
+ # Check for syntax
+ lens = args[1]
+ aug.transform(
+ :lens => lens,
+ :name => 'Validate_augeas',
+ :incl => tmpfile.path
+ )
+ aug.load!
+
+ unless aug.match("/augeas/files#{tmpfile.path}//error").empty?
+ error = aug.get("/augeas/files#{tmpfile.path}//error/message")
+ msg += " with error: #{error}"
+ raise Puppet::ParseError, (msg)
+ end
+
+ # Launch unit tests
+ tests = args[2] || []
+ aug.defvar('file', "/files#{tmpfile.path}")
+ tests.each do |t|
+ msg += " testing path #{t}"
+ raise Puppet::ParseError, (msg) unless aug.match(t).empty?
+ end
+ ensure
+ aug.close
+ tmpfile.unlink
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/validate_bool.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/validate_bool.rb
new file mode 100644
index 00000000..59a08056
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/validate_bool.rb
@@ -0,0 +1,34 @@
+module Puppet::Parser::Functions
+
+ newfunction(:validate_bool, :doc => <<-'ENDHEREDOC') do |args|
+ Validate that all passed values are either true or false. Abort catalog
+ compilation if any value fails this check.
+
+ The following values will pass:
+
+ $iamtrue = true
+ validate_bool(true)
+ validate_bool(true, true, false, $iamtrue)
+
+ The following values will fail, causing compilation to abort:
+
+ $some_array = [ true ]
+ validate_bool("false")
+ validate_bool("true")
+ validate_bool($some_array)
+
+ ENDHEREDOC
+
+ unless args.length > 0 then
+ raise Puppet::ParseError, ("validate_bool(): wrong number of arguments (#{args.length}; must be > 0)")
+ end
+
+ args.each do |arg|
+ unless function_is_bool([arg])
+ raise Puppet::ParseError, ("#{arg.inspect} is not a boolean. It looks to be a #{arg.class}")
+ end
+ end
+
+ end
+
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/validate_cmd.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/validate_cmd.rb
new file mode 100644
index 00000000..5df3c609
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/validate_cmd.rb
@@ -0,0 +1,63 @@
+require 'puppet/util/execution'
+require 'tempfile'
+
+module Puppet::Parser::Functions
+ newfunction(:validate_cmd, :doc => <<-'ENDHEREDOC') do |args|
+ Perform validation of a string with an external command.
+ The first argument of this function should be a string to
+ test, and the second argument should be a path to a test command
+ taking a % as a placeholder for the file path (will default to the end).
+ If the command, launched against a tempfile containing the passed string,
+ returns a non-null value, compilation will abort with a parse error.
+
+ If a third argument is specified, this will be the error message raised and
+ seen by the user.
+
+ A helpful error message can be returned like this:
+
+ Example:
+
+ # Defaults to end of path
+ validate_cmd($sudoerscontent, '/usr/sbin/visudo -c -f', 'Visudo failed to validate sudoers content')
+
+ # % as file location
+ validate_cmd($haproxycontent, '/usr/sbin/haproxy -f % -c', 'Haproxy failed to validate config content')
+
+ ENDHEREDOC
+ if (args.length < 2) or (args.length > 3) then
+ raise Puppet::ParseError, ("validate_cmd(): wrong number of arguments (#{args.length}; must be 2 or 3)")
+ end
+
+ msg = args[2] || "validate_cmd(): failed to validate content with command #{args[1].inspect}"
+
+ content = args[0]
+ checkscript = args[1]
+
+ # Test content in a temporary file
+ tmpfile = Tempfile.new("validate_cmd")
+ begin
+ tmpfile.write(content)
+ tmpfile.close
+
+ if checkscript =~ /\s%(\s|$)/
+ check_with_correct_location = checkscript.gsub(/%/,tmpfile.path)
+ else
+ check_with_correct_location = "#{checkscript} #{tmpfile.path}"
+ end
+
+ if Puppet::Util::Execution.respond_to?('execute')
+ Puppet::Util::Execution.execute(check_with_correct_location)
+ else
+ Puppet::Util.execute(check_with_correct_location)
+ end
+ rescue Puppet::ExecutionFailure => detail
+ msg += "\n#{detail}"
+ raise Puppet::ParseError, msg
+ rescue Exception => detail
+ msg += "\n#{detail.class.name} #{detail}"
+ raise Puppet::ParseError, msg
+ ensure
+ tmpfile.unlink
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/validate_hash.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/validate_hash.rb
new file mode 100644
index 00000000..9bdd5432
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/validate_hash.rb
@@ -0,0 +1,33 @@
+module Puppet::Parser::Functions
+
+ newfunction(:validate_hash, :doc => <<-'ENDHEREDOC') do |args|
+ Validate that all passed values are hash data structures. Abort catalog
+ compilation if any value fails this check.
+
+ The following values will pass:
+
+ $my_hash = { 'one' => 'two' }
+ validate_hash($my_hash)
+
+ The following values will fail, causing compilation to abort:
+
+ validate_hash(true)
+ validate_hash('some_string')
+ $undefined = undef
+ validate_hash($undefined)
+
+ ENDHEREDOC
+
+ unless args.length > 0 then
+ raise Puppet::ParseError, ("validate_hash(): wrong number of arguments (#{args.length}; must be > 0)")
+ end
+
+ args.each do |arg|
+ unless arg.is_a?(Hash)
+ raise Puppet::ParseError, ("#{arg.inspect} is not a Hash. It looks to be a #{arg.class}")
+ end
+ end
+
+ end
+
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/validate_ipv4_address.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/validate_ipv4_address.rb
new file mode 100644
index 00000000..fc02748e
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/validate_ipv4_address.rb
@@ -0,0 +1,48 @@
+module Puppet::Parser::Functions
+
+ newfunction(:validate_ipv4_address, :doc => <<-ENDHEREDOC
+ Validate that all values passed are valid IPv4 addresses.
+ Fail compilation if any value fails this check.
+
+ The following values will pass:
+
+ $my_ip = "1.2.3.4"
+ validate_ipv4_address($my_ip)
+ validate_bool("8.8.8.8", "172.16.0.1", $my_ip)
+
+ The following values will fail, causing compilation to abort:
+
+ $some_array = [ 1, true, false, "garbage string", "3ffe:505:2" ]
+ validate_ipv4_address($some_array)
+
+ ENDHEREDOC
+ ) do |args|
+
+ require "ipaddr"
+ rescuable_exceptions = [ ArgumentError ]
+
+ if defined?(IPAddr::InvalidAddressError)
+ rescuable_exceptions << IPAddr::InvalidAddressError
+ end
+
+ unless args.length > 0 then
+ raise Puppet::ParseError, ("validate_ipv4_address(): wrong number of arguments (#{args.length}; must be > 0)")
+ end
+
+ args.each do |arg|
+ unless arg.is_a?(String)
+ raise Puppet::ParseError, "#{arg.inspect} is not a string."
+ end
+
+ begin
+ unless IPAddr.new(arg).ipv4?
+ raise Puppet::ParseError, "#{arg.inspect} is not a valid IPv4 address."
+ end
+ rescue *rescuable_exceptions
+ raise Puppet::ParseError, "#{arg.inspect} is not a valid IPv4 address."
+ end
+ end
+
+ end
+
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/validate_ipv6_address.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/validate_ipv6_address.rb
new file mode 100644
index 00000000..b0f2558d
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/validate_ipv6_address.rb
@@ -0,0 +1,49 @@
+module Puppet::Parser::Functions
+
+ newfunction(:validate_ipv6_address, :doc => <<-ENDHEREDOC
+ Validate that all values passed are valid IPv6 addresses.
+ Fail compilation if any value fails this check.
+
+ The following values will pass:
+
+ $my_ip = "3ffe:505:2"
+ validate_ipv6_address(1)
+ validate_ipv6_address($my_ip)
+ validate_bool("fe80::baf6:b1ff:fe19:7507", $my_ip)
+
+ The following values will fail, causing compilation to abort:
+
+ $some_array = [ true, false, "garbage string", "1.2.3.4" ]
+ validate_ipv6_address($some_array)
+
+ ENDHEREDOC
+ ) do |args|
+
+ require "ipaddr"
+ rescuable_exceptions = [ ArgumentError ]
+
+ if defined?(IPAddr::InvalidAddressError)
+ rescuable_exceptions << IPAddr::InvalidAddressError
+ end
+
+ unless args.length > 0 then
+ raise Puppet::ParseError, ("validate_ipv6_address(): wrong number of arguments (#{args.length}; must be > 0)")
+ end
+
+ args.each do |arg|
+ unless arg.is_a?(String)
+ raise Puppet::ParseError, "#{arg.inspect} is not a string."
+ end
+
+ begin
+ unless IPAddr.new(arg).ipv6?
+ raise Puppet::ParseError, "#{arg.inspect} is not a valid IPv6 address."
+ end
+ rescue *rescuable_exceptions
+ raise Puppet::ParseError, "#{arg.inspect} is not a valid IPv6 address."
+ end
+ end
+
+ end
+
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/validate_re.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/validate_re.rb
new file mode 100644
index 00000000..ca25a702
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/validate_re.rb
@@ -0,0 +1,40 @@
+module Puppet::Parser::Functions
+ newfunction(:validate_re, :doc => <<-'ENDHEREDOC') do |args|
+ Perform simple validation of a string against one or more regular
+ expressions. The first argument of this function should be a string to
+ test, and the second argument should be a stringified regular expression
+ (without the // delimiters) or an array of regular expressions. If none
+ of the regular expressions match the string passed in, compilation will
+ abort with a parse error.
+
+ If a third argument is specified, this will be the error message raised and
+ seen by the user.
+
+ The following strings will validate against the regular expressions:
+
+ validate_re('one', '^one$')
+ validate_re('one', [ '^one', '^two' ])
+
+ The following strings will fail to validate, causing compilation to abort:
+
+ validate_re('one', [ '^two', '^three' ])
+
+ A helpful error message can be returned like this:
+
+ validate_re($::puppetversion, '^2.7', 'The $puppetversion fact value does not match 2.7')
+
+ ENDHEREDOC
+ if (args.length < 2) or (args.length > 3) then
+ raise Puppet::ParseError, ("validate_re(): wrong number of arguments (#{args.length}; must be 2 or 3)")
+ end
+
+ msg = args[2] || "validate_re(): #{args[0].inspect} does not match #{args[1].inspect}"
+
+ # We're using a flattened array here because we can't call String#any? in
+ # Ruby 1.9 like we can in Ruby 1.8
+ raise Puppet::ParseError, (msg) unless [args[1]].flatten.any? do |re_str|
+ args[0] =~ Regexp.compile(re_str)
+ end
+
+ end
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/validate_slength.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/validate_slength.rb
new file mode 100644
index 00000000..7d534f37
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/validate_slength.rb
@@ -0,0 +1,71 @@
+module Puppet::Parser::Functions
+
+ newfunction(:validate_slength, :doc => <<-'ENDHEREDOC') do |args|
+ Validate that the first argument is a string (or an array of strings), and
+ less/equal to than the length of the second argument. An optional third
+ parameter can be given a the minimum length. It fails if the first
+ argument is not a string or array of strings, and if arg 2 and arg 3 are
+ not convertable to a number.
+
+ The following values will pass:
+
+ validate_slength("discombobulate",17)
+ validate_slength(["discombobulate","moo"],17)
+ validate_slength(["discombobulate","moo"],17,3)
+
+ The following valueis will not:
+
+ validate_slength("discombobulate",1)
+ validate_slength(["discombobulate","thermometer"],5)
+ validate_slength(["discombobulate","moo"],17,10)
+
+ ENDHEREDOC
+
+ raise Puppet::ParseError, "validate_slength(): Wrong number of arguments (#{args.length}; must be 2 or 3)" unless args.length == 2 or args.length == 3
+
+ input, max_length, min_length = *args
+
+ begin
+ max_length = Integer(max_length)
+ raise ArgumentError if max_length <= 0
+ rescue ArgumentError, TypeError
+ raise Puppet::ParseError, "validate_slength(): Expected second argument to be a positive Numeric, got #{max_length}:#{max_length.class}"
+ end
+
+ if min_length
+ begin
+ min_length = Integer(min_length)
+ raise ArgumentError if min_length < 0
+ rescue ArgumentError, TypeError
+ raise Puppet::ParseError, "validate_slength(): Expected third argument to be unset or a positive Numeric, got #{min_length}:#{min_length.class}"
+ end
+ else
+ min_length = 0
+ end
+
+ if min_length > max_length
+ raise Puppet::ParseError, "validate_slength(): Expected second argument to be larger than third argument"
+ end
+
+ validator = lambda do |str|
+ unless str.length <= max_length and str.length >= min_length
+ raise Puppet::ParseError, "validate_slength(): Expected length of #{input.inspect} to be between #{min_length} and #{max_length}, was #{input.length}"
+ end
+ end
+
+ case input
+ when String
+ validator.call(input)
+ when Array
+ input.each_with_index do |arg, pos|
+ if arg.is_a? String
+ validator.call(arg)
+ else
+ raise Puppet::ParseError, "validate_slength(): Expected element at array position #{pos} to be a String, got #{arg.class}"
+ end
+ end
+ else
+ raise Puppet::ParseError, "validate_slength(): Expected first argument to be a String or Array, got #{input.class}"
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/validate_string.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/validate_string.rb
new file mode 100644
index 00000000..c841f6ab
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/validate_string.rb
@@ -0,0 +1,38 @@
+module Puppet::Parser::Functions
+
+ newfunction(:validate_string, :doc => <<-'ENDHEREDOC') do |args|
+ Validate that all passed values are string data structures. Abort catalog
+ compilation if any value fails this check.
+
+ The following values will pass:
+
+ $my_string = "one two"
+ validate_string($my_string, 'three')
+
+ The following values will fail, causing compilation to abort:
+
+ validate_string(true)
+ validate_string([ 'some', 'array' ])
+
+ Note: validate_string(undef) will not fail in this version of the
+ functions API (incl. current and future parser). Instead, use:
+
+ if $var == undef {
+ fail('...')
+ }
+
+ ENDHEREDOC
+
+ unless args.length > 0 then
+ raise Puppet::ParseError, ("validate_string(): wrong number of arguments (#{args.length}; must be > 0)")
+ end
+
+ args.each do |arg|
+ unless arg.is_a?(String)
+ raise Puppet::ParseError, ("#{arg.inspect} is not a string. It looks to be a #{arg.class}")
+ end
+ end
+
+ end
+
+end
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/values.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/values.rb
new file mode 100644
index 00000000..16067561
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/values.rb
@@ -0,0 +1,39 @@
+#
+# values.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:values, :type => :rvalue, :doc => <<-EOS
+When given a hash this function will return the values of that hash.
+
+*Examples:*
+
+ $hash = {
+ 'a' => 1,
+ 'b' => 2,
+ 'c' => 3,
+ }
+ values($hash)
+
+This example would return:
+
+ [1,2,3]
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "values(): Wrong number of arguments " +
+ "given (#{arguments.size} for 1)") if arguments.size < 1
+
+ hash = arguments[0]
+
+ unless hash.is_a?(Hash)
+ raise(Puppet::ParseError, 'values(): Requires hash to work with')
+ end
+
+ result = hash.values
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/values_at.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/values_at.rb
new file mode 100644
index 00000000..f350f539
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/values_at.rb
@@ -0,0 +1,99 @@
+#
+# values_at.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:values_at, :type => :rvalue, :doc => <<-EOS
+Finds value inside an array based on location.
+
+The first argument is the array you want to analyze, and the second element can
+be a combination of:
+
+* A single numeric index
+* A range in the form of 'start-stop' (eg. 4-9)
+* An array combining the above
+
+*Examples*:
+
+ values_at(['a','b','c'], 2)
+
+Would return ['c'].
+
+ values_at(['a','b','c'], ["0-1"])
+
+Would return ['a','b'].
+
+ values_at(['a','b','c','d','e'], [0, "2-3"])
+
+Would return ['a','c','d'].
+ EOS
+ ) do |arguments|
+
+ raise(Puppet::ParseError, "values_at(): Wrong number of " +
+ "arguments given (#{arguments.size} for 2)") if arguments.size < 2
+
+ array = arguments.shift
+
+ unless array.is_a?(Array)
+ raise(Puppet::ParseError, 'values_at(): Requires array to work with')
+ end
+
+ indices = [arguments.shift].flatten() # Get them all ... Pokemon ...
+
+ if not indices or indices.empty?
+ raise(Puppet::ParseError, 'values_at(): You must provide ' +
+ 'at least one positive index to collect')
+ end
+
+ result = []
+ indices_list = []
+
+ indices.each do |i|
+ i = i.to_s
+ if m = i.match(/^(\d+)(\.\.\.?|\-)(\d+)$/)
+ start = m[1].to_i
+ stop = m[3].to_i
+
+ type = m[2]
+
+ if start > stop
+ raise(Puppet::ParseError, 'values_at(): Stop index in ' +
+ 'given indices range is smaller than the start index')
+ elsif stop > array.size - 1 # First element is at index 0 is it not?
+ raise(Puppet::ParseError, 'values_at(): Stop index in ' +
+ 'given indices range exceeds array size')
+ end
+
+ range = case type
+ when /^(\.\.|\-)$/ then (start .. stop)
+ when /^(\.\.\.)$/ then (start ... stop) # Exclusive of last element ...
+ end
+
+ range.each { |i| indices_list << i.to_i }
+ else
+ # Only positive numbers allowed in this case ...
+ if not i.match(/^\d+$/)
+ raise(Puppet::ParseError, 'values_at(): Unknown format ' +
+ 'of given index')
+ end
+
+ # In Puppet numbers are often string-encoded ...
+ i = i.to_i
+
+ if i > array.size - 1 # Same story. First element is at index 0 ...
+ raise(Puppet::ParseError, 'values_at(): Given index ' +
+ 'exceeds array size')
+ end
+
+ indices_list << i
+ end
+ end
+
+ # We remove nil values as they make no sense in Puppet DSL ...
+ result = indices_list.collect { |i| array[i] }.compact
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/parser/functions/zip.rb b/puppet/modules/stdlib/lib/puppet/parser/functions/zip.rb
new file mode 100644
index 00000000..3074f282
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/parser/functions/zip.rb
@@ -0,0 +1,39 @@
+#
+# zip.rb
+#
+
+module Puppet::Parser::Functions
+ newfunction(:zip, :type => :rvalue, :doc => <<-EOS
+Takes one element from first array and merges corresponding elements from second array. This generates a sequence of n-element arrays, where n is one more than the count of arguments.
+
+*Example:*
+
+ zip(['1','2','3'],['4','5','6'])
+
+Would result in:
+
+ ["1", "4"], ["2", "5"], ["3", "6"]
+ EOS
+ ) do |arguments|
+
+ # Technically we support three arguments but only first is mandatory ...
+ raise(Puppet::ParseError, "zip(): Wrong number of arguments " +
+ "given (#{arguments.size} for 2)") if arguments.size < 2
+
+ a = arguments[0]
+ b = arguments[1]
+
+ unless a.is_a?(Array) and b.is_a?(Array)
+ raise(Puppet::ParseError, 'zip(): Requires array to work with')
+ end
+
+ flatten = function_str2bool([arguments[2]]) if arguments[2]
+
+ result = a.zip(b)
+ result = flatten ? result.flatten : result
+
+ return result
+ end
+end
+
+# vim: set ts=2 sw=2 et :
diff --git a/puppet/modules/stdlib/lib/puppet/provider/file_line/ruby.rb b/puppet/modules/stdlib/lib/puppet/provider/file_line/ruby.rb
new file mode 100644
index 00000000..ae1a8b3d
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/provider/file_line/ruby.rb
@@ -0,0 +1,85 @@
+Puppet::Type.type(:file_line).provide(:ruby) do
+ def exists?
+ lines.find do |line|
+ line.chomp == resource[:line].chomp
+ end
+ end
+
+ def create
+ if resource[:match]
+ handle_create_with_match
+ elsif resource[:after]
+ handle_create_with_after
+ else
+ append_line
+ end
+ end
+
+ def destroy
+ local_lines = lines
+ File.open(resource[:path],'w') do |fh|
+ fh.write(local_lines.reject{|l| l.chomp == resource[:line] }.join(''))
+ end
+ end
+
+ private
+ def lines
+ # If this type is ever used with very large files, we should
+ # write this in a different way, using a temp
+ # file; for now assuming that this type is only used on
+ # small-ish config files that can fit into memory without
+ # too much trouble.
+ @lines ||= File.readlines(resource[:path])
+ end
+
+ def handle_create_with_match()
+ regex = resource[:match] ? Regexp.new(resource[:match]) : nil
+ match_count = count_matches(regex)
+ if match_count > 1 && resource[:multiple].to_s != 'true'
+ raise Puppet::Error, "More than one line in file '#{resource[:path]}' matches pattern '#{resource[:match]}'"
+ end
+ File.open(resource[:path], 'w') do |fh|
+ lines.each do |l|
+ fh.puts(regex.match(l) ? resource[:line] : l)
+ end
+
+ if (match_count == 0)
+ fh.puts(resource[:line])
+ end
+ end
+ end
+
+ def handle_create_with_after
+ regex = Regexp.new(resource[:after])
+ count = count_matches(regex)
+ case count
+ when 1 # find the line to put our line after
+ File.open(resource[:path], 'w') do |fh|
+ lines.each do |l|
+ fh.puts(l)
+ if regex.match(l) then
+ fh.puts(resource[:line])
+ end
+ end
+ end
+ when 0 # append the line to the end of the file
+ append_line
+ else
+ raise Puppet::Error, "#{count} lines match pattern '#{resource[:after]}' in file '#{resource[:path]}'. One or no line must match the pattern."
+ end
+ end
+
+ def count_matches(regex)
+ lines.select{|l| l.match(regex)}.size
+ end
+
+ ##
+ # append the line to the file.
+ #
+ # @api private
+ def append_line
+ File.open(resource[:path], 'a') do |fh|
+ fh.puts resource[:line]
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/lib/puppet/type/anchor.rb b/puppet/modules/stdlib/lib/puppet/type/anchor.rb
new file mode 100644
index 00000000..fe1e5aa1
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/type/anchor.rb
@@ -0,0 +1,46 @@
+Puppet::Type.newtype(:anchor) do
+ desc <<-'ENDOFDESC'
+ A simple resource type intended to be used as an anchor in a composite class.
+
+ In Puppet 2.6, when a class declares another class, the resources in the
+ interior class are not contained by the exterior class. This interacts badly
+ with the pattern of composing complex modules from smaller classes, as it
+ makes it impossible for end users to specify order relationships between the
+ exterior class and other modules.
+
+ The anchor type lets you work around this. By sandwiching any interior
+ classes between two no-op resources that _are_ contained by the exterior
+ class, you can ensure that all resources in the module are contained.
+
+ class ntp {
+ # These classes will have the correct order relationship with each
+ # other. However, without anchors, they won't have any order
+ # relationship to Class['ntp'].
+ class { 'ntp::package': }
+ -> class { 'ntp::config': }
+ -> class { 'ntp::service': }
+
+ # These two resources "anchor" the composed classes within the ntp
+ # class.
+ anchor { 'ntp::begin': } -> Class['ntp::package']
+ Class['ntp::service'] -> anchor { 'ntp::end': }
+ }
+
+ This allows the end user of the ntp module to establish require and before
+ relationships with Class['ntp']:
+
+ class { 'ntp': } -> class { 'mcollective': }
+ class { 'mcollective': } -> class { 'ntp': }
+
+ ENDOFDESC
+
+ newparam :name do
+ desc "The name of the anchor resource."
+ end
+
+ def refresh
+ # We don't do anything with them, but we need this to
+ # show that we are "refresh aware" and not break the
+ # chain of propagation.
+ end
+end
diff --git a/puppet/modules/stdlib/lib/puppet/type/file_line.rb b/puppet/modules/stdlib/lib/puppet/type/file_line.rb
new file mode 100644
index 00000000..df263e6a
--- /dev/null
+++ b/puppet/modules/stdlib/lib/puppet/type/file_line.rb
@@ -0,0 +1,75 @@
+Puppet::Type.newtype(:file_line) do
+
+ desc <<-EOT
+ Ensures that a given line is contained within a file. The implementation
+ matches the full line, including whitespace at the beginning and end. If
+ the line is not contained in the given file, Puppet will add the line to
+ ensure the desired state. Multiple resources may be declared to manage
+ multiple lines in the same file.
+
+ Example:
+
+ file_line { 'sudo_rule':
+ path => '/etc/sudoers',
+ line => '%sudo ALL=(ALL) ALL',
+ }
+ file_line { 'sudo_rule_nopw':
+ path => '/etc/sudoers',
+ line => '%sudonopw ALL=(ALL) NOPASSWD: ALL',
+ }
+
+ In this example, Puppet will ensure both of the specified lines are
+ contained in the file /etc/sudoers.
+
+ **Autorequires:** If Puppet is managing the file that will contain the line
+ being managed, the file_line resource will autorequire that file.
+
+ EOT
+
+ ensurable do
+ defaultvalues
+ defaultto :present
+ end
+
+ newparam(:name, :namevar => true) do
+ desc 'An arbitrary name used as the identity of the resource.'
+ end
+
+ newparam(:match) do
+ desc 'An optional regular expression to run against existing lines in the file;\n' +
+ 'if a match is found, we replace that line rather than adding a new line.'
+ end
+
+ newparam(:multiple) do
+ desc 'An optional value to determine if match can change multiple lines.'
+ newvalues(true, false)
+ end
+
+ newparam(:after) do
+ desc 'An optional value used to specify the line after which we will add any new lines. (Existing lines are added in place)'
+ end
+
+ newparam(:line) do
+ desc 'The line to be appended to the file located by the path parameter.'
+ end
+
+ newparam(:path) do
+ desc 'The file Puppet will ensure contains the line specified by the line parameter.'
+ validate do |value|
+ unless (Puppet.features.posix? and value =~ /^\//) or (Puppet.features.microsoft_windows? and (value =~ /^.:\// or value =~ /^\/\/[^\/]+\/[^\/]+/))
+ raise(Puppet::Error, "File paths must be fully qualified, not '#{value}'")
+ end
+ end
+ end
+
+ # Autorequire the file resource if it's being managed
+ autorequire(:file) do
+ self[:path]
+ end
+
+ validate do
+ unless self[:line] and self[:path]
+ raise(Puppet::Error, "Both line and path are required attributes")
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/manifests/init.pp b/puppet/modules/stdlib/manifests/init.pp
new file mode 100644
index 00000000..500ad770
--- /dev/null
+++ b/puppet/modules/stdlib/manifests/init.pp
@@ -0,0 +1,20 @@
+# Class: stdlib
+#
+# This module manages stdlib. Most of stdlib's features are automatically
+# loaded by Puppet, but this class should be declared in order to use the
+# standardized run stages.
+#
+# Parameters: none
+#
+# Actions:
+#
+# Declares all other classes in the stdlib module. Currently, this consists
+# of stdlib::stages.
+#
+# Requires: nothing
+#
+class stdlib {
+
+ class { 'stdlib::stages': }
+
+}
diff --git a/puppet/modules/stdlib/manifests/stages.pp b/puppet/modules/stdlib/manifests/stages.pp
new file mode 100644
index 00000000..eb15fd65
--- /dev/null
+++ b/puppet/modules/stdlib/manifests/stages.pp
@@ -0,0 +1,43 @@
+# Class: stdlib::stages
+#
+# This class manages a standard set of run stages for Puppet. It is managed by
+# the stdlib class, and should not be declared independently.
+#
+# The high level stages are (in order):
+#
+# * setup
+# * main
+# * runtime
+# * setup_infra
+# * deploy_infra
+# * setup_app
+# * deploy_app
+# * deploy
+#
+# Parameters: none
+#
+# Actions:
+#
+# Declares various run-stages for deploying infrastructure,
+# language runtimes, and application layers.
+#
+# Requires: nothing
+#
+# Sample Usage:
+#
+# node default {
+# include stdlib
+# class { java: stage => 'runtime' }
+# }
+#
+class stdlib::stages {
+
+ stage { 'setup': before => Stage['main'] }
+ stage { 'runtime': require => Stage['main'] }
+ -> stage { 'setup_infra': }
+ -> stage { 'deploy_infra': }
+ -> stage { 'setup_app': }
+ -> stage { 'deploy_app': }
+ -> stage { 'deploy': }
+
+}
diff --git a/puppet/modules/stdlib/metadata.json b/puppet/modules/stdlib/metadata.json
new file mode 100644
index 00000000..27def9c0
--- /dev/null
+++ b/puppet/modules/stdlib/metadata.json
@@ -0,0 +1,113 @@
+{
+ "name": "puppetlabs-stdlib",
+ "version": "4.5.1",
+ "author": "puppetlabs",
+ "summary": "Standard library of resources for Puppet modules.",
+ "license": "Apache-2.0",
+ "source": "https://github.com/puppetlabs/puppetlabs-stdlib",
+ "project_page": "https://github.com/puppetlabs/puppetlabs-stdlib",
+ "issues_url": "https://tickets.puppetlabs.com/browse/MODULES",
+ "operatingsystem_support": [
+ {
+ "operatingsystem": "RedHat",
+ "operatingsystemrelease": [
+ "4",
+ "5",
+ "6",
+ "7"
+ ]
+ },
+ {
+ "operatingsystem": "CentOS",
+ "operatingsystemrelease": [
+ "4",
+ "5",
+ "6",
+ "7"
+ ]
+ },
+ {
+ "operatingsystem": "OracleLinux",
+ "operatingsystemrelease": [
+ "4",
+ "5",
+ "6",
+ "7"
+ ]
+ },
+ {
+ "operatingsystem": "Scientific",
+ "operatingsystemrelease": [
+ "4",
+ "5",
+ "6",
+ "7"
+ ]
+ },
+ {
+ "operatingsystem": "SLES",
+ "operatingsystemrelease": [
+ "10 SP4",
+ "11 SP1",
+ "12"
+ ]
+ },
+ {
+ "operatingsystem": "Debian",
+ "operatingsystemrelease": [
+ "6",
+ "7"
+ ]
+ },
+ {
+ "operatingsystem": "Ubuntu",
+ "operatingsystemrelease": [
+ "10.04",
+ "12.04",
+ "14.04"
+ ]
+ },
+ {
+ "operatingsystem": "Solaris",
+ "operatingsystemrelease": [
+ "10",
+ "11"
+ ]
+ },
+ {
+ "operatingsystem": "Windows",
+ "operatingsystemrelease": [
+ "Server 2003",
+ "Server 2003 R2",
+ "Server 2008",
+ "Server 2008 R2",
+ "Server 2012",
+ "Server 2012 R2",
+ "7",
+ "8"
+ ]
+ },
+ {
+ "operatingsystem": "AIX",
+ "operatingsystemrelease": [
+ "5.3",
+ "6.1",
+ "7.1"
+ ]
+ }
+ ],
+ "requirements": [
+ {
+ "name": "pe",
+ "version_requirement": "3.x"
+ },
+ {
+ "name": "puppet",
+ "version_requirement": ">=2.7.20 <4.0.0"
+ }
+ ],
+ "description": "Standard Library for Puppet Modules",
+ "dependencies": [
+
+ ]
+}
diff --git a/puppet/modules/stdlib/spec/acceptance/abs_spec.rb b/puppet/modules/stdlib/spec/acceptance/abs_spec.rb
new file mode 100755
index 00000000..6e41e2fd
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/abs_spec.rb
@@ -0,0 +1,30 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'abs function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'should accept a string' do
+ pp = <<-EOS
+ $input = '-34.56'
+ $output = abs($input)
+ notify { "$output": }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: 34.56/)
+ end
+ end
+
+ it 'should accept a float' do
+ pp = <<-EOS
+ $input = -34.56
+ $output = abs($input)
+ notify { "$output": }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: 34.56/)
+ end
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/any2array_spec.rb b/puppet/modules/stdlib/spec/acceptance/any2array_spec.rb
new file mode 100755
index 00000000..18ea4cd9
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/any2array_spec.rb
@@ -0,0 +1,49 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'any2array function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'should create an empty array' do
+ pp = <<-EOS
+ $input = ''
+ $output = any2array($input)
+ validate_array($output)
+ notify { "Output: ${output}": }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: Output: /)
+ end
+ end
+
+ it 'should leave arrays modified' do
+ pp = <<-EOS
+ $input = ['test', 'array']
+ $output = any2array($input)
+ validate_array($output)
+ notify { "Output: ${output}": }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: Output: (\[|)test(,\s|)array(\]|)/)
+ end
+ end
+
+ it 'should turn a hash into an array' do
+ pp = <<-EOS
+ $input = {'test' => 'array'}
+ $output = any2array($input)
+
+ validate_array($output)
+ # Check each element of the array is a plain string.
+ validate_string($output[0])
+ validate_string($output[1])
+ notify { "Output: ${output}": }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: Output: (\[|)test(,\s|)array(\]|)/)
+ end
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/base64_spec.rb b/puppet/modules/stdlib/spec/acceptance/base64_spec.rb
new file mode 100755
index 00000000..97e1738e
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/base64_spec.rb
@@ -0,0 +1,18 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'base64 function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'should encode then decode a string' do
+ pp = <<-EOS
+ $encodestring = base64('encode', 'thestring')
+ $decodestring = base64('decode', $encodestring)
+ notify { $decodestring: }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/thestring/)
+ end
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/bool2num_spec.rb b/puppet/modules/stdlib/spec/acceptance/bool2num_spec.rb
new file mode 100755
index 00000000..52ff75bc
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/bool2num_spec.rb
@@ -0,0 +1,34 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'bool2num function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ ['false', 'f', '0', 'n', 'no'].each do |bool|
+ it "should convert a given boolean, #{bool}, to 0" do
+ pp = <<-EOS
+ $input = "#{bool}"
+ $output = bool2num($input)
+ notify { "$output": }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: 0/)
+ end
+ end
+ end
+
+ ['true', 't', '1', 'y', 'yes'].each do |bool|
+ it "should convert a given boolean, #{bool}, to 1" do
+ pp = <<-EOS
+ $input = "#{bool}"
+ $output = bool2num($input)
+ notify { "$output": }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: 1/)
+ end
+ end
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/build_csv.rb b/puppet/modules/stdlib/spec/acceptance/build_csv.rb
new file mode 100755
index 00000000..62ecbf13
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/build_csv.rb
@@ -0,0 +1,83 @@
+#!/usr/bin/env ruby
+# vim: set sw=2 sts=2 et tw=80 :
+require 'rspec'
+
+#XXX Super ugly hack to keep from starting beaker nodes
+module Kernel
+ # make an alias of the original require
+ alias_method :original_require, :require
+ # rewrite require
+ def require name
+ original_require name if name != 'spec_helper_acceptance'
+ end
+end
+UNSUPPORTED_PLATFORMS = []
+def fact(*args) [] end
+#XXX End hax
+
+# Get a list of functions for test coverage
+function_list = Dir[File.join(File.dirname(__FILE__),"..","..","lib","puppet","parser","functions","*.rb")].collect do |function_rb|
+ File.basename(function_rb,".rb")
+end
+
+## Configure rspec to parse tests
+options = RSpec::Core::ConfigurationOptions.new(['spec/acceptance'])
+configuration = RSpec::configuration
+world = RSpec::world
+options.parse_options
+options.configure(configuration)
+configuration.load_spec_files
+
+## Collect up tests and example groups into a hash
+def get_tests(children)
+ children.inject({}) do |memo,c|
+ memo[c.description] = Hash.new
+ memo[c.description]["groups"] = get_tests(c.children) unless c.children.empty?
+ memo[c.description]["tests"] = c.examples.collect { |e|
+ e.description unless e.pending?
+ }.compact unless c.examples.empty?
+ memo[c.description]["pending_tests"] = c.examples.collect { |e|
+ e.description if e.pending?
+ }.compact unless c.examples.empty?
+ memo
+ end
+end
+
+def count_test_types_in(type,group)
+ return 0 if group.nil?
+ group.inject(0) do |m,(k,v)|
+ m += v.length if k == type
+ m += count_tests_in(v) if v.is_a?(Hash)
+ m
+ end
+end
+def count_tests_in(group)
+ count_test_types_in('tests',group)
+end
+def count_pending_tests_in(group)
+ count_test_types_in('pending_tests',group)
+end
+
+# Convert tests hash to csv format
+def to_csv(function_list,tests)
+ function_list.collect do |function_name|
+ if v = tests["#{function_name} function"]
+ positive_tests = count_tests_in(v["groups"]["success"])
+ negative_tests = count_tests_in(v["groups"]["failure"])
+ pending_tests =
+ count_pending_tests_in(v["groups"]["failure"]) +
+ count_pending_tests_in(v["groups"]["failure"])
+ else
+ positive_tests = 0
+ negative_tests = 0
+ pending_tests = 0
+ end
+ sprintf("%-25s, %-9d, %-9d, %-9d", function_name,positive_tests,negative_tests,pending_tests)
+ end.compact
+end
+
+tests = get_tests(world.example_groups)
+csv = to_csv(function_list,tests)
+percentage_tested = "#{tests.count*100/function_list.count}%"
+printf("%-25s, %-9s, %-9s, %-9s\n","#{percentage_tested} have tests.","Positive","Negative","Pending")
+puts csv
diff --git a/puppet/modules/stdlib/spec/acceptance/capitalize_spec.rb b/puppet/modules/stdlib/spec/acceptance/capitalize_spec.rb
new file mode 100755
index 00000000..e5e7b7bf
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/capitalize_spec.rb
@@ -0,0 +1,33 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'capitalize function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'should capitalize the first letter of a string' do
+ pp = <<-EOS
+ $input = 'this is a string'
+ $output = capitalize($input)
+ notify { $output: }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: This is a string/)
+ end
+ end
+
+ it 'should capitalize the first letter of an array of strings' do
+ pp = <<-EOS
+ $input = ['this', 'is', 'a', 'string']
+ $output = capitalize($input)
+ notify { $output: }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: This/)
+ expect(r.stdout).to match(/Notice: Is/)
+ expect(r.stdout).to match(/Notice: A/)
+ expect(r.stdout).to match(/Notice: String/)
+ end
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/chomp_spec.rb b/puppet/modules/stdlib/spec/acceptance/chomp_spec.rb
new file mode 100755
index 00000000..f6c15956
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/chomp_spec.rb
@@ -0,0 +1,21 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'chomp function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'should eat the newline' do
+ pp = <<-EOS
+ $input = "test\n"
+ if size($input) != 5 {
+ fail("Size of ${input} is not 5.")
+ }
+ $output = chomp($input)
+ if size($output) != 4 {
+ fail("Size of ${input} is not 4.")
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/chop_spec.rb b/puppet/modules/stdlib/spec/acceptance/chop_spec.rb
new file mode 100755
index 00000000..a16a7102
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/chop_spec.rb
@@ -0,0 +1,45 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'chop function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'should eat the last character' do
+ pp = <<-EOS
+ $input = "test"
+ if size($input) != 4 {
+ fail("Size of ${input} is not 4.")
+ }
+ $output = chop($input)
+ if size($output) != 3 {
+ fail("Size of ${input} is not 3.")
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+
+ it 'should eat the last two characters of \r\n' do
+ pp = <<-'EOS'
+ $input = "test\r\n"
+ if size($input) != 6 {
+ fail("Size of ${input} is not 6.")
+ }
+ $output = chop($input)
+ if size($output) != 4 {
+ fail("Size of ${input} is not 4.")
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+
+ it 'should not fail on empty strings' do
+ pp = <<-EOS
+ $input = ""
+ $output = chop($input)
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/concat_spec.rb b/puppet/modules/stdlib/spec/acceptance/concat_spec.rb
new file mode 100755
index 00000000..06b649f1
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/concat_spec.rb
@@ -0,0 +1,40 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'concat function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'should concat one array to another' do
+ pp = <<-EOS
+ $output = concat(['1','2','3'],['4','5','6'])
+ validate_array($output)
+ if size($output) != 6 {
+ fail("${output} should have 6 elements.")
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+ it 'should concat arrays and primitives to array' do
+ pp = <<-EOS
+ $output = concat(['1','2','3'],'4','5','6',['7','8','9'])
+ validate_array($output)
+ if size($output) != 9 {
+ fail("${output} should have 9 elements.")
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+ it 'should concat multiple arrays to one' do
+ pp = <<-EOS
+ $output = concat(['1','2','3'],['4','5','6'],['7','8','9'])
+ validate_array($output)
+ if size($output) != 9 {
+ fail("${output} should have 9 elements.")
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/count_spec.rb b/puppet/modules/stdlib/spec/acceptance/count_spec.rb
new file mode 100755
index 00000000..fe7ca9dc
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/count_spec.rb
@@ -0,0 +1,30 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'count function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'should count elements in an array' do
+ pp = <<-EOS
+ $input = [1,2,3,4]
+ $output = count($input)
+ notify { "$output": }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: 4/)
+ end
+ end
+
+ it 'should count elements in an array that match a second argument' do
+ pp = <<-EOS
+ $input = [1,1,1,2]
+ $output = count($input, 1)
+ notify { "$output": }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: 3/)
+ end
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/deep_merge_spec.rb b/puppet/modules/stdlib/spec/acceptance/deep_merge_spec.rb
new file mode 100755
index 00000000..c0f9b126
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/deep_merge_spec.rb
@@ -0,0 +1,20 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'deep_merge function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'should deep merge two hashes' do
+ pp = <<-EOS
+ $hash1 = {'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } }
+ $hash2 = {'two' => 'dos', 'three' => { 'five' => 5 } }
+ $merged_hash = deep_merge($hash1, $hash2)
+
+ if $merged_hash != { 'one' => 1, 'two' => 'dos', 'three' => { 'four' => 4, 'five' => 5 } } {
+ fail("Hash was incorrectly merged.")
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/defined_with_params_spec.rb b/puppet/modules/stdlib/spec/acceptance/defined_with_params_spec.rb
new file mode 100755
index 00000000..fc544508
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/defined_with_params_spec.rb
@@ -0,0 +1,22 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'defined_with_params function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'should successfully notify' do
+ pp = <<-EOS
+ user { 'dan':
+ ensure => present,
+ }
+
+ if defined_with_params(User[dan], {'ensure' => 'present' }) {
+ notify { 'User defined with ensure=>present': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: User defined with ensure=>present/)
+ end
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/delete_at_spec.rb b/puppet/modules/stdlib/spec/acceptance/delete_at_spec.rb
new file mode 100755
index 00000000..db0c01f7
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/delete_at_spec.rb
@@ -0,0 +1,19 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'delete_at function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'should delete elements of the array' do
+ pp = <<-EOS
+ $output = delete_at(['a','b','c','b'], 1)
+ if $output == ['a','c','b'] {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/delete_spec.rb b/puppet/modules/stdlib/spec/acceptance/delete_spec.rb
new file mode 100755
index 00000000..a28604ce
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/delete_spec.rb
@@ -0,0 +1,19 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'delete function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'should delete elements of the array' do
+ pp = <<-EOS
+ $output = delete(['a','b','c','b'], 'b')
+ if $output == ['a','c'] {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/delete_undef_values_spec.rb b/puppet/modules/stdlib/spec/acceptance/delete_undef_values_spec.rb
new file mode 100755
index 00000000..b7eda192
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/delete_undef_values_spec.rb
@@ -0,0 +1,19 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'delete_undef_values function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'should delete elements of the array' do
+ pp = <<-EOS
+ $output = delete_undef_values({a=>'A', b=>'', c=>undef, d => false})
+ if $output == { a => 'A', b => '', d => false } {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/delete_values_spec.rb b/puppet/modules/stdlib/spec/acceptance/delete_values_spec.rb
new file mode 100755
index 00000000..6d2369c3
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/delete_values_spec.rb
@@ -0,0 +1,25 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'delete_values function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'should delete elements of the hash' do
+ pp = <<-EOS
+ $a = { 'a' => 'A', 'b' => 'B', 'B' => 'C', 'd' => 'B' }
+ $b = { 'a' => 'A', 'B' => 'C' }
+ $o = delete_values($a, 'B')
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles non-hash arguments'
+ it 'handles improper argument counts'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/difference_spec.rb b/puppet/modules/stdlib/spec/acceptance/difference_spec.rb
new file mode 100755
index 00000000..2fae5c43
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/difference_spec.rb
@@ -0,0 +1,26 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'difference function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'returns non-duplicates in the first array' do
+ pp = <<-EOS
+ $a = ['a','b','c']
+ $b = ['b','c','d']
+ $c = ['a']
+ $o = difference($a, $b)
+ if $o == $c {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles non-array arguments'
+ it 'handles improper argument counts'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/dirname_spec.rb b/puppet/modules/stdlib/spec/acceptance/dirname_spec.rb
new file mode 100755
index 00000000..97913ddb
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/dirname_spec.rb
@@ -0,0 +1,42 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'dirname function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ context 'absolute path' do
+ it 'returns the dirname' do
+ pp = <<-EOS
+ $a = '/path/to/a/file.txt'
+ $b = '/path/to/a'
+ $o = dirname($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ end
+ context 'relative path' do
+ it 'returns the dirname' do
+ pp = <<-EOS
+ $a = 'path/to/a/file.txt'
+ $b = 'path/to/a'
+ $o = dirname($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles improper argument counts'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/downcase_spec.rb b/puppet/modules/stdlib/spec/acceptance/downcase_spec.rb
new file mode 100755
index 00000000..bc4e7069
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/downcase_spec.rb
@@ -0,0 +1,39 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'downcase function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'returns the downcase' do
+ pp = <<-EOS
+ $a = 'AOEU'
+ $b = 'aoeu'
+ $o = downcase($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'doesn\'t affect lowercase words' do
+ pp = <<-EOS
+ $a = 'aoeu aoeu'
+ $b = 'aoeu aoeu'
+ $o = downcase($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles improper argument counts'
+ it 'handles non-strings'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/empty_spec.rb b/puppet/modules/stdlib/spec/acceptance/empty_spec.rb
new file mode 100755
index 00000000..8b46aacd
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/empty_spec.rb
@@ -0,0 +1,39 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'empty function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'recognizes empty strings' do
+ pp = <<-EOS
+ $a = ''
+ $b = true
+ $o = empty($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'recognizes non-empty strings' do
+ pp = <<-EOS
+ $a = 'aoeu'
+ $b = false
+ $o = empty($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles improper argument counts'
+ it 'handles non-strings'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/ensure_packages_spec.rb b/puppet/modules/stdlib/spec/acceptance/ensure_packages_spec.rb
new file mode 100755
index 00000000..aedcfb55
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/ensure_packages_spec.rb
@@ -0,0 +1,22 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'ensure_packages function', :unless => fact('osfamily') =~ /windows/i do
+ describe 'success' do
+ it 'ensure_packages a package' do
+ apply_manifest('package { "rake": ensure => absent, provider => "gem", }')
+ pp = <<-EOS
+ $a = "rake"
+ ensure_packages($a,{'provider' => 'gem'})
+ EOS
+
+ apply_manifest(pp, :expect_changes => true)
+ end
+ it 'ensures a package already declared'
+ it 'takes defaults arguments'
+ end
+ describe 'failure' do
+ it 'handles no arguments'
+ it 'handles non strings'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/ensure_resource_spec.rb b/puppet/modules/stdlib/spec/acceptance/ensure_resource_spec.rb
new file mode 100755
index 00000000..1cee53db
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/ensure_resource_spec.rb
@@ -0,0 +1,22 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'ensure_resource function', :unless => fact('osfamily') =~ /windows/i do
+ describe 'success' do
+ it 'ensure_resource a package' do
+ apply_manifest('package { "rake": ensure => absent, provider => "gem", }')
+ pp = <<-EOS
+ $a = "rake"
+ ensure_resource('package', $a, {'provider' => 'gem'})
+ EOS
+
+ apply_manifest(pp, :expect_changes => true)
+ end
+ it 'ensures a resource already declared'
+ it 'takes defaults arguments'
+ end
+ describe 'failure' do
+ it 'handles no arguments'
+ it 'handles non strings'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/flatten_spec.rb b/puppet/modules/stdlib/spec/acceptance/flatten_spec.rb
new file mode 100755
index 00000000..c4d66e04
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/flatten_spec.rb
@@ -0,0 +1,39 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'flatten function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'flattens arrays' do
+ pp = <<-EOS
+ $a = ["a","b",["c",["d","e"],"f","g"]]
+ $b = ["a","b","c","d","e","f","g"]
+ $o = flatten($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'does not affect flat arrays' do
+ pp = <<-EOS
+ $a = ["a","b","c","d","e","f","g"]
+ $b = ["a","b","c","d","e","f","g"]
+ $o = flatten($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles improper argument counts'
+ it 'handles non-strings'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/floor_spec.rb b/puppet/modules/stdlib/spec/acceptance/floor_spec.rb
new file mode 100755
index 00000000..0dcdad9c
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/floor_spec.rb
@@ -0,0 +1,39 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'floor function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'floors floats' do
+ pp = <<-EOS
+ $a = 12.8
+ $b = 12
+ $o = floor($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'floors integers' do
+ pp = <<-EOS
+ $a = 7
+ $b = 7
+ $o = floor($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles improper argument counts'
+ it 'handles non-numbers'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/fqdn_rotate_spec.rb b/puppet/modules/stdlib/spec/acceptance/fqdn_rotate_spec.rb
new file mode 100755
index 00000000..753068bf
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/fqdn_rotate_spec.rb
@@ -0,0 +1,47 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'fqdn_rotate function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ let(:facts_d) do
+ if fact('is_pe', '--puppet') == "true"
+ if fact('osfamily') =~ /windows/i
+ if fact('kernelmajversion').to_f < 6.0
+ 'C:/Documents and Settings/All Users/Application Data/PuppetLabs/facter/facts.d'
+ else
+ 'C:/ProgramData/PuppetLabs/facter/facts.d'
+ end
+ else
+ '/etc/puppetlabs/facter/facts.d'
+ end
+ else
+ '/etc/facter/facts.d'
+ end
+ end
+ after :each do
+ shell("if [ -f '#{facts_d}/fqdn.txt' ] ; then rm '#{facts_d}/fqdn.txt' ; fi")
+ end
+ before :each do
+ #No need to create on windows, PE creates by default
+ if fact('osfamily') !~ /windows/i
+ shell("mkdir -p '#{facts_d}'")
+ end
+ end
+ it 'fqdn_rotates floats' do
+ shell("echo fqdn=fakehost.localdomain > '#{facts_d}/fqdn.txt'")
+ pp = <<-EOS
+ $a = ['a','b','c','d']
+ $o = fqdn_rotate($a)
+ notice(inline_template('fqdn_rotate is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/fqdn_rotate is \["c", "d", "a", "b"\]/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles improper argument counts'
+ it 'handles non-numbers'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/get_module_path_spec.rb b/puppet/modules/stdlib/spec/acceptance/get_module_path_spec.rb
new file mode 100755
index 00000000..6ac690c1
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/get_module_path_spec.rb
@@ -0,0 +1,27 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'get_module_path function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'get_module_paths dne' do
+ pp = <<-EOS
+ $a = $::is_pe ? {
+ 'true' => '/etc/puppetlabs/puppet/modules/dne',
+ 'false' => '/etc/puppet/modules/dne',
+ }
+ $o = get_module_path('dne')
+ if $o == $a {
+ notify { 'output correct': }
+ } else {
+ notify { "failed; module path is '$o'": }
+ }
+ EOS
+
+ apply_manifest(pp, :expect_failures => true)
+ end
+ end
+ describe 'failure' do
+ it 'handles improper argument counts'
+ it 'handles non-numbers'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/getparam_spec.rb b/puppet/modules/stdlib/spec/acceptance/getparam_spec.rb
new file mode 100755
index 00000000..b1a677ec
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/getparam_spec.rb
@@ -0,0 +1,24 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'getparam function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'getparam a notify' do
+ pp = <<-EOS
+ notify { 'rspec':
+ message => 'custom rspec message',
+ }
+ $o = getparam(Notify['rspec'], 'message')
+ notice(inline_template('getparam is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/getparam is "custom rspec message"/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles no arguments'
+ it 'handles non strings'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/getvar_spec.rb b/puppet/modules/stdlib/spec/acceptance/getvar_spec.rb
new file mode 100755
index 00000000..333c467f
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/getvar_spec.rb
@@ -0,0 +1,26 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'getvar function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'getvars from classes' do
+ pp = <<-EOS
+ class a::data { $foo = 'aoeu' }
+ include a::data
+ $b = 'aoeu'
+ $o = getvar("a::data::foo")
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles improper argument counts'
+ it 'handles non-numbers'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/grep_spec.rb b/puppet/modules/stdlib/spec/acceptance/grep_spec.rb
new file mode 100755
index 00000000..b39d48ec
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/grep_spec.rb
@@ -0,0 +1,26 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'grep function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'greps arrays' do
+ pp = <<-EOS
+ $a = ['aaabbb','bbbccc','dddeee']
+ $b = 'bbb'
+ $c = ['aaabbb','bbbccc']
+ $o = grep($a,$b)
+ if $o == $c {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles improper argument counts'
+ it 'handles non-arrays'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/has_interface_with_spec.rb b/puppet/modules/stdlib/spec/acceptance/has_interface_with_spec.rb
new file mode 100755
index 00000000..95901930
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/has_interface_with_spec.rb
@@ -0,0 +1,54 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'has_interface_with function', :unless => ((UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem'))) or (fact('osfamily') == 'windows') or (fact('osfamily') == 'AIX')) do
+ describe 'success' do
+ it 'has_interface_with existing ipaddress' do
+ pp = <<-EOS
+ $a = $::ipaddress
+ $o = has_interface_with('ipaddress', $a)
+ notice(inline_template('has_interface_with is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/has_interface_with is true/)
+ end
+ end
+ it 'has_interface_with absent ipaddress' do
+ pp = <<-EOS
+ $a = '128.0.0.1'
+ $o = has_interface_with('ipaddress', $a)
+ notice(inline_template('has_interface_with is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/has_interface_with is false/)
+ end
+ end
+ it 'has_interface_with existing interface' do
+ pp = <<-EOS
+ if $osfamily == 'Solaris' or $osfamily == 'Darwin' {
+ $a = 'lo0'
+ }elsif $osfamily == 'windows' {
+ $a = $::kernelmajversion ? {
+ /6\.(2|3|4)/ => 'Ethernet0',
+ /6\.(0|1)/ => 'Local_Area_Connection',
+ /5\.(1|2)/ => undef, #Broken current in facter
+ }
+ }else {
+ $a = 'lo'
+ }
+ $o = has_interface_with($a)
+ notice(inline_template('has_interface_with is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/has_interface_with is true/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles no arguments'
+ it 'handles non strings'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/has_ip_address_spec.rb b/puppet/modules/stdlib/spec/acceptance/has_ip_address_spec.rb
new file mode 100755
index 00000000..149a10dc
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/has_ip_address_spec.rb
@@ -0,0 +1,33 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'has_ip_address function', :unless => ((UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem'))) or (fact('osfamily') == 'windows') or (fact('osfamily') == 'AIX')) do
+ describe 'success' do
+ it 'has_ip_address existing ipaddress' do
+ pp = <<-EOS
+ $a = '127.0.0.1'
+ $o = has_ip_address($a)
+ notice(inline_template('has_ip_address is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/has_ip_address is true/)
+ end
+ end
+ it 'has_ip_address absent ipaddress' do
+ pp = <<-EOS
+ $a = '128.0.0.1'
+ $o = has_ip_address($a)
+ notice(inline_template('has_ip_address is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/has_ip_address is false/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles no arguments'
+ it 'handles non strings'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/has_ip_network_spec.rb b/puppet/modules/stdlib/spec/acceptance/has_ip_network_spec.rb
new file mode 100755
index 00000000..7d2f34ed
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/has_ip_network_spec.rb
@@ -0,0 +1,33 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'has_ip_network function', :unless => ((UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem'))) or (fact('osfamily') == 'windows') or (fact('osfamily') == 'AIX')) do
+ describe 'success' do
+ it 'has_ip_network existing ipaddress' do
+ pp = <<-EOS
+ $a = '127.0.0.0'
+ $o = has_ip_network($a)
+ notice(inline_template('has_ip_network is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/has_ip_network is true/)
+ end
+ end
+ it 'has_ip_network absent ipaddress' do
+ pp = <<-EOS
+ $a = '128.0.0.0'
+ $o = has_ip_network($a)
+ notice(inline_template('has_ip_network is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/has_ip_network is false/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles no arguments'
+ it 'handles non strings'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/has_key_spec.rb b/puppet/modules/stdlib/spec/acceptance/has_key_spec.rb
new file mode 100755
index 00000000..c8557cbe
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/has_key_spec.rb
@@ -0,0 +1,41 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'has_key function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'has_keys in hashes' do
+ pp = <<-EOS
+ $a = { 'aaa' => 'bbb','bbb' => 'ccc','ddd' => 'eee' }
+ $b = 'bbb'
+ $c = true
+ $o = has_key($a,$b)
+ if $o == $c {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'has_keys not in hashes' do
+ pp = <<-EOS
+ $a = { 'aaa' => 'bbb','bbb' => 'ccc','ddd' => 'eee' }
+ $b = 'ccc'
+ $c = false
+ $o = has_key($a,$b)
+ if $o == $c {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles improper argument counts'
+ it 'handles non-hashes'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/hash_spec.rb b/puppet/modules/stdlib/spec/acceptance/hash_spec.rb
new file mode 100755
index 00000000..ed53834b
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/hash_spec.rb
@@ -0,0 +1,26 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'hash function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'hashs arrays' do
+ pp = <<-EOS
+ $a = ['aaa','bbb','bbb','ccc','ddd','eee']
+ $b = { 'aaa' => 'bbb', 'bbb' => 'ccc', 'ddd' => 'eee' }
+ $o = hash($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'handles odd-length arrays'
+ end
+ describe 'failure' do
+ it 'handles improper argument counts'
+ it 'handles non-arrays'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/intersection_spec.rb b/puppet/modules/stdlib/spec/acceptance/intersection_spec.rb
new file mode 100755
index 00000000..66b86529
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/intersection_spec.rb
@@ -0,0 +1,27 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'intersection function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'intersections arrays' do
+ pp = <<-EOS
+ $a = ['aaa','bbb','ccc']
+ $b = ['bbb','ccc','ddd','eee']
+ $c = ['bbb','ccc']
+ $o = intersection($a,$b)
+ if $o == $c {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'intersections empty arrays'
+ end
+ describe 'failure' do
+ it 'handles improper argument counts'
+ it 'handles non-arrays'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/is_array_spec.rb b/puppet/modules/stdlib/spec/acceptance/is_array_spec.rb
new file mode 100755
index 00000000..9c6bad73
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/is_array_spec.rb
@@ -0,0 +1,67 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'is_array function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'is_arrays arrays' do
+ pp = <<-EOS
+ $a = ['aaa','bbb','ccc']
+ $b = true
+ $o = is_array($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_arrays empty arrays' do
+ pp = <<-EOS
+ $a = []
+ $b = true
+ $o = is_array($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_arrays strings' do
+ pp = <<-EOS
+ $a = "aoeu"
+ $b = false
+ $o = is_array($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_arrays hashes' do
+ pp = <<-EOS
+ $a = {'aaa'=>'bbb'}
+ $b = false
+ $o = is_array($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles improper argument counts'
+ it 'handles non-arrays'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/is_bool_spec.rb b/puppet/modules/stdlib/spec/acceptance/is_bool_spec.rb
new file mode 100755
index 00000000..60079f95
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/is_bool_spec.rb
@@ -0,0 +1,81 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'is_bool function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'is_bools arrays' do
+ pp = <<-EOS
+ $a = ['aaa','bbb','ccc']
+ $b = false
+ $o = is_bool($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_bools true' do
+ pp = <<-EOS
+ $a = true
+ $b = true
+ $o = is_bool($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_bools false' do
+ pp = <<-EOS
+ $a = false
+ $b = true
+ $o = is_bool($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_bools strings' do
+ pp = <<-EOS
+ $a = "true"
+ $b = false
+ $o = is_bool($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_bools hashes' do
+ pp = <<-EOS
+ $a = {'aaa'=>'bbb'}
+ $b = false
+ $o = is_bool($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles improper argument counts'
+ it 'handles non-arrays'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/is_domain_name_spec.rb b/puppet/modules/stdlib/spec/acceptance/is_domain_name_spec.rb
new file mode 100755
index 00000000..e0f03fa8
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/is_domain_name_spec.rb
@@ -0,0 +1,83 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'is_domain_name function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'is_domain_names arrays' do
+ pp = <<-EOS
+ $a = ['aaa.com','bbb','ccc']
+ $o = is_domain_name($a)
+ notice(inline_template('is_domain_name is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/is_domain_name is false/)
+ end
+ end
+ it 'is_domain_names true' do
+ pp = <<-EOS
+ $a = true
+ $o = is_domain_name($a)
+ notice(inline_template('is_domain_name is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/is_domain_name is false/)
+ end
+ end
+ it 'is_domain_names false' do
+ pp = <<-EOS
+ $a = false
+ $o = is_domain_name($a)
+ notice(inline_template('is_domain_name is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/is_domain_name is false/)
+ end
+ end
+ it 'is_domain_names strings with hyphens' do
+ pp = <<-EOS
+ $a = "3foo-bar.2bar-fuzz.com"
+ $b = true
+ $o = is_domain_name($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_domain_names strings beginning with hyphens' do
+ pp = <<-EOS
+ $a = "-bar.2bar-fuzz.com"
+ $b = false
+ $o = is_domain_name($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_domain_names hashes' do
+ pp = <<-EOS
+ $a = {'aaa'=>'www.com'}
+ $o = is_domain_name($a)
+ notice(inline_template('is_domain_name is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/is_domain_name is false/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles improper argument counts'
+ it 'handles non-arrays'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/is_float_spec.rb b/puppet/modules/stdlib/spec/acceptance/is_float_spec.rb
new file mode 100755
index 00000000..338ba58d
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/is_float_spec.rb
@@ -0,0 +1,86 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'is_float function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'is_floats arrays' do
+ pp = <<-EOS
+ $a = ['aaa.com','bbb','ccc']
+ $o = is_float($a)
+ notice(inline_template('is_float is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/is_float is false/)
+ end
+ end
+ it 'is_floats true' do
+ pp = <<-EOS
+ $a = true
+ $o = is_float($a)
+ notice(inline_template('is_float is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/is_float is false/)
+ end
+ end
+ it 'is_floats strings' do
+ pp = <<-EOS
+ $a = "3.5"
+ $b = true
+ $o = is_float($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_floats floats' do
+ pp = <<-EOS
+ $a = 3.5
+ $b = true
+ $o = is_float($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_floats integers' do
+ pp = <<-EOS
+ $a = 3
+ $b = false
+ $o = is_float($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_floats hashes' do
+ pp = <<-EOS
+ $a = {'aaa'=>'www.com'}
+ $o = is_float($a)
+ notice(inline_template('is_float is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/is_float is false/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles improper argument counts'
+ it 'handles non-arrays'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/is_function_available_spec.rb b/puppet/modules/stdlib/spec/acceptance/is_function_available_spec.rb
new file mode 100755
index 00000000..2b5dd6d1
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/is_function_available_spec.rb
@@ -0,0 +1,58 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'is_function_available function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'is_function_availables arrays' do
+ pp = <<-EOS
+ $a = ['fail','include','require']
+ $o = is_function_available($a)
+ notice(inline_template('is_function_available is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/is_function_available is false/)
+ end
+ end
+ it 'is_function_availables true' do
+ pp = <<-EOS
+ $a = true
+ $o = is_function_available($a)
+ notice(inline_template('is_function_available is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/is_function_available is false/)
+ end
+ end
+ it 'is_function_availables strings' do
+ pp = <<-EOS
+ $a = "fail"
+ $b = true
+ $o = is_function_available($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_function_availables function_availables' do
+ pp = <<-EOS
+ $a = "is_function_available"
+ $o = is_function_available($a)
+ notice(inline_template('is_function_available is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/is_function_available is true/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles improper argument counts'
+ it 'handles non-arrays'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/is_hash_spec.rb b/puppet/modules/stdlib/spec/acceptance/is_hash_spec.rb
new file mode 100755
index 00000000..2ef310ab
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/is_hash_spec.rb
@@ -0,0 +1,63 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'is_hash function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'is_hashs arrays' do
+ pp = <<-EOS
+ $a = ['aaa','bbb','ccc']
+ $o = is_hash($a)
+ notice(inline_template('is_hash is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/is_hash is false/)
+ end
+ end
+ it 'is_hashs empty hashs' do
+ pp = <<-EOS
+ $a = {}
+ $b = true
+ $o = is_hash($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_hashs strings' do
+ pp = <<-EOS
+ $a = "aoeu"
+ $b = false
+ $o = is_hash($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_hashs hashes' do
+ pp = <<-EOS
+ $a = {'aaa'=>'bbb'}
+ $b = true
+ $o = is_hash($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles improper argument counts'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/is_integer_spec.rb b/puppet/modules/stdlib/spec/acceptance/is_integer_spec.rb
new file mode 100755
index 00000000..bf6902b9
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/is_integer_spec.rb
@@ -0,0 +1,95 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'is_integer function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'is_integers arrays' do
+ pp = <<-EOS
+ $a = ['aaa.com','bbb','ccc']
+ $b = false
+ $o = is_integer($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_integers true' do
+ pp = <<-EOS
+ $a = true
+ $b = false
+ $o = is_integer($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_integers strings' do
+ pp = <<-EOS
+ $a = "3"
+ $b = true
+ $o = is_integer($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_integers floats' do
+ pp = <<-EOS
+ $a = 3.5
+ $b = false
+ $o = is_integer($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_integers integers' do
+ pp = <<-EOS
+ $a = 3
+ $b = true
+ $o = is_integer($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_integers hashes' do
+ pp = <<-EOS
+ $a = {'aaa'=>'www.com'}
+ $b = false
+ $o = is_integer($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles improper argument counts'
+ it 'handles non-arrays'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/is_ip_address_spec.rb b/puppet/modules/stdlib/spec/acceptance/is_ip_address_spec.rb
new file mode 100755
index 00000000..ed7a8543
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/is_ip_address_spec.rb
@@ -0,0 +1,80 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'is_ip_address function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'is_ip_addresss ipv4' do
+ pp = <<-EOS
+ $a = '1.2.3.4'
+ $b = true
+ $o = is_ip_address($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_ip_addresss ipv6' do
+ pp = <<-EOS
+ $a = "fe80:0000:cd12:d123:e2f8:47ff:fe09:dd74"
+ $b = true
+ $o = is_ip_address($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_ip_addresss ipv6 compressed' do
+ pp = <<-EOS
+ $a = "fe00::1"
+ $b = true
+ $o = is_ip_address($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_ip_addresss strings' do
+ pp = <<-EOS
+ $a = "aoeu"
+ $b = false
+ $o = is_ip_address($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_ip_addresss ipv4 out of range' do
+ pp = <<-EOS
+ $a = '1.2.3.400'
+ $b = false
+ $o = is_ip_address($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles improper argument counts'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/is_mac_address_spec.rb b/puppet/modules/stdlib/spec/acceptance/is_mac_address_spec.rb
new file mode 100755
index 00000000..a2c892f4
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/is_mac_address_spec.rb
@@ -0,0 +1,38 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'is_mac_address function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'is_mac_addresss a mac' do
+ pp = <<-EOS
+ $a = '00:a0:1f:12:7f:a0'
+ $b = true
+ $o = is_mac_address($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_mac_addresss a mac out of range' do
+ pp = <<-EOS
+ $a = '00:a0:1f:12:7f:g0'
+ $b = false
+ $o = is_mac_address($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles improper argument counts'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/is_numeric_spec.rb b/puppet/modules/stdlib/spec/acceptance/is_numeric_spec.rb
new file mode 100755
index 00000000..21c89884
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/is_numeric_spec.rb
@@ -0,0 +1,95 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'is_numeric function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'is_numerics arrays' do
+ pp = <<-EOS
+ $a = ['aaa.com','bbb','ccc']
+ $b = false
+ $o = is_numeric($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_numerics true' do
+ pp = <<-EOS
+ $a = true
+ $b = false
+ $o = is_numeric($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_numerics strings' do
+ pp = <<-EOS
+ $a = "3"
+ $b = true
+ $o = is_numeric($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_numerics floats' do
+ pp = <<-EOS
+ $a = 3.5
+ $b = true
+ $o = is_numeric($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_numerics integers' do
+ pp = <<-EOS
+ $a = 3
+ $b = true
+ $o = is_numeric($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_numerics hashes' do
+ pp = <<-EOS
+ $a = {'aaa'=>'www.com'}
+ $b = false
+ $o = is_numeric($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles improper argument counts'
+ it 'handles non-arrays'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/is_string_spec.rb b/puppet/modules/stdlib/spec/acceptance/is_string_spec.rb
new file mode 100755
index 00000000..94d8e967
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/is_string_spec.rb
@@ -0,0 +1,102 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'is_string function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'is_strings arrays' do
+ pp = <<-EOS
+ $a = ['aaa.com','bbb','ccc']
+ $b = false
+ $o = is_string($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_strings true' do
+ pp = <<-EOS
+ $a = true
+ $b = false
+ $o = is_string($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_strings strings' do
+ pp = <<-EOS
+ $a = "aoeu"
+ $o = is_string($a)
+ notice(inline_template('is_string is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/is_string is true/)
+ end
+ end
+ it 'is_strings number strings' do
+ pp = <<-EOS
+ $a = "3"
+ $o = is_string($a)
+ notice(inline_template('is_string is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/is_string is false/)
+ end
+ end
+ it 'is_strings floats' do
+ pp = <<-EOS
+ $a = 3.5
+ $b = false
+ $o = is_string($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_strings integers' do
+ pp = <<-EOS
+ $a = 3
+ $b = false
+ $o = is_string($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'is_strings hashes' do
+ pp = <<-EOS
+ $a = {'aaa'=>'www.com'}
+ $b = false
+ $o = is_string($a)
+ if $o == $b {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles improper argument counts'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/join_keys_to_values_spec.rb b/puppet/modules/stdlib/spec/acceptance/join_keys_to_values_spec.rb
new file mode 100755
index 00000000..70493fd5
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/join_keys_to_values_spec.rb
@@ -0,0 +1,24 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'join_keys_to_values function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'join_keys_to_valuess hashes' do
+ pp = <<-EOS
+ $a = {'aaa'=>'bbb','ccc'=>'ddd'}
+ $b = ':'
+ $o = join_keys_to_values($a,$b)
+ notice(inline_template('join_keys_to_values is <%= @o.sort.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/join_keys_to_values is \["aaa:bbb", "ccc:ddd"\]/)
+ end
+ end
+ it 'handles non hashes'
+ it 'handles empty hashes'
+ end
+ describe 'failure' do
+ it 'handles improper argument counts'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/join_spec.rb b/puppet/modules/stdlib/spec/acceptance/join_spec.rb
new file mode 100755
index 00000000..5397ce2c
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/join_spec.rb
@@ -0,0 +1,26 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'join function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'joins arrays' do
+ pp = <<-EOS
+ $a = ['aaa','bbb','ccc']
+ $b = ':'
+ $c = 'aaa:bbb:ccc'
+ $o = join($a,$b)
+ if $o == $c {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ it 'handles non arrays'
+ end
+ describe 'failure' do
+ it 'handles improper argument counts'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/keys_spec.rb b/puppet/modules/stdlib/spec/acceptance/keys_spec.rb
new file mode 100755
index 00000000..176918e9
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/keys_spec.rb
@@ -0,0 +1,23 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'keys function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'keyss hashes' do
+ pp = <<-EOS
+ $a = {'aaa'=>'bbb','ccc'=>'ddd'}
+ $o = keys($a)
+ notice(inline_template('keys is <%= @o.sort.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/keys is \["aaa", "ccc"\]/)
+ end
+ end
+ it 'handles non hashes'
+ it 'handles empty hashes'
+ end
+ describe 'failure' do
+ it 'handles improper argument counts'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/loadyaml_spec.rb b/puppet/modules/stdlib/spec/acceptance/loadyaml_spec.rb
new file mode 100644
index 00000000..1e910a97
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/loadyaml_spec.rb
@@ -0,0 +1,33 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+tmpdir = default.tmpdir('stdlib')
+
+describe 'loadyaml function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'loadyamls array of values' do
+ shell("echo '---
+ aaa: 1
+ bbb: 2
+ ccc: 3
+ ddd: 4' > #{tmpdir}/testyaml.yaml")
+ pp = <<-EOS
+ $o = loadyaml('#{tmpdir}/testyaml.yaml')
+ notice(inline_template('loadyaml[aaa] is <%= @o["aaa"].inspect %>'))
+ notice(inline_template('loadyaml[bbb] is <%= @o["bbb"].inspect %>'))
+ notice(inline_template('loadyaml[ccc] is <%= @o["ccc"].inspect %>'))
+ notice(inline_template('loadyaml[ddd] is <%= @o["ddd"].inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/loadyaml\[aaa\] is 1/)
+ expect(r.stdout).to match(/loadyaml\[bbb\] is 2/)
+ expect(r.stdout).to match(/loadyaml\[ccc\] is 3/)
+ expect(r.stdout).to match(/loadyaml\[ddd\] is 4/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'fails with no arguments'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/lstrip_spec.rb b/puppet/modules/stdlib/spec/acceptance/lstrip_spec.rb
new file mode 100755
index 00000000..3dc952fb
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/lstrip_spec.rb
@@ -0,0 +1,34 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'lstrip function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'lstrips arrays' do
+ pp = <<-EOS
+ $a = [" the "," public "," art","galleries "]
+ # Anagram: Large picture halls, I bet
+ $o = lstrip($a)
+ notice(inline_template('lstrip is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/lstrip is \["the ", "public ", "art", "galleries "\]/)
+ end
+ end
+ it 'lstrips strings' do
+ pp = <<-EOS
+ $a = " blowzy night-frumps vex'd jack q "
+ $o = lstrip($a)
+ notice(inline_template('lstrip is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/lstrip is "blowzy night-frumps vex'd jack q "/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles no arguments'
+ it 'handles non strings or arrays'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/max_spec.rb b/puppet/modules/stdlib/spec/acceptance/max_spec.rb
new file mode 100755
index 00000000..f04e3d28
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/max_spec.rb
@@ -0,0 +1,20 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'max function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'maxs arrays' do
+ pp = <<-EOS
+ $o = max("the","public","art","galleries")
+ notice(inline_template('max is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/max is "the"/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles no arguments'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/member_spec.rb b/puppet/modules/stdlib/spec/acceptance/member_spec.rb
new file mode 100755
index 00000000..fe75a078
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/member_spec.rb
@@ -0,0 +1,54 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'member function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ shared_examples 'item found' do
+ it 'should output correctly' do
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ end
+ describe 'success' do
+ it 'members arrays' do
+ pp = <<-EOS
+ $a = ['aaa','bbb','ccc']
+ $b = 'ccc'
+ $c = true
+ $o = member($a,$b)
+ if $o == $c {
+ notify { 'output correct': }
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/Notice: output correct/)
+ end
+ end
+ describe 'members array of integers' do
+ it_should_behave_like 'item found' do
+ let(:pp) { <<-EOS
+ if member( [1,2,3,4], 4 ){
+ notify { 'output correct': }
+ }
+ EOS
+ }
+ end
+ end
+ describe 'members of mixed array' do
+ it_should_behave_like 'item found' do
+ let(:pp) { <<-EOS
+ if member( ['a','4',3], 'a' ){
+ notify { 'output correct': }
+}
+ EOS
+ }
+ end
+ end
+ it 'members arrays without members'
+ end
+
+ describe 'failure' do
+ it 'handles improper argument counts'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/merge_spec.rb b/puppet/modules/stdlib/spec/acceptance/merge_spec.rb
new file mode 100755
index 00000000..227b9942
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/merge_spec.rb
@@ -0,0 +1,23 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'merge function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'should merge two hashes' do
+ pp = <<-EOS
+ $a = {'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } }
+ $b = {'two' => 'dos', 'three' => { 'five' => 5 } }
+ $o = merge($a, $b)
+ notice(inline_template('merge[one] is <%= @o["one"].inspect %>'))
+ notice(inline_template('merge[two] is <%= @o["two"].inspect %>'))
+ notice(inline_template('merge[three] is <%= @o["three"].inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/merge\[one\] is ("1"|1)/)
+ expect(r.stdout).to match(/merge\[two\] is "dos"/)
+ expect(r.stdout).to match(/merge\[three\] is {"five"=>("5"|5)}/)
+ end
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/min_spec.rb b/puppet/modules/stdlib/spec/acceptance/min_spec.rb
new file mode 100755
index 00000000..509092d3
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/min_spec.rb
@@ -0,0 +1,20 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'min function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'mins arrays' do
+ pp = <<-EOS
+ $o = min("the","public","art","galleries")
+ notice(inline_template('min is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/min is "art"/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles no arguments'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/nodesets/centos-59-x64.yml b/puppet/modules/stdlib/spec/acceptance/nodesets/centos-59-x64.yml
new file mode 100644
index 00000000..2ad90b86
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/nodesets/centos-59-x64.yml
@@ -0,0 +1,10 @@
+HOSTS:
+ centos-59-x64:
+ roles:
+ - master
+ platform: el-5-x86_64
+ box : centos-59-x64-vbox4210-nocm
+ box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-59-x64-vbox4210-nocm.box
+ hypervisor : vagrant
+CONFIG:
+ type: git
diff --git a/puppet/modules/stdlib/spec/acceptance/nodesets/centos-6-vcloud.yml b/puppet/modules/stdlib/spec/acceptance/nodesets/centos-6-vcloud.yml
new file mode 100644
index 00000000..ca9c1d32
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/nodesets/centos-6-vcloud.yml
@@ -0,0 +1,15 @@
+HOSTS:
+ 'centos-6-vcloud':
+ roles:
+ - master
+ platform: el-6-x86_64
+ hypervisor: vcloud
+ template: centos-6-x86_64
+CONFIG:
+ type: foss
+ ssh:
+ keys: "~/.ssh/id_rsa-acceptance"
+ datastore: instance0
+ folder: Delivery/Quality Assurance/Enterprise/Dynamic
+ resourcepool: delivery/Quality Assurance/Enterprise/Dynamic
+ pooling_api: http://vcloud.delivery.puppetlabs.net/
diff --git a/puppet/modules/stdlib/spec/acceptance/nodesets/centos-64-x64-pe.yml b/puppet/modules/stdlib/spec/acceptance/nodesets/centos-64-x64-pe.yml
new file mode 100644
index 00000000..7d9242f1
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/nodesets/centos-64-x64-pe.yml
@@ -0,0 +1,12 @@
+HOSTS:
+ centos-64-x64:
+ roles:
+ - master
+ - database
+ - dashboard
+ platform: el-6-x86_64
+ box : centos-64-x64-vbox4210-nocm
+ box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-64-x64-vbox4210-nocm.box
+ hypervisor : vagrant
+CONFIG:
+ type: pe
diff --git a/puppet/modules/stdlib/spec/acceptance/nodesets/centos-64-x64.yml b/puppet/modules/stdlib/spec/acceptance/nodesets/centos-64-x64.yml
new file mode 100644
index 00000000..05540ed8
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/nodesets/centos-64-x64.yml
@@ -0,0 +1,10 @@
+HOSTS:
+ centos-64-x64:
+ roles:
+ - master
+ platform: el-6-x86_64
+ box : centos-64-x64-vbox4210-nocm
+ box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-64-x64-vbox4210-nocm.box
+ hypervisor : vagrant
+CONFIG:
+ type: foss
diff --git a/puppet/modules/stdlib/spec/acceptance/nodesets/centos-65-x64.yml b/puppet/modules/stdlib/spec/acceptance/nodesets/centos-65-x64.yml
new file mode 100644
index 00000000..4e2cb809
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/nodesets/centos-65-x64.yml
@@ -0,0 +1,10 @@
+HOSTS:
+ centos-65-x64:
+ roles:
+ - master
+ platform: el-6-x86_64
+ box : centos-65-x64-vbox436-nocm
+ box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-65-x64-virtualbox-nocm.box
+ hypervisor : vagrant
+CONFIG:
+ type: foss
diff --git a/puppet/modules/stdlib/spec/acceptance/nodesets/default.yml b/puppet/modules/stdlib/spec/acceptance/nodesets/default.yml
new file mode 100644
index 00000000..4e2cb809
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/nodesets/default.yml
@@ -0,0 +1,10 @@
+HOSTS:
+ centos-65-x64:
+ roles:
+ - master
+ platform: el-6-x86_64
+ box : centos-65-x64-vbox436-nocm
+ box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-65-x64-virtualbox-nocm.box
+ hypervisor : vagrant
+CONFIG:
+ type: foss
diff --git a/puppet/modules/stdlib/spec/acceptance/nodesets/fedora-18-x64.yml b/puppet/modules/stdlib/spec/acceptance/nodesets/fedora-18-x64.yml
new file mode 100644
index 00000000..13616498
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/nodesets/fedora-18-x64.yml
@@ -0,0 +1,10 @@
+HOSTS:
+ fedora-18-x64:
+ roles:
+ - master
+ platform: fedora-18-x86_64
+ box : fedora-18-x64-vbox4210-nocm
+ box_url : http://puppet-vagrant-boxes.puppetlabs.com/fedora-18-x64-vbox4210-nocm.box
+ hypervisor : vagrant
+CONFIG:
+ type: foss
diff --git a/puppet/modules/stdlib/spec/acceptance/nodesets/sles-11-x64.yml b/puppet/modules/stdlib/spec/acceptance/nodesets/sles-11-x64.yml
new file mode 100644
index 00000000..41abe213
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/nodesets/sles-11-x64.yml
@@ -0,0 +1,10 @@
+HOSTS:
+ sles-11-x64.local:
+ roles:
+ - master
+ platform: sles-11-x64
+ box : sles-11sp1-x64-vbox4210-nocm
+ box_url : http://puppet-vagrant-boxes.puppetlabs.com/sles-11sp1-x64-vbox4210-nocm.box
+ hypervisor : vagrant
+CONFIG:
+ type: foss
diff --git a/puppet/modules/stdlib/spec/acceptance/nodesets/ubuntu-server-10044-x64.yml b/puppet/modules/stdlib/spec/acceptance/nodesets/ubuntu-server-10044-x64.yml
new file mode 100644
index 00000000..5ca1514e
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/nodesets/ubuntu-server-10044-x64.yml
@@ -0,0 +1,10 @@
+HOSTS:
+ ubuntu-server-10044-x64:
+ roles:
+ - master
+ platform: ubuntu-10.04-amd64
+ box : ubuntu-server-10044-x64-vbox4210-nocm
+ box_url : http://puppet-vagrant-boxes.puppetlabs.com/ubuntu-server-10044-x64-vbox4210-nocm.box
+ hypervisor : vagrant
+CONFIG:
+ type: foss
diff --git a/puppet/modules/stdlib/spec/acceptance/nodesets/ubuntu-server-12042-x64.yml b/puppet/modules/stdlib/spec/acceptance/nodesets/ubuntu-server-12042-x64.yml
new file mode 100644
index 00000000..d065b304
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/nodesets/ubuntu-server-12042-x64.yml
@@ -0,0 +1,10 @@
+HOSTS:
+ ubuntu-server-12042-x64:
+ roles:
+ - master
+ platform: ubuntu-12.04-amd64
+ box : ubuntu-server-12042-x64-vbox4210-nocm
+ box_url : http://puppet-vagrant-boxes.puppetlabs.com/ubuntu-server-12042-x64-vbox4210-nocm.box
+ hypervisor : vagrant
+CONFIG:
+ type: foss
diff --git a/puppet/modules/stdlib/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml b/puppet/modules/stdlib/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml
new file mode 100644
index 00000000..cba1cd04
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml
@@ -0,0 +1,11 @@
+HOSTS:
+ ubuntu-server-1404-x64:
+ roles:
+ - master
+ platform: ubuntu-14.04-amd64
+ box : puppetlabs/ubuntu-14.04-64-nocm
+ box_url : https://vagrantcloud.com/puppetlabs/ubuntu-14.04-64-nocm
+ hypervisor : vagrant
+CONFIG:
+ log_level : debug
+ type: git
diff --git a/puppet/modules/stdlib/spec/acceptance/nodesets/windows-2003-i386.yml b/puppet/modules/stdlib/spec/acceptance/nodesets/windows-2003-i386.yml
new file mode 100644
index 00000000..47dadbd5
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/nodesets/windows-2003-i386.yml
@@ -0,0 +1,26 @@
+HOSTS:
+ ubuntu1204:
+ roles:
+ - master
+ - database
+ - dashboard
+ platform: ubuntu-12.04-amd64
+ template: ubuntu-1204-x86_64
+ hypervisor: vcloud
+ win2003_i386:
+ roles:
+ - agent
+ - default
+ platform: windows-2003-i386
+ template: win-2003-i386
+ hypervisor: vcloud
+CONFIG:
+ nfs_server: none
+ ssh:
+ keys: "~/.ssh/id_rsa-acceptance"
+ consoleport: 443
+ datastore: instance0
+ folder: Delivery/Quality Assurance/Enterprise/Dynamic
+ resourcepool: delivery/Quality Assurance/Enterprise/Dynamic
+ pooling_api: http://vcloud.delivery.puppetlabs.net/
+ pe_dir: http://neptune.puppetlabs.lan/3.2/ci-ready/
diff --git a/puppet/modules/stdlib/spec/acceptance/nodesets/windows-2003-x86_64.yml b/puppet/modules/stdlib/spec/acceptance/nodesets/windows-2003-x86_64.yml
new file mode 100644
index 00000000..6a884bc9
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/nodesets/windows-2003-x86_64.yml
@@ -0,0 +1,26 @@
+HOSTS:
+ ubuntu1204:
+ roles:
+ - master
+ - database
+ - dashboard
+ platform: ubuntu-12.04-amd64
+ template: ubuntu-1204-x86_64
+ hypervisor: vcloud
+ win2003_x86_64:
+ roles:
+ - agent
+ - default
+ platform: windows-2003-x86_64
+ template: win-2003-x86_64
+ hypervisor: vcloud
+CONFIG:
+ nfs_server: none
+ ssh:
+ keys: "~/.ssh/id_rsa-acceptance"
+ consoleport: 443
+ datastore: instance0
+ folder: Delivery/Quality Assurance/Enterprise/Dynamic
+ resourcepool: delivery/Quality Assurance/Enterprise/Dynamic
+ pooling_api: http://vcloud.delivery.puppetlabs.net/
+ pe_dir: http://neptune.puppetlabs.lan/3.2/ci-ready/
diff --git a/puppet/modules/stdlib/spec/acceptance/nodesets/windows-2008-x86_64.yml b/puppet/modules/stdlib/spec/acceptance/nodesets/windows-2008-x86_64.yml
new file mode 100644
index 00000000..ae3c11dd
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/nodesets/windows-2008-x86_64.yml
@@ -0,0 +1,26 @@
+HOSTS:
+ ubuntu1204:
+ roles:
+ - master
+ - database
+ - dashboard
+ platform: ubuntu-12.04-amd64
+ template: ubuntu-1204-x86_64
+ hypervisor: vcloud
+ win2008_x86_64:
+ roles:
+ - agent
+ - default
+ platform: windows-2008-x86_64
+ template: win-2008-x86_64
+ hypervisor: vcloud
+CONFIG:
+ nfs_server: none
+ ssh:
+ keys: "~/.ssh/id_rsa-acceptance"
+ consoleport: 443
+ datastore: instance0
+ folder: Delivery/Quality Assurance/Enterprise/Dynamic
+ resourcepool: delivery/Quality Assurance/Enterprise/Dynamic
+ pooling_api: http://vcloud.delivery.puppetlabs.net/
+ pe_dir: http://neptune.puppetlabs.lan/3.2/ci-ready/
diff --git a/puppet/modules/stdlib/spec/acceptance/nodesets/windows-2008r2-x86_64.yml b/puppet/modules/stdlib/spec/acceptance/nodesets/windows-2008r2-x86_64.yml
new file mode 100644
index 00000000..63923ac1
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/nodesets/windows-2008r2-x86_64.yml
@@ -0,0 +1,26 @@
+HOSTS:
+ ubuntu1204:
+ roles:
+ - master
+ - database
+ - dashboard
+ platform: ubuntu-12.04-amd64
+ template: ubuntu-1204-x86_64
+ hypervisor: vcloud
+ win2008r2:
+ roles:
+ - agent
+ - default
+ platform: windows-2008r2-x86_64
+ template: win-2008r2-x86_64
+ hypervisor: vcloud
+CONFIG:
+ nfs_server: none
+ ssh:
+ keys: "~/.ssh/id_rsa-acceptance"
+ consoleport: 443
+ datastore: instance0
+ folder: Delivery/Quality Assurance/Enterprise/Dynamic
+ resourcepool: delivery/Quality Assurance/Enterprise/Dynamic
+ pooling_api: http://vcloud.delivery.puppetlabs.net/
+ pe_dir: http://neptune.puppetlabs.lan/3.2/ci-ready/
diff --git a/puppet/modules/stdlib/spec/acceptance/nodesets/windows-2012-x86_64.yml b/puppet/modules/stdlib/spec/acceptance/nodesets/windows-2012-x86_64.yml
new file mode 100644
index 00000000..eaa4eca9
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/nodesets/windows-2012-x86_64.yml
@@ -0,0 +1,26 @@
+HOSTS:
+ ubuntu1204:
+ roles:
+ - master
+ - database
+ - dashboard
+ platform: ubuntu-12.04-amd64
+ template: ubuntu-1204-x86_64
+ hypervisor: vcloud
+ win2012:
+ roles:
+ - agent
+ - default
+ platform: windows-2012-x86_64
+ template: win-2012-x86_64
+ hypervisor: vcloud
+CONFIG:
+ nfs_server: none
+ ssh:
+ keys: "~/.ssh/id_rsa-acceptance"
+ consoleport: 443
+ datastore: instance0
+ folder: Delivery/Quality Assurance/Enterprise/Dynamic
+ resourcepool: delivery/Quality Assurance/Enterprise/Dynamic
+ pooling_api: http://vcloud.delivery.puppetlabs.net/
+ pe_dir: http://neptune.puppetlabs.lan/3.2/ci-ready/
diff --git a/puppet/modules/stdlib/spec/acceptance/nodesets/windows-2012r2-x86_64.yml b/puppet/modules/stdlib/spec/acceptance/nodesets/windows-2012r2-x86_64.yml
new file mode 100644
index 00000000..1f0ea97c
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/nodesets/windows-2012r2-x86_64.yml
@@ -0,0 +1,26 @@
+HOSTS:
+ ubuntu1204:
+ roles:
+ - master
+ - database
+ - dashboard
+ platform: ubuntu-12.04-amd64
+ template: ubuntu-1204-x86_64
+ hypervisor: vcloud
+ win2012r2:
+ roles:
+ - agent
+ - default
+ platform: windows-2012r2-x86_64
+ template: win-2012r2-x86_64
+ hypervisor: vcloud
+CONFIG:
+ nfs_server: none
+ ssh:
+ keys: "~/.ssh/id_rsa-acceptance"
+ consoleport: 443
+ datastore: instance0
+ folder: Delivery/Quality Assurance/Enterprise/Dynamic
+ resourcepool: delivery/Quality Assurance/Enterprise/Dynamic
+ pooling_api: http://vcloud.delivery.puppetlabs.net/
+ pe_dir: http://neptune.puppetlabs.lan/3.2/ci-ready/
diff --git a/puppet/modules/stdlib/spec/acceptance/num2bool_spec.rb b/puppet/modules/stdlib/spec/acceptance/num2bool_spec.rb
new file mode 100755
index 00000000..1d99ba02
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/num2bool_spec.rb
@@ -0,0 +1,76 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'num2bool function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'bools positive numbers and numeric strings as true' do
+ pp = <<-EOS
+ $a = 1
+ $b = "1"
+ $c = "50"
+ $ao = num2bool($a)
+ $bo = num2bool($b)
+ $co = num2bool($c)
+ notice(inline_template('a is <%= @ao.inspect %>'))
+ notice(inline_template('b is <%= @bo.inspect %>'))
+ notice(inline_template('c is <%= @co.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/a is true/)
+ expect(r.stdout).to match(/b is true/)
+ expect(r.stdout).to match(/c is true/)
+ end
+ end
+ it 'bools negative numbers as false' do
+ pp = <<-EOS
+ $a = 0
+ $b = -0.1
+ $c = ["-50","1"]
+ $ao = num2bool($a)
+ $bo = num2bool($b)
+ $co = num2bool($c)
+ notice(inline_template('a is <%= @ao.inspect %>'))
+ notice(inline_template('b is <%= @bo.inspect %>'))
+ notice(inline_template('c is <%= @co.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/a is false/)
+ expect(r.stdout).to match(/b is false/)
+ expect(r.stdout).to match(/c is false/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'fails on words' do
+ pp = <<-EOS
+ $a = "a"
+ $ao = num2bool($a)
+ notice(inline_template('a is <%= @ao.inspect %>'))
+ EOS
+ expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/not look like a number/)
+ end
+
+ it 'fails on numberwords' do
+ pp = <<-EOS
+ $b = "1b"
+ $bo = num2bool($b)
+ notice(inline_template('b is <%= @bo.inspect %>'))
+ EOS
+ expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/not look like a number/)
+
+ end
+
+ it 'fails on non-numeric/strings' do
+ pending "The function will call .to_s.to_i on anything not a Numeric or
+ String, and results in 0. Is this intended?"
+ pp = <<-EOS
+ $c = {"c" => "-50"}
+ $co = num2bool($c)
+ notice(inline_template('c is <%= @co.inspect %>'))
+ EOS
+ expect(apply_manifest(ppc :expect_failures => true).stderr).to match(/Unable to parse/)
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/parsejson_spec.rb b/puppet/modules/stdlib/spec/acceptance/parsejson_spec.rb
new file mode 100755
index 00000000..50978102
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/parsejson_spec.rb
@@ -0,0 +1,34 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'parsejson function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'parses valid json' do
+ pp = <<-EOS
+ $a = '{"hunter": "washere", "tests": "passing"}'
+ $ao = parsejson($a)
+ $tests = $ao['tests']
+ notice(inline_template('tests are <%= @tests.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/tests are "passing"/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'raises error on incorrect json' do
+ pp = <<-EOS
+ $a = '{"hunter": "washere", "tests": "passing",}'
+ $ao = parsejson($a)
+ notice(inline_template('a is <%= @ao.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :expect_failures => true) do |r|
+ expect(r.stderr).to match(/expected next name/)
+ end
+ end
+
+ it 'raises error on incorrect number of arguments'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/parseyaml_spec.rb b/puppet/modules/stdlib/spec/acceptance/parseyaml_spec.rb
new file mode 100755
index 00000000..5819837c
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/parseyaml_spec.rb
@@ -0,0 +1,35 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'parseyaml function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'parses valid yaml' do
+ pp = <<-EOS
+ $a = "---\nhunter: washere\ntests: passing\n"
+ $o = parseyaml($a)
+ $tests = $o['tests']
+ notice(inline_template('tests are <%= @tests.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/tests are "passing"/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'raises error on incorrect yaml' do
+ pp = <<-EOS
+ $a = "---\nhunter: washere\ntests: passing\n:"
+ $o = parseyaml($a)
+ $tests = $o['tests']
+ notice(inline_template('tests are <%= @tests.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :expect_failures => true) do |r|
+ expect(r.stderr).to match(/(syntax error|did not find expected key)/)
+ end
+ end
+
+ it 'raises error on incorrect number of arguments'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/pick_default_spec.rb b/puppet/modules/stdlib/spec/acceptance/pick_default_spec.rb
new file mode 100755
index 00000000..a663f54e
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/pick_default_spec.rb
@@ -0,0 +1,54 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'pick_default function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'pick_defaults a default value' do
+ pp = <<-EOS
+ $a = undef
+ $o = pick_default($a, 'default')
+ notice(inline_template('picked is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/picked is "default"/)
+ end
+ end
+ it 'pick_defaults with no value' do
+ pp = <<-EOS
+ $a = undef
+ $b = undef
+ $o = pick_default($a,$b)
+ notice(inline_template('picked is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/picked is ""/)
+ end
+ end
+ it 'pick_defaults the first set value' do
+ pp = <<-EOS
+ $a = "something"
+ $b = "long"
+ $o = pick_default($a, $b, 'default')
+ notice(inline_template('picked is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/picked is "something"/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'raises error with no values' do
+ pp = <<-EOS
+ $o = pick_default()
+ notice(inline_template('picked is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :expect_failures => true) do |r|
+ expect(r.stderr).to match(/Must receive at least one argument/)
+ end
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/pick_spec.rb b/puppet/modules/stdlib/spec/acceptance/pick_spec.rb
new file mode 100755
index 00000000..46cf63f2
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/pick_spec.rb
@@ -0,0 +1,44 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'pick function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'picks a default value' do
+ pp = <<-EOS
+ $a = undef
+ $o = pick($a, 'default')
+ notice(inline_template('picked is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/picked is "default"/)
+ end
+ end
+ it 'picks the first set value' do
+ pp = <<-EOS
+ $a = "something"
+ $b = "long"
+ $o = pick($a, $b, 'default')
+ notice(inline_template('picked is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/picked is "something"/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'raises error with all undef values' do
+ pp = <<-EOS
+ $a = undef
+ $b = undef
+ $o = pick($a, $b)
+ notice(inline_template('picked is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :expect_failures => true) do |r|
+ expect(r.stderr).to match(/must receive at least one non empty value/)
+ end
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/prefix_spec.rb b/puppet/modules/stdlib/spec/acceptance/prefix_spec.rb
new file mode 100755
index 00000000..de55530e
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/prefix_spec.rb
@@ -0,0 +1,42 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'prefix function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'prefixes array of values' do
+ pp = <<-EOS
+ $o = prefix(['a','b','c'],'p')
+ notice(inline_template('prefix is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/prefix is \["pa", "pb", "pc"\]/)
+ end
+ end
+ it 'prefixs with empty array' do
+ pp = <<-EOS
+ $o = prefix([],'p')
+ notice(inline_template('prefix is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/prefix is \[\]/)
+ end
+ end
+ it 'prefixs array of values with undef' do
+ pp = <<-EOS
+ $o = prefix(['a','b','c'], undef)
+ notice(inline_template('prefix is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/prefix is \["a", "b", "c"\]/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'fails with no arguments'
+ it 'fails when first argument is not array'
+ it 'fails when second argument is not string'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/range_spec.rb b/puppet/modules/stdlib/spec/acceptance/range_spec.rb
new file mode 100755
index 00000000..a3ccd339
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/range_spec.rb
@@ -0,0 +1,36 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'range function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'ranges letters' do
+ pp = <<-EOS
+ $o = range('a','d')
+ notice(inline_template('range is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/range is \["a", "b", "c", "d"\]/)
+ end
+ end
+ it 'ranges letters with a step' do
+ pp = <<-EOS
+ $o = range('a','d', '2')
+ notice(inline_template('range is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/range is \["a", "c"\]/)
+ end
+ end
+ it 'ranges letters with a negative step'
+ it 'ranges numbers'
+ it 'ranges numbers with a step'
+ it 'ranges numbers with a negative step'
+ it 'ranges numeric strings'
+ it 'ranges zero padded numbers'
+ end
+ describe 'failure' do
+ it 'fails with no arguments'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/reject_spec.rb b/puppet/modules/stdlib/spec/acceptance/reject_spec.rb
new file mode 100755
index 00000000..7f16a008
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/reject_spec.rb
@@ -0,0 +1,42 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'reject function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'rejects array of values' do
+ pp = <<-EOS
+ $o = reject(['aaa','bbb','ccc','aaaddd'], 'aaa')
+ notice(inline_template('reject is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/reject is \["bbb", "ccc"\]/)
+ end
+ end
+ it 'rejects with empty array' do
+ pp = <<-EOS
+ $o = reject([],'aaa')
+ notice(inline_template('reject is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/reject is \[\]/)
+ end
+ end
+ it 'rejects array of values with undef' do
+ pp = <<-EOS
+ $o = reject(['aaa','bbb','ccc','aaaddd'], undef)
+ notice(inline_template('reject is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/reject is \[\]/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'fails with no arguments'
+ it 'fails when first argument is not array'
+ it 'fails when second argument is not string'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/reverse_spec.rb b/puppet/modules/stdlib/spec/acceptance/reverse_spec.rb
new file mode 100755
index 00000000..c3f01567
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/reverse_spec.rb
@@ -0,0 +1,23 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'reverse function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'reverses strings' do
+ pp = <<-EOS
+ $a = "the public art galleries"
+ # Anagram: Large picture halls, I bet
+ $o = reverse($a)
+ notice(inline_template('reverse is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/reverse is "seirellag tra cilbup eht"/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles no arguments'
+ it 'handles non strings or arrays'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/rstrip_spec.rb b/puppet/modules/stdlib/spec/acceptance/rstrip_spec.rb
new file mode 100755
index 00000000..b57a8b04
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/rstrip_spec.rb
@@ -0,0 +1,34 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'rstrip function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'rstrips arrays' do
+ pp = <<-EOS
+ $a = [" the "," public "," art","galleries "]
+ # Anagram: Large picture halls, I bet
+ $o = rstrip($a)
+ notice(inline_template('rstrip is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/rstrip is \[" the", " public", " art", "galleries"\]/)
+ end
+ end
+ it 'rstrips strings' do
+ pp = <<-EOS
+ $a = " blowzy night-frumps vex'd jack q "
+ $o = rstrip($a)
+ notice(inline_template('rstrip is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/rstrip is " blowzy night-frumps vex'd jack q"/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles no arguments'
+ it 'handles non strings or arrays'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/shuffle_spec.rb b/puppet/modules/stdlib/spec/acceptance/shuffle_spec.rb
new file mode 100755
index 00000000..b840d1f1
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/shuffle_spec.rb
@@ -0,0 +1,34 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'shuffle function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'shuffles arrays' do
+ pp = <<-EOS
+ $a = ["1", "2", "3", "4", "5", "6", "7", "8", "the","public","art","galleries"]
+ # Anagram: Large picture halls, I bet
+ $o = shuffle($a)
+ notice(inline_template('shuffle is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to_not match(/shuffle is \["1", "2", "3", "4", "5", "6", "7", "8", "the", "public", "art", "galleries"\]/)
+ end
+ end
+ it 'shuffles strings' do
+ pp = <<-EOS
+ $a = "blowzy night-frumps vex'd jack q"
+ $o = shuffle($a)
+ notice(inline_template('shuffle is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to_not match(/shuffle is "blowzy night-frumps vex'd jack q"/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles no arguments'
+ it 'handles non strings or arrays'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/size_spec.rb b/puppet/modules/stdlib/spec/acceptance/size_spec.rb
new file mode 100755
index 00000000..a52b778b
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/size_spec.rb
@@ -0,0 +1,55 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'size function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'single string size' do
+ pp = <<-EOS
+ $a = 'discombobulate'
+ $o = size($a)
+ notice(inline_template('size is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/size is 14/)
+ end
+ end
+ it 'with empty string' do
+ pp = <<-EOS
+ $a = ''
+ $o = size($a)
+ notice(inline_template('size is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/size is 0/)
+ end
+ end
+ it 'with undef' do
+ pp = <<-EOS
+ $a = undef
+ $o = size($a)
+ notice(inline_template('size is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/size is 0/)
+ end
+ end
+ it 'strings in array' do
+ pp = <<-EOS
+ $a = ['discombobulate', 'moo']
+ $o = size($a)
+ notice(inline_template('size is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/size is 2/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles no arguments'
+ it 'handles non strings or arrays'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/sort_spec.rb b/puppet/modules/stdlib/spec/acceptance/sort_spec.rb
new file mode 100755
index 00000000..c85bfabd
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/sort_spec.rb
@@ -0,0 +1,34 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'sort function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'sorts arrays' do
+ pp = <<-EOS
+ $a = ["the","public","art","galleries"]
+ # Anagram: Large picture halls, I bet
+ $o = sort($a)
+ notice(inline_template('sort is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/sort is \["art", "galleries", "public", "the"\]/)
+ end
+ end
+ it 'sorts strings' do
+ pp = <<-EOS
+ $a = "blowzy night-frumps vex'd jack q"
+ $o = sort($a)
+ notice(inline_template('sort is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/sort is " '-abcdefghijklmnopqrstuvwxyz"/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles no arguments'
+ it 'handles non strings or arrays'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/squeeze_spec.rb b/puppet/modules/stdlib/spec/acceptance/squeeze_spec.rb
new file mode 100755
index 00000000..400a458c
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/squeeze_spec.rb
@@ -0,0 +1,47 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'squeeze function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'squeezes arrays' do
+ pp = <<-EOS
+ # Real words!
+ $a = ["wallless", "laparohysterosalpingooophorectomy", "brrr", "goddessship"]
+ $o = squeeze($a)
+ notice(inline_template('squeeze is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/squeeze is \["wales", "laparohysterosalpingophorectomy", "br", "godeship"\]/)
+ end
+ end
+ it 'squeezez arrays with an argument'
+ it 'squeezes strings' do
+ pp = <<-EOS
+ $a = "wallless laparohysterosalpingooophorectomy brrr goddessship"
+ $o = squeeze($a)
+ notice(inline_template('squeeze is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/squeeze is "wales laparohysterosalpingophorectomy br godeship"/)
+ end
+ end
+
+ it 'squeezes strings with an argument' do
+ pp = <<-EOS
+ $a = "countessship duchessship governessship hostessship"
+ $o = squeeze($a, 's')
+ notice(inline_template('squeeze is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/squeeze is "counteship ducheship governeship hosteship"/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles no arguments'
+ it 'handles non strings or arrays'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/str2bool_spec.rb b/puppet/modules/stdlib/spec/acceptance/str2bool_spec.rb
new file mode 100755
index 00000000..cf549dab
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/str2bool_spec.rb
@@ -0,0 +1,31 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'str2bool function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'works with "y"' do
+ pp = <<-EOS
+ $o = str2bool('y')
+ notice(inline_template('str2bool is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/str2bool is true/)
+ end
+ end
+ it 'works with "Y"'
+ it 'works with "yes"'
+ it 'works with "1"'
+ it 'works with "true"'
+ it 'works with "n"'
+ it 'works with "N"'
+ it 'works with "no"'
+ it 'works with "0"'
+ it 'works with "false"'
+ it 'works with undef'
+ end
+ describe 'failure' do
+ it 'handles no arguments'
+ it 'handles non arrays or strings'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/str2saltedsha512_spec.rb b/puppet/modules/stdlib/spec/acceptance/str2saltedsha512_spec.rb
new file mode 100755
index 00000000..993e63ba
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/str2saltedsha512_spec.rb
@@ -0,0 +1,22 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'str2saltedsha512 function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'works with "y"' do
+ pp = <<-EOS
+ $o = str2saltedsha512('password')
+ notice(inline_template('str2saltedsha512 is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/str2saltedsha512 is "[a-f0-9]{136}"/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles no arguments'
+ it 'handles more than one argument'
+ it 'handles non strings'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/strftime_spec.rb b/puppet/modules/stdlib/spec/acceptance/strftime_spec.rb
new file mode 100755
index 00000000..53b7f903
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/strftime_spec.rb
@@ -0,0 +1,22 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'strftime function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'gives the Century' do
+ pp = <<-EOS
+ $o = strftime('%C')
+ notice(inline_template('strftime is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/strftime is "20"/)
+ end
+ end
+ it 'takes a timezone argument'
+ end
+ describe 'failure' do
+ it 'handles no arguments'
+ it 'handles invalid format strings'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/strip_spec.rb b/puppet/modules/stdlib/spec/acceptance/strip_spec.rb
new file mode 100755
index 00000000..906fd7ab
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/strip_spec.rb
@@ -0,0 +1,34 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'strip function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'strips arrays' do
+ pp = <<-EOS
+ $a = [" the "," public "," art","galleries "]
+ # Anagram: Large picture halls, I bet
+ $o = strip($a)
+ notice(inline_template('strip is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/strip is \["the", "public", "art", "galleries"\]/)
+ end
+ end
+ it 'strips strings' do
+ pp = <<-EOS
+ $a = " blowzy night-frumps vex'd jack q "
+ $o = strip($a)
+ notice(inline_template('strip is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/strip is "blowzy night-frumps vex'd jack q"/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles no arguments'
+ it 'handles non strings or arrays'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/suffix_spec.rb b/puppet/modules/stdlib/spec/acceptance/suffix_spec.rb
new file mode 100755
index 00000000..630f866d
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/suffix_spec.rb
@@ -0,0 +1,42 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'suffix function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'suffixes array of values' do
+ pp = <<-EOS
+ $o = suffix(['a','b','c'],'p')
+ notice(inline_template('suffix is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/suffix is \["ap", "bp", "cp"\]/)
+ end
+ end
+ it 'suffixs with empty array' do
+ pp = <<-EOS
+ $o = suffix([],'p')
+ notice(inline_template('suffix is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/suffix is \[\]/)
+ end
+ end
+ it 'suffixs array of values with undef' do
+ pp = <<-EOS
+ $o = suffix(['a','b','c'], undef)
+ notice(inline_template('suffix is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/suffix is \["a", "b", "c"\]/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'fails with no arguments'
+ it 'fails when first argument is not array'
+ it 'fails when second argument is not string'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/swapcase_spec.rb b/puppet/modules/stdlib/spec/acceptance/swapcase_spec.rb
new file mode 100755
index 00000000..b7894fbe
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/swapcase_spec.rb
@@ -0,0 +1,22 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'swapcase function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'works with strings' do
+ pp = <<-EOS
+ $o = swapcase('aBcD')
+ notice(inline_template('swapcase is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/swapcase is "AbCd"/)
+ end
+ end
+ it 'works with arrays'
+ end
+ describe 'failure' do
+ it 'handles no arguments'
+ it 'handles non arrays or strings'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/time_spec.rb b/puppet/modules/stdlib/spec/acceptance/time_spec.rb
new file mode 100755
index 00000000..cdb29607
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/time_spec.rb
@@ -0,0 +1,36 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'time function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'gives the time' do
+ pp = <<-EOS
+ $o = time()
+ notice(inline_template('time is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ m = r.stdout.match(/time is (\d+)\D/)
+
+ # When I wrote this test
+ expect(Integer(m[1])).to be > 1398894170
+ end
+ end
+ it 'takes a timezone argument' do
+ pp = <<-EOS
+ $o = time('UTC')
+ notice(inline_template('time is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ m = r.stdout.match(/time is (\d+)\D/)
+
+ expect(Integer(m[1])).to be > 1398894170
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles more arguments'
+ it 'handles invalid timezones'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/to_bytes_spec.rb b/puppet/modules/stdlib/spec/acceptance/to_bytes_spec.rb
new file mode 100755
index 00000000..2b4c61f4
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/to_bytes_spec.rb
@@ -0,0 +1,27 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'to_bytes function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'converts kB to B' do
+ pp = <<-EOS
+ $o = to_bytes('4 kB')
+ notice(inline_template('to_bytes is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ m = r.stdout.match(/to_bytes is (\d+)\D/)
+ expect(m[1]).to eq("4096")
+ end
+ end
+ it 'works without the B in unit'
+ it 'works without a space before unit'
+ it 'works without a unit'
+ it 'converts fractions'
+ end
+ describe 'failure' do
+ it 'handles no arguments'
+ it 'handles non integer arguments'
+ it 'handles unknown units like uB'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/type_spec.rb b/puppet/modules/stdlib/spec/acceptance/type_spec.rb
new file mode 100755
index 00000000..67e32480
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/type_spec.rb
@@ -0,0 +1,37 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'type function', :unless => (UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) || is_future_parser_enabled?) do
+ describe 'success' do
+ it 'types arrays' do
+ pp = <<-EOS
+ $a = ["the","public","art","galleries"]
+ # Anagram: Large picture halls, I bet
+ $o = type($a)
+ notice(inline_template('type is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/type is "array"/)
+ end
+ end
+ it 'types strings' do
+ pp = <<-EOS
+ $a = "blowzy night-frumps vex'd jack q"
+ $o = type($a)
+ notice(inline_template('type is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/type is "string"/)
+ end
+ end
+ it 'types hashes'
+ it 'types integers'
+ it 'types floats'
+ it 'types booleans'
+ end
+ describe 'failure' do
+ it 'handles no arguments'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/union_spec.rb b/puppet/modules/stdlib/spec/acceptance/union_spec.rb
new file mode 100755
index 00000000..6db8d0cf
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/union_spec.rb
@@ -0,0 +1,24 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'union function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'unions arrays' do
+ pp = <<-EOS
+ $a = ["the","public"]
+ $b = ["art","galleries"]
+ # Anagram: Large picture halls, I bet
+ $o = union($a,$b)
+ notice(inline_template('union is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/union is \["the", "public", "art", "galleries"\]/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles no arguments'
+ it 'handles non arrays'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/unique_spec.rb b/puppet/modules/stdlib/spec/acceptance/unique_spec.rb
new file mode 100755
index 00000000..bfadad19
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/unique_spec.rb
@@ -0,0 +1,33 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'unique function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'uniques arrays' do
+ pp = <<-EOS
+ $a = ["wallless", "wallless", "brrr", "goddessship"]
+ $o = unique($a)
+ notice(inline_template('unique is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/unique is \["wallless", "brrr", "goddessship"\]/)
+ end
+ end
+ it 'uniques strings' do
+ pp = <<-EOS
+ $a = "wallless laparohysterosalpingooophorectomy brrr goddessship"
+ $o = unique($a)
+ notice(inline_template('unique is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/unique is "wales prohytingcmbd"/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles no arguments'
+ it 'handles non strings or arrays'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/unsupported_spec.rb b/puppet/modules/stdlib/spec/acceptance/unsupported_spec.rb
new file mode 100755
index 00000000..1c559f67
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/unsupported_spec.rb
@@ -0,0 +1,11 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'unsupported distributions and OSes', :if => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ it 'should fail' do
+ pp = <<-EOS
+ class { 'mysql::server': }
+ EOS
+ expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/unsupported osfamily/i)
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/upcase_spec.rb b/puppet/modules/stdlib/spec/acceptance/upcase_spec.rb
new file mode 100755
index 00000000..3d2906d7
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/upcase_spec.rb
@@ -0,0 +1,33 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'upcase function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'upcases arrays' do
+ pp = <<-EOS
+ $a = ["wallless", "laparohysterosalpingooophorectomy", "brrr", "goddessship"]
+ $o = upcase($a)
+ notice(inline_template('upcase is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/upcase is \["WALLLESS", "LAPAROHYSTEROSALPINGOOOPHORECTOMY", "BRRR", "GODDESSSHIP"\]/)
+ end
+ end
+ it 'upcases strings' do
+ pp = <<-EOS
+ $a = "wallless laparohysterosalpingooophorectomy brrr goddessship"
+ $o = upcase($a)
+ notice(inline_template('upcase is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/upcase is "WALLLESS LAPAROHYSTEROSALPINGOOOPHORECTOMY BRRR GODDESSSHIP"/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles no arguments'
+ it 'handles non strings or arrays'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/uriescape_spec.rb b/puppet/modules/stdlib/spec/acceptance/uriescape_spec.rb
new file mode 100755
index 00000000..7e30205e
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/uriescape_spec.rb
@@ -0,0 +1,23 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'uriescape function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'uriescape strings' do
+ pp = <<-EOS
+ $a = ":/?#[]@!$&'()*+,;= \\\"{}"
+ $o = uriescape($a)
+ notice(inline_template('uriescape is <%= @o.inspect %>'))
+ EOS
+
+ apply_manifest(pp, :catch_failures => true) do |r|
+ expect(r.stdout).to match(/uriescape is ":\/\?%23\[\]@!\$&'\(\)\*\+,;=%20%22%7B%7D"/)
+ end
+ end
+ it 'does nothing if a string is already safe'
+ end
+ describe 'failure' do
+ it 'handles no arguments'
+ it 'handles non strings or arrays'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/validate_absolute_path_spec.rb b/puppet/modules/stdlib/spec/acceptance/validate_absolute_path_spec.rb
new file mode 100755
index 00000000..7082e848
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/validate_absolute_path_spec.rb
@@ -0,0 +1,31 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'validate_absolute_path function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ %w{
+ C:/
+ C:\\\\
+ C:\\\\WINDOWS\\\\System32
+ C:/windows/system32
+ X:/foo/bar
+ X:\\\\foo\\\\bar
+ /var/tmp
+ /var/lib/puppet
+ /var/opt/../lib/puppet
+ }.each do |path|
+ it "validates a single argument #{path}" do
+ pp = <<-EOS
+ $one = '#{path}'
+ validate_absolute_path($one)
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles improper number of arguments'
+ it 'handles relative paths'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/validate_array_spec.rb b/puppet/modules/stdlib/spec/acceptance/validate_array_spec.rb
new file mode 100755
index 00000000..b53e98c2
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/validate_array_spec.rb
@@ -0,0 +1,37 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'validate_array function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'validates a single argument' do
+ pp = <<-EOS
+ $one = ['a', 'b']
+ validate_array($one)
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+ it 'validates an multiple arguments' do
+ pp = <<-EOS
+ $one = ['a', 'b']
+ $two = [['c'], 'd']
+ validate_array($one,$two)
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+ it 'validates a non-array' do
+ {
+ %{validate_array({'a' => 'hash' })} => "Hash",
+ %{validate_array('string')} => "String",
+ %{validate_array(false)} => "FalseClass",
+ %{validate_array(undef)} => "String"
+ }.each do |pp,type|
+ expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/a #{type}/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles improper number of arguments'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/validate_augeas_spec.rb b/puppet/modules/stdlib/spec/acceptance/validate_augeas_spec.rb
new file mode 100755
index 00000000..71a4c842
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/validate_augeas_spec.rb
@@ -0,0 +1,63 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'validate_augeas function', :unless => ((UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem'))) or (fact('osfamily') == 'windows')) do
+ describe 'prep' do
+ it 'installs augeas for tests'
+ end
+ describe 'success' do
+ context 'valid inputs with no 3rd argument' do
+ {
+ 'root:x:0:0:root:/root:/bin/bash\n' => 'Passwd.lns',
+ 'proc /proc proc nodev,noexec,nosuid 0 0\n' => 'Fstab.lns'
+ }.each do |line,lens|
+ it "validates a single argument for #{lens}" do
+ pp = <<-EOS
+ $line = "#{line}"
+ $lens = "#{lens}"
+ validate_augeas($line, $lens)
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+ end
+ end
+ context 'valid inputs with 3rd and 4th arguments' do
+ it "validates a restricted value" do
+ line = 'root:x:0:0:root:/root:/bin/barsh\n'
+ lens = 'Passwd.lns'
+ restriction = '$file/*[shell="/bin/barsh"]'
+ pp = <<-EOS
+ $line = "#{line}"
+ $lens = "#{lens}"
+ $restriction = ['#{restriction}']
+ validate_augeas($line, $lens, $restriction, "my custom failure message")
+ EOS
+
+ expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/my custom failure message/)
+ end
+ end
+ context 'invalid inputs' do
+ {
+ 'root:x:0:0:root' => 'Passwd.lns',
+ '127.0.1.1' => 'Hosts.lns'
+ }.each do |line,lens|
+ it "validates a single argument for #{lens}" do
+ pp = <<-EOS
+ $line = "#{line}"
+ $lens = "#{lens}"
+ validate_augeas($line, $lens)
+ EOS
+
+ apply_manifest(pp, :expect_failures => true)
+ end
+ end
+ end
+ context 'garbage inputs' do
+ it 'raises an error on invalid inputs'
+ end
+ end
+ describe 'failure' do
+ it 'handles improper number of arguments'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/validate_bool_spec.rb b/puppet/modules/stdlib/spec/acceptance/validate_bool_spec.rb
new file mode 100755
index 00000000..c837f089
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/validate_bool_spec.rb
@@ -0,0 +1,37 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'validate_bool function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'validates a single argument' do
+ pp = <<-EOS
+ $one = true
+ validate_bool($one)
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+ it 'validates an multiple arguments' do
+ pp = <<-EOS
+ $one = true
+ $two = false
+ validate_bool($one,$two)
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+ it 'validates a non-bool' do
+ {
+ %{validate_bool('true')} => "String",
+ %{validate_bool('false')} => "String",
+ %{validate_bool([true])} => "Array",
+ %{validate_bool(undef)} => "String",
+ }.each do |pp,type|
+ expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/a #{type}/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles improper number of arguments'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/validate_cmd_spec.rb b/puppet/modules/stdlib/spec/acceptance/validate_cmd_spec.rb
new file mode 100755
index 00000000..5ac66fdb
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/validate_cmd_spec.rb
@@ -0,0 +1,52 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'validate_cmd function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'validates a true command' do
+ pp = <<-EOS
+ $one = 'foo'
+ if $::osfamily == 'windows' {
+ $two = 'echo' #shell built-in
+ } else {
+ $two = '/bin/echo'
+ }
+ validate_cmd($one,$two)
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+ it 'validates a fail command' do
+ pp = <<-EOS
+ $one = 'foo'
+ if $::osfamily == 'windows' {
+ $two = 'C:/aoeu'
+ } else {
+ $two = '/bin/aoeu'
+ }
+ validate_cmd($one,$two)
+ EOS
+
+ apply_manifest(pp, :expect_failures => true)
+ end
+ it 'validates a fail command with a custom error message' do
+ pp = <<-EOS
+ $one = 'foo'
+ if $::osfamily == 'windows' {
+ $two = 'C:/aoeu'
+ } else {
+ $two = '/bin/aoeu'
+ }
+ validate_cmd($one,$two,"aoeu is dvorak")
+ EOS
+
+ apply_manifest(pp, :expect_failures => true) do |output|
+ expect(output.stderr).to match(/aoeu is dvorak/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles improper number of arguments'
+ it 'handles improper argument types'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/validate_hash_spec.rb b/puppet/modules/stdlib/spec/acceptance/validate_hash_spec.rb
new file mode 100755
index 00000000..52fb615b
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/validate_hash_spec.rb
@@ -0,0 +1,37 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'validate_hash function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'validates a single argument' do
+ pp = <<-EOS
+ $one = { 'a' => 1 }
+ validate_hash($one)
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+ it 'validates an multiple arguments' do
+ pp = <<-EOS
+ $one = { 'a' => 1 }
+ $two = { 'b' => 2 }
+ validate_hash($one,$two)
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+ it 'validates a non-hash' do
+ {
+ %{validate_hash('{ "not" => "hash" }')} => "String",
+ %{validate_hash('string')} => "String",
+ %{validate_hash(["array"])} => "Array",
+ %{validate_hash(undef)} => "String",
+ }.each do |pp,type|
+ expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/a #{type}/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles improper number of arguments'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/validate_ipv4_address_spec.rb b/puppet/modules/stdlib/spec/acceptance/validate_ipv4_address_spec.rb
new file mode 100755
index 00000000..64841c37
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/validate_ipv4_address_spec.rb
@@ -0,0 +1,31 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'validate_ipv4_address function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'validates a single argument' do
+ pp = <<-EOS
+ $one = '1.2.3.4'
+ validate_ipv4_address($one)
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+ it 'validates an multiple arguments' do
+ pp = <<-EOS
+ $one = '1.2.3.4'
+ $two = '5.6.7.8'
+ validate_ipv4_address($one,$two)
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+ end
+ describe 'failure' do
+ it 'handles improper number of arguments'
+ it 'handles ipv6 addresses'
+ it 'handles non-ipv4 strings'
+ it 'handles numbers'
+ it 'handles no arguments'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/validate_ipv6_address_spec.rb b/puppet/modules/stdlib/spec/acceptance/validate_ipv6_address_spec.rb
new file mode 100755
index 00000000..6426d1a5
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/validate_ipv6_address_spec.rb
@@ -0,0 +1,31 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'validate_ipv6_address function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'validates a single argument' do
+ pp = <<-EOS
+ $one = '3ffe:0505:0002::'
+ validate_ipv6_address($one)
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+ it 'validates an multiple arguments' do
+ pp = <<-EOS
+ $one = '3ffe:0505:0002::'
+ $two = '3ffe:0505:0001::'
+ validate_ipv6_address($one,$two)
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+ end
+ describe 'failure' do
+ it 'handles improper number of arguments'
+ it 'handles ipv6 addresses'
+ it 'handles non-ipv6 strings'
+ it 'handles numbers'
+ it 'handles no arguments'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/validate_re_spec.rb b/puppet/modules/stdlib/spec/acceptance/validate_re_spec.rb
new file mode 100755
index 00000000..22f6d47d
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/validate_re_spec.rb
@@ -0,0 +1,47 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'validate_re function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'validates a string' do
+ pp = <<-EOS
+ $one = 'one'
+ $two = '^one$'
+ validate_re($one,$two)
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+ it 'validates an array' do
+ pp = <<-EOS
+ $one = 'one'
+ $two = ['^one$', '^two']
+ validate_re($one,$two)
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+ it 'validates a failed array' do
+ pp = <<-EOS
+ $one = 'one'
+ $two = ['^two$', '^three']
+ validate_re($one,$two)
+ EOS
+
+ apply_manifest(pp, :expect_failures => true)
+ end
+ it 'validates a failed array with a custom error message' do
+ pp = <<-EOS
+ $one = '3.4.3'
+ $two = '^2.7'
+ validate_re($one,$two,"The $puppetversion fact does not match 2.7")
+ EOS
+
+ expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/does not match/)
+ end
+ end
+ describe 'failure' do
+ it 'handles improper number of arguments'
+ it 'handles improper argument types'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/validate_slength_spec.rb b/puppet/modules/stdlib/spec/acceptance/validate_slength_spec.rb
new file mode 100755
index 00000000..1ab2bb98
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/validate_slength_spec.rb
@@ -0,0 +1,72 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'validate_slength function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'validates a single string max' do
+ pp = <<-EOS
+ $one = 'discombobulate'
+ $two = 17
+ validate_slength($one,$two)
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+ it 'validates multiple string maxes' do
+ pp = <<-EOS
+ $one = ['discombobulate', 'moo']
+ $two = 17
+ validate_slength($one,$two)
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+ it 'validates min/max of strings in array' do
+ pp = <<-EOS
+ $one = ['discombobulate', 'moo']
+ $two = 17
+ $three = 3
+ validate_slength($one,$two,$three)
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+ it 'validates a single string max of incorrect length' do
+ pp = <<-EOS
+ $one = 'discombobulate'
+ $two = 1
+ validate_slength($one,$two)
+ EOS
+
+ apply_manifest(pp, :expect_failures => true)
+ end
+ it 'validates multiple string maxes of incorrect length' do
+ pp = <<-EOS
+ $one = ['discombobulate', 'moo']
+ $two = 3
+ validate_slength($one,$two)
+ EOS
+
+ apply_manifest(pp, :expect_failures => true)
+ end
+ it 'validates multiple strings min/maxes of incorrect length' do
+ pp = <<-EOS
+ $one = ['discombobulate', 'moo']
+ $two = 17
+ $three = 10
+ validate_slength($one,$two,$three)
+ EOS
+
+ apply_manifest(pp, :expect_failures => true)
+ end
+ end
+ describe 'failure' do
+ it 'handles improper number of arguments'
+ it 'handles improper first argument type'
+ it 'handles non-strings in array of first argument'
+ it 'handles improper second argument type'
+ it 'handles improper third argument type'
+ it 'handles negative ranges'
+ it 'handles improper ranges'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/validate_string_spec.rb b/puppet/modules/stdlib/spec/acceptance/validate_string_spec.rb
new file mode 100755
index 00000000..8956f48c
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/validate_string_spec.rb
@@ -0,0 +1,36 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'validate_string function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'validates a single argument' do
+ pp = <<-EOS
+ $one = 'string'
+ validate_string($one)
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+ it 'validates an multiple arguments' do
+ pp = <<-EOS
+ $one = 'string'
+ $two = 'also string'
+ validate_string($one,$two)
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+ it 'validates a non-string' do
+ {
+ %{validate_string({ 'a' => 'hash' })} => "Hash",
+ %{validate_string(['array'])} => "Array",
+ %{validate_string(false)} => "FalseClass",
+ }.each do |pp,type|
+ expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/a #{type}/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles improper number of arguments'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/values_at_spec.rb b/puppet/modules/stdlib/spec/acceptance/values_at_spec.rb
new file mode 100755
index 00000000..da63cf30
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/values_at_spec.rb
@@ -0,0 +1,73 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'values_at function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'returns a specific value' do
+ pp = <<-EOS
+ $one = ['a','b','c','d','e']
+ $two = 1
+ $output = values_at($one,$two)
+ notice(inline_template('<%= @output.inspect %>'))
+ EOS
+
+ expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\["b"\]/)
+ end
+ it 'returns a specific negative index value' do
+ pending("negative numbers don't work")
+ pp = <<-EOS
+ $one = ['a','b','c','d','e']
+ $two = -1
+ $output = values_at($one,$two)
+ notice(inline_template('<%= @output.inspect %>'))
+ EOS
+
+ expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\["e"\]/)
+ end
+ it 'returns a range of values' do
+ pp = <<-EOS
+ $one = ['a','b','c','d','e']
+ $two = "1-3"
+ $output = values_at($one,$two)
+ notice(inline_template('<%= @output.inspect %>'))
+ EOS
+
+ expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\["b", "c", "d"\]/)
+ end
+ it 'returns a negative specific value and range of values' do
+ pp = <<-EOS
+ $one = ['a','b','c','d','e']
+ $two = ["1-3",0]
+ $output = values_at($one,$two)
+ notice(inline_template('<%= @output.inspect %>'))
+ EOS
+
+ expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\["b", "c", "d", "a"\]/)
+ end
+ end
+ describe 'failure' do
+ it 'handles improper number of arguments' do
+ pp = <<-EOS
+ $one = ['a','b','c','d','e']
+ $output = values_at($one)
+ notice(inline_template('<%= @output.inspect %>'))
+ EOS
+
+ expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/Wrong number of arguments/)
+ end
+ it 'handles non-indicies arguments' do
+ pp = <<-EOS
+ $one = ['a','b','c','d','e']
+ $two = []
+ $output = values_at($one,$two)
+ notice(inline_template('<%= @output.inspect %>'))
+ EOS
+
+ expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/at least one positive index/)
+ end
+
+ it 'detects index ranges smaller than the start range'
+ it 'handles index ranges larger than array'
+ it 'handles non-integer indicies'
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/values_spec.rb b/puppet/modules/stdlib/spec/acceptance/values_spec.rb
new file mode 100755
index 00000000..a2eff329
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/values_spec.rb
@@ -0,0 +1,35 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+
+describe 'values function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'returns an array of values' do
+ pp = <<-EOS
+ $arg = {
+ 'a' => 1,
+ 'b' => 2,
+ 'c' => 3,
+ }
+ $output = values($arg)
+ notice(inline_template('<%= @output.sort.inspect %>'))
+ EOS
+ if is_future_parser_enabled?
+ expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\[1, 2, 3\]/)
+ else
+ expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\["1", "2", "3"\]/)
+ end
+
+ end
+ end
+ describe 'failure' do
+ it 'handles non-hash arguments' do
+ pp = <<-EOS
+ $arg = "foo"
+ $output = values($arg)
+ notice(inline_template('<%= @output.inspect %>'))
+ EOS
+
+ expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/Requires hash/)
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/acceptance/zip_spec.rb b/puppet/modules/stdlib/spec/acceptance/zip_spec.rb
new file mode 100755
index 00000000..139079e3
--- /dev/null
+++ b/puppet/modules/stdlib/spec/acceptance/zip_spec.rb
@@ -0,0 +1,86 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper_acceptance'
+require 'puppet'
+
+describe 'zip function', :unless => UNSUPPORTED_PLATFORMS.include?(fact('operatingsystem')) do
+ describe 'success' do
+ it 'zips two arrays of numbers together' do
+ pp = <<-EOS
+ $one = [1,2,3,4]
+ $two = [5,6,7,8]
+ $output = zip($one,$two)
+ notice(inline_template('<%= @output.inspect %>'))
+ EOS
+ if is_future_parser_enabled?
+ expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\[\[1, 5\], \[2, 6\], \[3, 7\], \[4, 8\]\]/)
+ else
+ expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\[\["1", "5"\], \["2", "6"\], \["3", "7"\], \["4", "8"\]\]/)
+ end
+ end
+ it 'zips two arrays of numbers & bools together' do
+ pp = <<-EOS
+ $one = [1,2,"three",4]
+ $two = [true,true,false,false]
+ $output = zip($one,$two)
+ notice(inline_template('<%= @output.inspect %>'))
+ EOS
+ if is_future_parser_enabled?
+ expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\[\[1, true\], \[2, true\], \["three", false\], \[4, false\]\]/)
+ else
+ expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\[\["1", true\], \["2", true\], \["three", false\], \["4", false\]\]/)
+ end
+ end
+ it 'zips two arrays of numbers together and flattens them' do
+ # XXX This only tests the argument `true`, even though the following are valid:
+ # 1 t y true yes
+ # 0 f n false no
+ # undef undefined
+ pp = <<-EOS
+ $one = [1,2,3,4]
+ $two = [5,6,7,8]
+ $output = zip($one,$two,true)
+ notice(inline_template('<%= @output.inspect %>'))
+ EOS
+ if is_future_parser_enabled?
+ expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\[1, 5, 2, 6, 3, 7, 4, 8\]/)
+ else
+ expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\["1", "5", "2", "6", "3", "7", "4", "8"\]/)
+ end
+ end
+ it 'handles unmatched length' do
+ # XXX Is this expected behavior?
+ pp = <<-EOS
+ $one = [1,2]
+ $two = [5,6,7,8]
+ $output = zip($one,$two)
+ notice(inline_template('<%= @output.inspect %>'))
+ EOS
+ if is_future_parser_enabled?
+ expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\[\[1, 5\], \[2, 6\]\]/)
+ else
+ expect(apply_manifest(pp, :catch_failures => true).stdout).to match(/\[\["1", "5"\], \["2", "6"\]\]/)
+ end
+ end
+ end
+ describe 'failure' do
+ it 'handles improper number of arguments' do
+ pp = <<-EOS
+ $one = [1,2]
+ $output = zip($one)
+ notice(inline_template('<%= @output.inspect %>'))
+ EOS
+
+ expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/Wrong number of arguments/)
+ end
+ it 'handles improper argument types' do
+ pp = <<-EOS
+ $one = "a string"
+ $two = [5,6,7,8]
+ $output = zip($one,$two)
+ notice(inline_template('<%= @output.inspect %>'))
+ EOS
+
+ expect(apply_manifest(pp, :expect_failures => true).stderr).to match(/Requires array/)
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/classes/anchor_spec.rb b/puppet/modules/stdlib/spec/classes/anchor_spec.rb
new file mode 100755
index 00000000..2d4455e4
--- /dev/null
+++ b/puppet/modules/stdlib/spec/classes/anchor_spec.rb
@@ -0,0 +1,30 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+require 'puppet_spec/compiler'
+
+describe "anchorrefresh" do
+ include PuppetSpec::Compiler
+
+ let :transaction do
+ apply_compiled_manifest(<<-ANCHORCLASS)
+ class anchored {
+ anchor { 'anchored::begin': }
+ ~> anchor { 'anchored::end': }
+ }
+
+ class anchorrefresh {
+ notify { 'first': }
+ ~> class { 'anchored': }
+ ~> anchor { 'final': }
+ }
+
+ include anchorrefresh
+ ANCHORCLASS
+ end
+
+ it 'propagates events through the anchored class' do
+ resource = transaction.resource_status('Anchor[final]')
+
+ expect(resource.restarted).to eq(true)
+ end
+end
diff --git a/puppet/modules/stdlib/spec/fixtures/dscacheutil/root b/puppet/modules/stdlib/spec/fixtures/dscacheutil/root
new file mode 100644
index 00000000..1e34519b
--- /dev/null
+++ b/puppet/modules/stdlib/spec/fixtures/dscacheutil/root
@@ -0,0 +1,8 @@
+name: root
+password: *
+uid: 0
+gid: 0
+dir: /var/root
+shell: /bin/bash
+gecos: rawr Root
+
diff --git a/puppet/modules/stdlib/spec/functions/abs_spec.rb b/puppet/modules/stdlib/spec/functions/abs_spec.rb
new file mode 100755
index 00000000..3c25ce28
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/abs_spec.rb
@@ -0,0 +1,25 @@
+#! /usr/bin/env ruby -S rspec
+
+require 'spec_helper'
+
+describe "the abs function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("abs")).to eq("function_abs")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_abs([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should convert a negative number into a positive" do
+ result = scope.function_abs(["-34"])
+ expect(result).to(eq(34))
+ end
+
+ it "should do nothing with a positive number" do
+ result = scope.function_abs(["5678"])
+ expect(result).to(eq(5678))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/any2array_spec.rb b/puppet/modules/stdlib/spec/functions/any2array_spec.rb
new file mode 100755
index 00000000..87cd04b5
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/any2array_spec.rb
@@ -0,0 +1,55 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the any2array function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("any2array")).to eq("function_any2array")
+ end
+
+ it "should return an empty array if there is less than 1 argument" do
+ result = scope.function_any2array([])
+ expect(result).to(eq([]))
+ end
+
+ it "should convert boolean true to [ true ] " do
+ result = scope.function_any2array([true])
+ expect(result).to(eq([true]))
+ end
+
+ it "should convert one object to [object]" do
+ result = scope.function_any2array(['one'])
+ expect(result).to(eq(['one']))
+ end
+
+ it "should convert multiple objects to [objects]" do
+ result = scope.function_any2array(['one', 'two'])
+ expect(result).to(eq(['one', 'two']))
+ end
+
+ it "should return empty array it was called with" do
+ result = scope.function_any2array([[]])
+ expect(result).to(eq([]))
+ end
+
+ it "should return one-member array it was called with" do
+ result = scope.function_any2array([['string']])
+ expect(result).to(eq(['string']))
+ end
+
+ it "should return multi-member array it was called with" do
+ result = scope.function_any2array([['one', 'two']])
+ expect(result).to(eq(['one', 'two']))
+ end
+
+ it "should return members of a hash it was called with" do
+ result = scope.function_any2array([{ 'key' => 'value' }])
+ expect(result).to(eq(['key', 'value']))
+ end
+
+ it "should return an empty array if it was called with an empty hash" do
+ result = scope.function_any2array([{ }])
+ expect(result).to(eq([]))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/base64_spec.rb b/puppet/modules/stdlib/spec/functions/base64_spec.rb
new file mode 100755
index 00000000..e93fafcd
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/base64_spec.rb
@@ -0,0 +1,34 @@
+#! /usr/bin/env ruby -S rspec
+
+require 'spec_helper'
+
+describe "the base64 function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("base64")).to eq("function_base64")
+ end
+
+ it "should raise a ParseError if there are other than 2 arguments" do
+ expect { scope.function_base64([]) }.to(raise_error(Puppet::ParseError))
+ expect { scope.function_base64(["asdf"]) }.to(raise_error(Puppet::ParseError))
+ expect { scope.function_base64(["asdf","moo","cow"]) }.to(raise_error(Puppet::ParseError))
+ end
+
+ it "should raise a ParseError if argument 1 isn't 'encode' or 'decode'" do
+ expect { scope.function_base64(["bees","astring"]) }.to(raise_error(Puppet::ParseError, /first argument must be one of/))
+ end
+
+ it "should raise a ParseError if argument 2 isn't a string" do
+ expect { scope.function_base64(["encode",["2"]]) }.to(raise_error(Puppet::ParseError, /second argument must be a string/))
+ end
+
+ it "should encode a encoded string" do
+ result = scope.function_base64(["encode",'thestring'])
+ expect(result).to match(/\AdGhlc3RyaW5n\n\Z/)
+ end
+ it "should decode a base64 encoded string" do
+ result = scope.function_base64(["decode",'dGhlc3RyaW5n'])
+ expect(result).to eq('thestring')
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/bool2num_spec.rb b/puppet/modules/stdlib/spec/functions/bool2num_spec.rb
new file mode 100755
index 00000000..3904d7e4
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/bool2num_spec.rb
@@ -0,0 +1,38 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the bool2num function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("bool2num")).to eq("function_bool2num")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_bool2num([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should convert true to 1" do
+ result = scope.function_bool2num([true])
+ expect(result).to(eq(1))
+ end
+
+ it "should convert 'true' to 1" do
+ result = scope.function_bool2num(['true'])
+ result.should(eq(1))
+ end
+
+ it "should convert 'false' to 0" do
+ result = scope.function_bool2num(['false'])
+ expect(result).to(eq(0))
+ end
+
+ it "should accept objects which extend String" do
+ class AlsoString < String
+ end
+
+ value = AlsoString.new('true')
+ result = scope.function_bool2num([value])
+ result.should(eq(1))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/capitalize_spec.rb b/puppet/modules/stdlib/spec/functions/capitalize_spec.rb
new file mode 100755
index 00000000..fd0e92ba
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/capitalize_spec.rb
@@ -0,0 +1,28 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the capitalize function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("capitalize")).to eq("function_capitalize")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_capitalize([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should capitalize the beginning of a string" do
+ result = scope.function_capitalize(["abc"])
+ expect(result).to(eq("Abc"))
+ end
+
+ it "should accept objects which extend String" do
+ class AlsoString < String
+ end
+
+ value = AlsoString.new('abc')
+ result = scope.function_capitalize([value])
+ result.should(eq('Abc'))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/chomp_spec.rb b/puppet/modules/stdlib/spec/functions/chomp_spec.rb
new file mode 100755
index 00000000..b1e1e60f
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/chomp_spec.rb
@@ -0,0 +1,28 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the chomp function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("chomp")).to eq("function_chomp")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_chomp([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should chomp the end of a string" do
+ result = scope.function_chomp(["abc\n"])
+ expect(result).to(eq("abc"))
+ end
+
+ it "should accept objects which extend String" do
+ class AlsoString < String
+ end
+
+ value = AlsoString.new("abc\n")
+ result = scope.function_chomp([value])
+ result.should(eq("abc"))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/chop_spec.rb b/puppet/modules/stdlib/spec/functions/chop_spec.rb
new file mode 100755
index 00000000..c8a19519
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/chop_spec.rb
@@ -0,0 +1,28 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the chop function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("chop")).to eq("function_chop")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_chop([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should chop the end of a string" do
+ result = scope.function_chop(["asdf\n"])
+ expect(result).to(eq("asdf"))
+ end
+
+ it "should accept objects which extend String" do
+ class AlsoString < String
+ end
+
+ value = AlsoString.new("abc\n")
+ result = scope.function_chop([value])
+ result.should(eq('abc'))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/concat_spec.rb b/puppet/modules/stdlib/spec/functions/concat_spec.rb
new file mode 100755
index 00000000..49fa6bb3
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/concat_spec.rb
@@ -0,0 +1,50 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the concat function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should raise a ParseError if the client does not provide at least two arguments" do
+ expect { scope.function_concat([]) }.to(raise_error(Puppet::ParseError))
+ expect { scope.function_concat([[1]]) }.to(raise_error(Puppet::ParseError))
+ end
+
+ it "should raise a ParseError if the first parameter is not an array" do
+ expect { scope.function_concat([1, []])}.to(raise_error(Puppet::ParseError))
+ end
+
+ it "should not raise a ParseError if the client provides more than two arguments" do
+ expect { scope.function_concat([[1],[2],[3]]) }.not_to raise_error
+ end
+
+ it "should be able to concat an array" do
+ result = scope.function_concat([['1','2','3'],['4','5','6']])
+ expect(result).to(eq(['1','2','3','4','5','6']))
+ end
+
+ it "should be able to concat a primitive to an array" do
+ result = scope.function_concat([['1','2','3'],'4'])
+ expect(result).to(eq(['1','2','3','4']))
+ end
+
+ it "should not accidentally flatten nested arrays" do
+ result = scope.function_concat([['1','2','3'],[['4','5'],'6']])
+ expect(result).to(eq(['1','2','3',['4','5'],'6']))
+ end
+
+ it "should leave the original array intact" do
+ array_original = ['1','2','3']
+ result = scope.function_concat([array_original,['4','5','6']])
+ array_original.should(eq(['1','2','3']))
+ end
+
+ it "should be able to concat multiple arrays" do
+ result = scope.function_concat([['1','2','3'],['4','5','6'],['7','8','9']])
+ expect(result).to(eq(['1','2','3','4','5','6','7','8','9']))
+ end
+
+ it "should be able to concat mix of primitives and arrays to a final array" do
+ result = scope.function_concat([['1','2','3'],'4',['5','6','7']])
+ expect(result).to(eq(['1','2','3','4','5','6','7']))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/count_spec.rb b/puppet/modules/stdlib/spec/functions/count_spec.rb
new file mode 100755
index 00000000..f8f1d484
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/count_spec.rb
@@ -0,0 +1,31 @@
+#! /usr/bin/env ruby -S rspec
+
+require 'spec_helper'
+
+describe "the count function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("count")).to eq("function_count")
+ end
+
+ it "should raise a ArgumentError if there is more than 2 arguments" do
+ expect { scope.function_count(['foo', 'bar', 'baz']) }.to( raise_error(ArgumentError))
+ end
+
+ it "should be able to count arrays" do
+ expect(scope.function_count([["1","2","3"]])).to(eq(3))
+ end
+
+ it "should be able to count matching elements in arrays" do
+ expect(scope.function_count([["1", "2", "2"], "2"])).to(eq(2))
+ end
+
+ it "should not count nil or empty strings" do
+ expect(scope.function_count([["foo","bar",nil,""]])).to(eq(2))
+ end
+
+ it 'does not count an undefined hash key or an out of bound array index (which are both :undef)' do
+ expect(scope.function_count([["foo",:undef,:undef]])).to eq(1)
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/deep_merge_spec.rb b/puppet/modules/stdlib/spec/functions/deep_merge_spec.rb
new file mode 100755
index 00000000..7087904a
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/deep_merge_spec.rb
@@ -0,0 +1,105 @@
+#! /usr/bin/env ruby -S rspec
+
+require 'spec_helper'
+
+describe Puppet::Parser::Functions.function(:deep_merge) do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ describe 'when calling deep_merge from puppet' do
+ it "should not compile when no arguments are passed" do
+ skip("Fails on 2.6.x, see bug #15912") if Puppet.version =~ /^2\.6\./
+ Puppet[:code] = '$x = deep_merge()'
+ expect {
+ scope.compiler.compile
+ }.to raise_error(Puppet::ParseError, /wrong number of arguments/)
+ end
+
+ it "should not compile when 1 argument is passed" do
+ skip("Fails on 2.6.x, see bug #15912") if Puppet.version =~ /^2\.6\./
+ Puppet[:code] = "$my_hash={'one' => 1}\n$x = deep_merge($my_hash)"
+ expect {
+ scope.compiler.compile
+ }.to raise_error(Puppet::ParseError, /wrong number of arguments/)
+ end
+ end
+
+ describe 'when calling deep_merge on the scope instance' do
+ it 'should require all parameters are hashes' do
+ expect { new_hash = scope.function_deep_merge([{}, '2'])}.to raise_error(Puppet::ParseError, /unexpected argument type String/)
+ expect { new_hash = scope.function_deep_merge([{}, 2])}.to raise_error(Puppet::ParseError, /unexpected argument type Fixnum/)
+ end
+
+ it 'should accept empty strings as puppet undef' do
+ expect { new_hash = scope.function_deep_merge([{}, ''])}.not_to raise_error
+ end
+
+ it 'should be able to deep_merge two hashes' do
+ new_hash = scope.function_deep_merge([{'one' => '1', 'two' => '1'}, {'two' => '2', 'three' => '2'}])
+ expect(new_hash['one']).to eq('1')
+ expect(new_hash['two']).to eq('2')
+ expect(new_hash['three']).to eq('2')
+ end
+
+ it 'should deep_merge multiple hashes' do
+ hash = scope.function_deep_merge([{'one' => 1}, {'one' => '2'}, {'one' => '3'}])
+ expect(hash['one']).to eq('3')
+ end
+
+ it 'should accept empty hashes' do
+ expect(scope.function_deep_merge([{},{},{}])).to eq({})
+ end
+
+ it 'should deep_merge subhashes' do
+ hash = scope.function_deep_merge([{'one' => 1}, {'two' => 2, 'three' => { 'four' => 4 } }])
+ expect(hash['one']).to eq(1)
+ expect(hash['two']).to eq(2)
+ expect(hash['three']).to eq({ 'four' => 4 })
+ end
+
+ it 'should append to subhashes' do
+ hash = scope.function_deep_merge([{'one' => { 'two' => 2 } }, { 'one' => { 'three' => 3 } }])
+ expect(hash['one']).to eq({ 'two' => 2, 'three' => 3 })
+ end
+
+ it 'should append to subhashes 2' do
+ hash = scope.function_deep_merge([{'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } }, {'two' => 'dos', 'three' => { 'five' => 5 } }])
+ expect(hash['one']).to eq(1)
+ expect(hash['two']).to eq('dos')
+ expect(hash['three']).to eq({ 'four' => 4, 'five' => 5 })
+ end
+
+ it 'should append to subhashes 3' do
+ hash = scope.function_deep_merge([{ 'key1' => { 'a' => 1, 'b' => 2 }, 'key2' => { 'c' => 3 } }, { 'key1' => { 'b' => 99 } }])
+ expect(hash['key1']).to eq({ 'a' => 1, 'b' => 99 })
+ expect(hash['key2']).to eq({ 'c' => 3 })
+ end
+
+ it 'should not change the original hashes' do
+ hash1 = {'one' => { 'two' => 2 } }
+ hash2 = { 'one' => { 'three' => 3 } }
+ hash = scope.function_deep_merge([hash1, hash2])
+ expect(hash1).to eq({'one' => { 'two' => 2 } })
+ expect(hash2).to eq({ 'one' => { 'three' => 3 } })
+ expect(hash['one']).to eq({ 'two' => 2, 'three' => 3 })
+ end
+
+ it 'should not change the original hashes 2' do
+ hash1 = {'one' => { 'two' => [1,2] } }
+ hash2 = { 'one' => { 'three' => 3 } }
+ hash = scope.function_deep_merge([hash1, hash2])
+ expect(hash1).to eq({'one' => { 'two' => [1,2] } })
+ expect(hash2).to eq({ 'one' => { 'three' => 3 } })
+ expect(hash['one']).to eq({ 'two' => [1,2], 'three' => 3 })
+ end
+
+ it 'should not change the original hashes 3' do
+ hash1 = {'one' => { 'two' => [1,2, {'two' => 2} ] } }
+ hash2 = { 'one' => { 'three' => 3 } }
+ hash = scope.function_deep_merge([hash1, hash2])
+ expect(hash1).to eq({'one' => { 'two' => [1,2, {'two' => 2}] } })
+ expect(hash2).to eq({ 'one' => { 'three' => 3 } })
+ expect(hash['one']).to eq({ 'two' => [1,2, {'two' => 2} ], 'three' => 3 })
+ expect(hash['one']['two']).to eq([1,2, {'two' => 2}])
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/defined_with_params_spec.rb b/puppet/modules/stdlib/spec/functions/defined_with_params_spec.rb
new file mode 100755
index 00000000..35903047
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/defined_with_params_spec.rb
@@ -0,0 +1,37 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+require 'rspec-puppet'
+describe 'defined_with_params' do
+ describe 'when a resource is not specified' do
+ it { is_expected.to run.with_params().and_raise_error(ArgumentError) }
+ end
+ describe 'when compared against a resource with no attributes' do
+ let :pre_condition do
+ 'user { "dan": }'
+ end
+ it do
+ is_expected.to run.with_params('User[dan]', {}).and_return(true)
+ is_expected.to run.with_params('User[bob]', {}).and_return(false)
+ is_expected.to run.with_params('User[dan]', {'foo' => 'bar'}).and_return(false)
+ end
+ end
+
+ describe 'when compared against a resource with attributes' do
+ let :pre_condition do
+ 'user { "dan": ensure => present, shell => "/bin/csh", managehome => false}'
+ end
+ it do
+ is_expected.to run.with_params('User[dan]', {}).and_return(true)
+ is_expected.to run.with_params('User[dan]', '').and_return(true)
+ is_expected.to run.with_params('User[dan]', {'ensure' => 'present'}
+ ).and_return(true)
+ is_expected.to run.with_params('User[dan]',
+ {'ensure' => 'present', 'managehome' => false}
+ ).and_return(true)
+ is_expected.to run.with_params('User[dan]',
+ {'ensure' => 'absent', 'managehome' => false}
+ ).and_return(false)
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/delete_at_spec.rb b/puppet/modules/stdlib/spec/functions/delete_at_spec.rb
new file mode 100755
index 00000000..7c20aec4
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/delete_at_spec.rb
@@ -0,0 +1,25 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the delete_at function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("delete_at")).to eq("function_delete_at")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_delete_at([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should delete an item at specified location from an array" do
+ result = scope.function_delete_at([['a','b','c'],1])
+ expect(result).to(eq(['a','c']))
+ end
+
+ it "should not change origin array passed as argument" do
+ origin_array = ['a','b','c','d']
+ result = scope.function_delete_at([origin_array, 1])
+ expect(origin_array).to(eq(['a','b','c','d']))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/delete_spec.rb b/puppet/modules/stdlib/spec/functions/delete_spec.rb
new file mode 100755
index 00000000..c8edd78e
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/delete_spec.rb
@@ -0,0 +1,61 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the delete function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("delete")).to eq("function_delete")
+ end
+
+ it "should raise a ParseError if there are fewer than 2 arguments" do
+ expect { scope.function_delete([]) }.to(raise_error(Puppet::ParseError))
+ end
+
+ it "should raise a ParseError if there are greater than 2 arguments" do
+ expect { scope.function_delete([[], 'foo', 'bar']) }.to(raise_error(Puppet::ParseError))
+ end
+
+ it "should raise a TypeError if a number is passed as the first argument" do
+ expect { scope.function_delete([1, 'bar']) }.to(raise_error(TypeError))
+ end
+
+ it "should delete all instances of an element from an array" do
+ result = scope.function_delete([['a', 'b', 'c', 'b'], 'b'])
+ expect(result).to(eq(['a', 'c']))
+ end
+
+ it "should delete all instances of a substring from a string" do
+ result = scope.function_delete(['foobarbabarz', 'bar'])
+ expect(result).to(eq('foobaz'))
+ end
+
+ it "should delete a key from a hash" do
+ result = scope.function_delete([{'a' => 1, 'b' => 2, 'c' => 3}, 'b'])
+ expect(result).to(eq({'a' => 1, 'c' => 3}))
+ end
+
+ it 'should accept an array of items to delete' do
+ result = scope.function_delete([{'a' => 1, 'b' => 2, 'c' => 3}, ['b', 'c']])
+ expect(result).to(eq({'a' => 1}))
+ end
+
+ it "should not change origin array passed as argument" do
+ origin_array = ['a', 'b', 'c', 'd']
+ result = scope.function_delete([origin_array, 'b'])
+ expect(origin_array).to(eq(['a', 'b', 'c', 'd']))
+ end
+
+ it "should not change the origin string passed as argument" do
+ origin_string = 'foobarbabarz'
+ result = scope.function_delete([origin_string, 'bar'])
+ expect(origin_string).to(eq('foobarbabarz'))
+ end
+
+ it "should not change origin hash passed as argument" do
+ origin_hash = {'a' => 1, 'b' => 2, 'c' => 3}
+ result = scope.function_delete([origin_hash, 'b'])
+ expect(origin_hash).to(eq({'a' => 1, 'b' => 2, 'c' => 3}))
+ end
+
+end
diff --git a/puppet/modules/stdlib/spec/functions/delete_undef_values_spec.rb b/puppet/modules/stdlib/spec/functions/delete_undef_values_spec.rb
new file mode 100755
index 00000000..dc679535
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/delete_undef_values_spec.rb
@@ -0,0 +1,41 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the delete_undef_values function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("delete_undef_values")).to eq("function_delete_undef_values")
+ end
+
+ it "should raise a ParseError if there is less than 1 argument" do
+ expect { scope.function_delete_undef_values([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should raise a ParseError if the argument is not Array nor Hash" do
+ expect { scope.function_delete_undef_values(['']) }.to( raise_error(Puppet::ParseError))
+ expect { scope.function_delete_undef_values([nil]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should delete all undef items from Array and only these" do
+ result = scope.function_delete_undef_values([['a',:undef,'c','undef']])
+ expect(result).to(eq(['a','c','undef']))
+ end
+
+ it "should delete all undef items from Hash and only these" do
+ result = scope.function_delete_undef_values([{'a'=>'A','b'=>:undef,'c'=>'C','d'=>'undef'}])
+ expect(result).to(eq({'a'=>'A','c'=>'C','d'=>'undef'}))
+ end
+
+ it "should not change origin array passed as argument" do
+ origin_array = ['a',:undef,'c','undef']
+ result = scope.function_delete_undef_values([origin_array])
+ expect(origin_array).to(eq(['a',:undef,'c','undef']))
+ end
+
+ it "should not change origin hash passed as argument" do
+ origin_hash = { 'a' => 1, 'b' => :undef, 'c' => 'undef' }
+ result = scope.function_delete_undef_values([origin_hash])
+ expect(origin_hash).to(eq({ 'a' => 1, 'b' => :undef, 'c' => 'undef' }))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/delete_values_spec.rb b/puppet/modules/stdlib/spec/functions/delete_values_spec.rb
new file mode 100755
index 00000000..4f4d411b
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/delete_values_spec.rb
@@ -0,0 +1,36 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the delete_values function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("delete_values")).to eq("function_delete_values")
+ end
+
+ it "should raise a ParseError if there are fewer than 2 arguments" do
+ expect { scope.function_delete_values([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should raise a ParseError if there are greater than 2 arguments" do
+ expect { scope.function_delete_values([[], 'foo', 'bar']) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should raise a TypeError if the argument is not a hash" do
+ expect { scope.function_delete_values([1,'bar']) }.to( raise_error(TypeError))
+ expect { scope.function_delete_values(['foo','bar']) }.to( raise_error(TypeError))
+ expect { scope.function_delete_values([[],'bar']) }.to( raise_error(TypeError))
+ end
+
+ it "should delete all instances of a value from a hash" do
+ result = scope.function_delete_values([{ 'a'=>'A', 'b'=>'B', 'B'=>'C', 'd'=>'B' },'B'])
+ expect(result).to(eq({ 'a'=>'A', 'B'=>'C' }))
+ end
+
+ it "should not change origin hash passed as argument" do
+ origin_hash = { 'a' => 1, 'b' => 2, 'c' => 3 }
+ result = scope.function_delete_values([origin_hash, 2])
+ expect(origin_hash).to(eq({ 'a' => 1, 'b' => 2, 'c' => 3 }))
+ end
+
+end
diff --git a/puppet/modules/stdlib/spec/functions/difference_spec.rb b/puppet/modules/stdlib/spec/functions/difference_spec.rb
new file mode 100755
index 00000000..24b2b1bc
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/difference_spec.rb
@@ -0,0 +1,19 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the difference function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("difference")).to eq("function_difference")
+ end
+
+ it "should raise a ParseError if there are fewer than 2 arguments" do
+ expect { scope.function_difference([]) }.to( raise_error(Puppet::ParseError) )
+ end
+
+ it "should return the difference between two arrays" do
+ result = scope.function_difference([["a","b","c"],["b","c","d"]])
+ expect(result).to(eq(["a"]))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/dirname_spec.rb b/puppet/modules/stdlib/spec/functions/dirname_spec.rb
new file mode 100755
index 00000000..8a3bcabf
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/dirname_spec.rb
@@ -0,0 +1,24 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the dirname function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("dirname")).to eq("function_dirname")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_dirname([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should return dirname for an absolute path" do
+ result = scope.function_dirname(['/path/to/a/file.ext'])
+ expect(result).to(eq('/path/to/a'))
+ end
+
+ it "should return dirname for a relative path" do
+ result = scope.function_dirname(['path/to/a/file.ext'])
+ expect(result).to(eq('path/to/a'))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/downcase_spec.rb b/puppet/modules/stdlib/spec/functions/downcase_spec.rb
new file mode 100755
index 00000000..edebc44f
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/downcase_spec.rb
@@ -0,0 +1,33 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the downcase function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("downcase")).to eq("function_downcase")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_downcase([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should downcase a string" do
+ result = scope.function_downcase(["ASFD"])
+ expect(result).to(eq("asfd"))
+ end
+
+ it "should do nothing to a string that is already downcase" do
+ result = scope.function_downcase(["asdf asdf"])
+ expect(result).to(eq("asdf asdf"))
+ end
+
+ it "should accept objects which extend String" do
+ class AlsoString < String
+ end
+
+ value = AlsoString.new("ASFD")
+ result = scope.function_downcase([value])
+ result.should(eq('asfd'))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/empty_spec.rb b/puppet/modules/stdlib/spec/functions/empty_spec.rb
new file mode 100755
index 00000000..6a97c4cd
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/empty_spec.rb
@@ -0,0 +1,32 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the empty function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("empty")).to eq("function_empty")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_empty([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should return a true for an empty string" do
+ result = scope.function_empty([''])
+ expect(result).to(eq(true))
+ end
+
+ it "should return a false for a non-empty string" do
+ result = scope.function_empty(['asdf'])
+ expect(result).to(eq(false))
+ end
+
+ it "should accept objects which extend String" do
+ class AlsoString < String
+ end
+
+ value = AlsoString.new()
+ result = scope.function_empty([value])
+ result.should(eq(true))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/ensure_packages_spec.rb b/puppet/modules/stdlib/spec/functions/ensure_packages_spec.rb
new file mode 100755
index 00000000..436be10b
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/ensure_packages_spec.rb
@@ -0,0 +1,81 @@
+#! /usr/bin/env ruby
+
+require 'spec_helper'
+require 'rspec-puppet'
+require 'puppet_spec/compiler'
+
+describe 'ensure_packages' do
+ include PuppetSpec::Compiler
+
+ before :each do
+ Puppet::Parser::Functions.autoloader.loadall
+ Puppet::Parser::Functions.function(:ensure_packages)
+ Puppet::Parser::Functions.function(:ensure_resource)
+ Puppet::Parser::Functions.function(:defined_with_params)
+ Puppet::Parser::Functions.function(:create_resources)
+ end
+
+ let :node do Puppet::Node.new('localhost') end
+ let :compiler do Puppet::Parser::Compiler.new(node) end
+ let :scope do
+ if Puppet.version.to_f >= 3.0
+ Puppet::Parser::Scope.new(compiler)
+ else
+ newscope = Puppet::Parser::Scope.new
+ newscope.compiler = compiler
+ newscope.source = Puppet::Resource::Type.new(:node, :localhost)
+ newscope
+ end
+ end
+
+ describe 'argument handling' do
+ it 'fails with no arguments' do
+ expect {
+ scope.function_ensure_packages([])
+ }.to raise_error(Puppet::ParseError, /0 for 1 or 2/)
+ end
+
+ it 'accepts an array of values' do
+ scope.function_ensure_packages([['foo']])
+ end
+
+ it 'accepts a single package name as a string' do
+ scope.function_ensure_packages(['foo'])
+ end
+ end
+
+ context 'given a catalog with puppet package => absent' do
+ let :catalog do
+ compile_to_catalog(<<-EOS
+ ensure_packages(['facter'])
+ package { puppet: ensure => absent }
+ EOS
+ )
+ end
+
+ it 'has no effect on Package[puppet]' do
+ expect(catalog.resource(:package, 'puppet')['ensure']).to eq('absent')
+ end
+ end
+
+ context 'given a clean catalog' do
+ let :catalog do
+ compile_to_catalog('ensure_packages(["facter"])')
+ end
+
+ it 'declares package resources with ensure => present' do
+ expect(catalog.resource(:package, 'facter')['ensure']).to eq('present')
+ end
+ end
+
+ context 'given a clean catalog and specified defaults' do
+ let :catalog do
+ compile_to_catalog('ensure_packages(["facter"], {"provider" => "gem"})')
+ end
+
+ it 'declares package resources with ensure => present' do
+ expect(catalog.resource(:package, 'facter')['ensure']).to eq('present')
+ expect(catalog.resource(:package, 'facter')['provider']).to eq('gem')
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/ensure_resource_spec.rb b/puppet/modules/stdlib/spec/functions/ensure_resource_spec.rb
new file mode 100755
index 00000000..33bcac0d
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/ensure_resource_spec.rb
@@ -0,0 +1,113 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+require 'rspec-puppet'
+require 'puppet_spec/compiler'
+
+describe 'ensure_resource' do
+ include PuppetSpec::Compiler
+
+ before :all do
+ Puppet::Parser::Functions.autoloader.loadall
+ Puppet::Parser::Functions.function(:ensure_packages)
+ 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
+
+ describe 'when a type or title is not specified' do
+ it { expect { scope.function_ensure_resource([]) }.to raise_error }
+ it { expect { scope.function_ensure_resource(['type']) }.to raise_error }
+ end
+
+ describe 'when compared against a resource with no attributes' do
+ let :catalog do
+ compile_to_catalog(<<-EOS
+ user { "dan": }
+ ensure_resource('user', 'dan', {})
+ EOS
+ )
+ end
+
+ it 'should contain the the ensured resources' do
+ expect(catalog.resource(:user, 'dan').to_s).to eq('User[dan]')
+ end
+ end
+
+ describe 'works when compared against a resource with non-conflicting attributes' do
+ [
+ "ensure_resource('User', 'dan', {})",
+ "ensure_resource('User', 'dan', '')",
+ "ensure_resource('User', 'dan', {'ensure' => 'present'})",
+ "ensure_resource('User', 'dan', {'ensure' => 'present', 'managehome' => false})"
+ ].each do |ensure_resource|
+ pp = <<-EOS
+ user { "dan": ensure => present, shell => "/bin/csh", managehome => false}
+ #{ensure_resource}
+ EOS
+
+ it { expect { compile_to_catalog(pp) }.to_not raise_error }
+ end
+ end
+
+ describe 'fails when compared against a resource with conflicting attributes' do
+ pp = <<-EOS
+ user { "dan": ensure => present, shell => "/bin/csh", managehome => false}
+ ensure_resource('User', 'dan', {'ensure' => 'absent', 'managehome' => false})
+ EOS
+
+ it { expect { compile_to_catalog(pp) }.to raise_error }
+ end
+
+ describe 'when an array of new resources are passed in' do
+ let :catalog do
+ compile_to_catalog("ensure_resource('User', ['dan', 'alex'], {})")
+ end
+
+ it 'should contain the ensured resources' do
+ expect(catalog.resource('User[dan]').to_s).to eq('User[dan]')
+ expect(catalog.resource('User[alex]').to_s).to eq('User[alex]')
+ end
+ end
+
+ describe 'when an array of existing resources is compared against existing resources' do
+ pp = <<-EOS
+ user { 'dan': ensure => present; 'alex': ensure => present }
+ ensure_resource('User', ['dan', 'alex'], {})
+ EOS
+
+ let :catalog do
+ compile_to_catalog(pp)
+ end
+
+ it 'should return the existing resources' do
+ expect(catalog.resource('User[dan]').to_s).to eq('User[dan]')
+ expect(catalog.resource('User[alex]').to_s).to eq('User[alex]')
+ end
+ end
+
+ describe 'works when compared against existing resources with attributes' do
+ [
+ "ensure_resource('User', ['dan', 'alex'], {})",
+ "ensure_resource('User', ['dan', 'alex'], '')",
+ "ensure_resource('User', ['dan', 'alex'], {'ensure' => 'present'})",
+ ].each do |ensure_resource|
+ pp = <<-EOS
+ user { 'dan': ensure => present; 'alex': ensure => present }
+ #{ensure_resource}
+ EOS
+
+ it { expect { compile_to_catalog(pp) }.to_not raise_error }
+ end
+ end
+
+ describe 'fails when compared against existing resources with conflicting attributes' do
+ pp = <<-EOS
+ user { 'dan': ensure => present; 'alex': ensure => present }
+ ensure_resource('User', ['dan', 'alex'], {'ensure' => 'absent'})
+ EOS
+
+ it { expect { compile_to_catalog(pp) }.to raise_error }
+ end
+
+end
diff --git a/puppet/modules/stdlib/spec/functions/flatten_spec.rb b/puppet/modules/stdlib/spec/functions/flatten_spec.rb
new file mode 100755
index 00000000..de8c66d6
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/flatten_spec.rb
@@ -0,0 +1,27 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the flatten function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("flatten")).to eq("function_flatten")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_flatten([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should raise a ParseError if there is more than 1 argument" do
+ expect { scope.function_flatten([[], []]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should flatten a complex data structure" do
+ result = scope.function_flatten([["a","b",["c",["d","e"],"f","g"]]])
+ expect(result).to(eq(["a","b","c","d","e","f","g"]))
+ end
+
+ it "should do nothing to a structure that is already flat" do
+ result = scope.function_flatten([["a","b","c","d"]])
+ expect(result).to(eq(["a","b","c","d"]))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/floor_spec.rb b/puppet/modules/stdlib/spec/functions/floor_spec.rb
new file mode 100755
index 00000000..12a69179
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/floor_spec.rb
@@ -0,0 +1,39 @@
+#! /usr/bin/env ruby -S rspec
+
+require 'spec_helper'
+
+describe "the floor function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("floor")).to eq("function_floor")
+ end
+
+ it "should raise a ParseError if there is less than 1 argument" do
+ expect { scope.function_floor([]) }.to( raise_error(Puppet::ParseError, /Wrong number of arguments/))
+ end
+
+ it "should should raise a ParseError if input isn't numeric (eg. String)" do
+ expect { scope.function_floor(["foo"]) }.to( raise_error(Puppet::ParseError, /Wrong argument type/))
+ end
+
+ it "should should raise a ParseError if input isn't numeric (eg. Boolean)" do
+ expect { scope.function_floor([true]) }.to( raise_error(Puppet::ParseError, /Wrong argument type/))
+ end
+
+ it "should return an integer when a numeric type is passed" do
+ result = scope.function_floor([12.4])
+ expect(result.is_a?(Integer)).to(eq(true))
+ end
+
+ it "should return the input when an integer is passed" do
+ result = scope.function_floor([7])
+ expect(result).to(eq(7))
+ end
+
+ it "should return the largest integer less than or equal to the input" do
+ result = scope.function_floor([3.8])
+ expect(result).to(eq(3))
+ end
+end
+
diff --git a/puppet/modules/stdlib/spec/functions/fqdn_rotate_spec.rb b/puppet/modules/stdlib/spec/functions/fqdn_rotate_spec.rb
new file mode 100755
index 00000000..40057d4f
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/fqdn_rotate_spec.rb
@@ -0,0 +1,43 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the fqdn_rotate function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("fqdn_rotate")).to eq("function_fqdn_rotate")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_fqdn_rotate([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should rotate a string and the result should be the same size" do
+ scope.expects(:lookupvar).with("::fqdn").returns("127.0.0.1")
+ result = scope.function_fqdn_rotate(["asdf"])
+ expect(result.size).to(eq(4))
+ end
+
+ it "should rotate a string to give the same results for one host" do
+ scope.expects(:lookupvar).with("::fqdn").returns("127.0.0.1").twice
+ expect(scope.function_fqdn_rotate(["abcdefg"])).to eql(scope.function_fqdn_rotate(["abcdefg"]))
+ end
+
+ it "should rotate a string to give different values on different hosts" do
+ scope.expects(:lookupvar).with("::fqdn").returns("127.0.0.1")
+ val1 = scope.function_fqdn_rotate(["abcdefghijklmnopqrstuvwxyz01234567890987654321"])
+ scope.expects(:lookupvar).with("::fqdn").returns("127.0.0.2")
+ val2 = scope.function_fqdn_rotate(["abcdefghijklmnopqrstuvwxyz01234567890987654321"])
+ expect(val1).not_to eql(val2)
+ end
+
+ it "should accept objects which extend String" do
+ class AlsoString < String
+ end
+
+ scope.expects(:lookupvar).with("::fqdn").returns("127.0.0.1")
+ value = AlsoString.new("asdf")
+ result = scope.function_fqdn_rotate([value])
+ result.size.should(eq(4))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/get_module_path_spec.rb b/puppet/modules/stdlib/spec/functions/get_module_path_spec.rb
new file mode 100755
index 00000000..38ce6459
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/get_module_path_spec.rb
@@ -0,0 +1,46 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe Puppet::Parser::Functions.function(:get_module_path) do
+ Internals = PuppetlabsSpec::PuppetInternals
+ class StubModule
+ attr_reader :path
+ def initialize(path)
+ @path = path
+ end
+ end
+
+ def scope(environment = "production")
+ Internals.scope(:compiler => Internals.compiler(:node => Internals.node(:environment => environment)))
+ end
+
+ it 'should only allow one argument' do
+ expect { scope.function_get_module_path([]) }.to raise_error(Puppet::ParseError, /Wrong number of arguments, expects one/)
+ expect { scope.function_get_module_path(['1','2','3']) }.to raise_error(Puppet::ParseError, /Wrong number of arguments, expects one/)
+ end
+ it 'should raise an exception when the module cannot be found' do
+ expect { scope.function_get_module_path(['foo']) }.to raise_error(Puppet::ParseError, /Could not find module/)
+ end
+ describe 'when locating a module' do
+ let(:modulepath) { "/tmp/does_not_exist" }
+ let(:path_of_module_foo) { StubModule.new("/tmp/does_not_exist/foo") }
+
+ before(:each) { Puppet[:modulepath] = modulepath }
+
+ it 'should be able to find module paths from the modulepath setting' do
+ Puppet::Module.expects(:find).with('foo', 'production').returns(path_of_module_foo)
+ expect(scope.function_get_module_path(['foo'])).to eq(path_of_module_foo.path)
+ end
+ it 'should be able to find module paths when the modulepath is a list' do
+ Puppet[:modulepath] = modulepath + ":/tmp"
+ Puppet::Module.expects(:find).with('foo', 'production').returns(path_of_module_foo)
+ expect(scope.function_get_module_path(['foo'])).to eq(path_of_module_foo.path)
+ end
+ it 'should respect the environment' do
+ skip("Disabled on Puppet 2.6.x") if Puppet.version =~ /^2\.6\b/
+ Puppet.settings[:environment] = 'danstestenv'
+ Puppet::Module.expects(:find).with('foo', 'danstestenv').returns(path_of_module_foo)
+ expect(scope('danstestenv').function_get_module_path(['foo'])).to eq(path_of_module_foo.path)
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/getparam_spec.rb b/puppet/modules/stdlib/spec/functions/getparam_spec.rb
new file mode 100755
index 00000000..833c4d4f
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/getparam_spec.rb
@@ -0,0 +1,76 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+require 'rspec-puppet'
+require 'puppet_spec/compiler'
+
+describe 'getparam' do
+ include PuppetSpec::Compiler
+
+ before :each do
+ Puppet::Parser::Functions.autoloader.loadall
+ Puppet::Parser::Functions.function(:getparam)
+ end
+
+ let :node do Puppet::Node.new('localhost') end
+ let :compiler do Puppet::Parser::Compiler.new(node) end
+ if Puppet.version.to_f >= 3.0
+ let :scope do Puppet::Parser::Scope.new(compiler) end
+ else
+ let :scope do
+ newscope = Puppet::Parser::Scope.new
+ newscope.compiler = compiler
+ newscope.source = Puppet::Resource::Type.new(:node, :localhost)
+ newscope
+ end
+ end
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("getparam")).to eq("function_getparam")
+ end
+
+ describe 'when a resource is not specified' do
+ it { expect { scope.function_getparam([]) }.to raise_error }
+ it { expect { scope.function_getparam(['User[dan]']) }.to raise_error }
+ it { expect { scope.function_getparam(['User[dan]']) }.to raise_error }
+ it { expect { scope.function_getparam(['User[dan]', {}]) }.to raise_error }
+ # This seems to be OK because we just check for a string.
+ it { expect { scope.function_getparam(['User[dan]', '']) }.to_not raise_error }
+ end
+
+ describe 'when compared against a resource with no params' do
+ let :catalog do
+ compile_to_catalog(<<-EOS
+ user { "dan": }
+ EOS
+ )
+ end
+
+ it do
+ expect(scope.function_getparam(['User[dan]', 'shell'])).to eq('')
+ end
+ end
+
+ describe 'when compared against a resource with params' do
+ let :catalog do
+ compile_to_catalog(<<-EOS
+ user { 'dan': ensure => present, shell => '/bin/sh', managehome => false}
+ $test = getparam(User[dan], 'shell')
+ EOS
+ )
+ end
+
+ it do
+ resource = Puppet::Parser::Resource.new(:user, 'dan', {:scope => scope})
+ resource.set_parameter('ensure', 'present')
+ resource.set_parameter('shell', '/bin/sh')
+ resource.set_parameter('managehome', false)
+ compiler.add_resource(scope, resource)
+
+ expect(scope.function_getparam(['User[dan]', 'shell'])).to eq('/bin/sh')
+ expect(scope.function_getparam(['User[dan]', ''])).to eq('')
+ expect(scope.function_getparam(['User[dan]', 'ensure'])).to eq('present')
+ # TODO: Expected this to be false, figure out why we're getting '' back.
+ expect(scope.function_getparam(['User[dan]', 'managehome'])).to eq('')
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/getvar_spec.rb b/puppet/modules/stdlib/spec/functions/getvar_spec.rb
new file mode 100755
index 00000000..87ab9b5a
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/getvar_spec.rb
@@ -0,0 +1,37 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe Puppet::Parser::Functions.function(:getvar) do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+ describe 'when calling getvar from puppet' do
+
+ it "should not compile when no arguments are passed" do
+ skip("Fails on 2.6.x, see bug #15912") if Puppet.version =~ /^2\.6\./
+ Puppet[:code] = '$foo = getvar()'
+ expect {
+ scope.compiler.compile
+ }.to raise_error(Puppet::ParseError, /wrong number of arguments/)
+ end
+
+ it "should not compile when too many arguments are passed" do
+ skip("Fails on 2.6.x, see bug #15912") if Puppet.version =~ /^2\.6\./
+ Puppet[:code] = '$foo = getvar("foo::bar", "baz")'
+ expect {
+ scope.compiler.compile
+ }.to raise_error(Puppet::ParseError, /wrong number of arguments/)
+ end
+
+ it "should lookup variables in other namespaces" do
+ skip("Fails on 2.6.x, see bug #15912") if Puppet.version =~ /^2\.6\./
+ Puppet[:code] = <<-'ENDofPUPPETcode'
+ class site::data { $foo = 'baz' }
+ include site::data
+ $foo = getvar("site::data::foo")
+ if $foo != 'baz' {
+ fail('getvar did not return what we expect')
+ }
+ ENDofPUPPETcode
+ scope.compiler.compile
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/grep_spec.rb b/puppet/modules/stdlib/spec/functions/grep_spec.rb
new file mode 100755
index 00000000..9c671dd8
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/grep_spec.rb
@@ -0,0 +1,19 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the grep function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("grep")).to eq("function_grep")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_grep([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should grep contents from an array" do
+ result = scope.function_grep([["aaabbb","bbbccc","dddeee"], "bbb"])
+ expect(result).to(eq(["aaabbb","bbbccc"]))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/has_interface_with_spec.rb b/puppet/modules/stdlib/spec/functions/has_interface_with_spec.rb
new file mode 100755
index 00000000..23e09a95
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/has_interface_with_spec.rb
@@ -0,0 +1,64 @@
+#!/usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe Puppet::Parser::Functions.function(:has_interface_with) do
+
+ let(:scope) do
+ PuppetlabsSpec::PuppetInternals.scope
+ end
+
+ # The subject of these examples is the method itself.
+ subject do
+ function_name = Puppet::Parser::Functions.function(:has_interface_with)
+ scope.method(function_name)
+ end
+
+ # We need to mock out the Facts so we can specify how we expect this function
+ # to behave on different platforms.
+ context "On Mac OS X Systems" do
+ before :each do
+ scope.stubs(:lookupvar).with("interfaces").returns('lo0,gif0,stf0,en1,p2p0,fw0,en0,vmnet1,vmnet8,utun0')
+ end
+ it 'should have loopback (lo0)' do
+ expect(subject.call(['lo0'])).to be_truthy
+ end
+ it 'should not have loopback (lo)' do
+ expect(subject.call(['lo'])).to be_falsey
+ end
+ end
+ context "On Linux Systems" do
+ before :each do
+ scope.stubs(:lookupvar).with("interfaces").returns('eth0,lo')
+ scope.stubs(:lookupvar).with("ipaddress").returns('10.0.0.1')
+ scope.stubs(:lookupvar).with("ipaddress_lo").returns('127.0.0.1')
+ scope.stubs(:lookupvar).with("ipaddress_eth0").returns('10.0.0.1')
+ scope.stubs(:lookupvar).with('muppet').returns('kermit')
+ scope.stubs(:lookupvar).with('muppet_lo').returns('mspiggy')
+ scope.stubs(:lookupvar).with('muppet_eth0').returns('kermit')
+ end
+ it 'should have loopback (lo)' do
+ expect(subject.call(['lo'])).to be_truthy
+ end
+ it 'should not have loopback (lo0)' do
+ expect(subject.call(['lo0'])).to be_falsey
+ end
+ it 'should have ipaddress with 127.0.0.1' do
+ expect(subject.call(['ipaddress', '127.0.0.1'])).to be_truthy
+ end
+ it 'should have ipaddress with 10.0.0.1' do
+ expect(subject.call(['ipaddress', '10.0.0.1'])).to be_truthy
+ end
+ it 'should not have ipaddress with 10.0.0.2' do
+ expect(subject.call(['ipaddress', '10.0.0.2'])).to be_falsey
+ end
+ it 'should have muppet named kermit' do
+ expect(subject.call(['muppet', 'kermit'])).to be_truthy
+ end
+ it 'should have muppet named mspiggy' do
+ expect(subject.call(['muppet', 'mspiggy'])).to be_truthy
+ end
+ it 'should not have muppet named bigbird' do
+ expect(subject.call(['muppet', 'bigbird'])).to be_falsey
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/has_ip_address_spec.rb b/puppet/modules/stdlib/spec/functions/has_ip_address_spec.rb
new file mode 100755
index 00000000..0df12a7b
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/has_ip_address_spec.rb
@@ -0,0 +1,39 @@
+#!/usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe Puppet::Parser::Functions.function(:has_ip_address) do
+
+ let(:scope) do
+ PuppetlabsSpec::PuppetInternals.scope
+ end
+
+ subject do
+ function_name = Puppet::Parser::Functions.function(:has_ip_address)
+ scope.method(function_name)
+ end
+
+ context "On Linux Systems" do
+ before :each do
+ scope.stubs(:lookupvar).with('interfaces').returns('eth0,lo')
+ scope.stubs(:lookupvar).with('ipaddress').returns('10.0.2.15')
+ scope.stubs(:lookupvar).with('ipaddress_eth0').returns('10.0.2.15')
+ scope.stubs(:lookupvar).with('ipaddress_lo').returns('127.0.0.1')
+ end
+
+ it 'should have primary address (10.0.2.15)' do
+ expect(subject.call(['10.0.2.15'])).to be_truthy
+ end
+
+ it 'should have lookupback address (127.0.0.1)' do
+ expect(subject.call(['127.0.0.1'])).to be_truthy
+ end
+
+ it 'should not have other address' do
+ expect(subject.call(['192.1681.1.1'])).to be_falsey
+ end
+
+ it 'should not have "mspiggy" on an interface' do
+ expect(subject.call(['mspiggy'])).to be_falsey
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/has_ip_network_spec.rb b/puppet/modules/stdlib/spec/functions/has_ip_network_spec.rb
new file mode 100755
index 00000000..2a2578e2
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/has_ip_network_spec.rb
@@ -0,0 +1,36 @@
+#!/usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe Puppet::Parser::Functions.function(:has_ip_network) do
+
+ let(:scope) do
+ PuppetlabsSpec::PuppetInternals.scope
+ end
+
+ subject do
+ function_name = Puppet::Parser::Functions.function(:has_ip_network)
+ scope.method(function_name)
+ end
+
+ context "On Linux Systems" do
+ before :each do
+ scope.stubs(:lookupvar).with('interfaces').returns('eth0,lo')
+ scope.stubs(:lookupvar).with('network').returns(:undefined)
+ scope.stubs(:lookupvar).with('network_eth0').returns('10.0.2.0')
+ scope.stubs(:lookupvar).with('network_lo').returns('127.0.0.1')
+ end
+
+ it 'should have primary network (10.0.2.0)' do
+ expect(subject.call(['10.0.2.0'])).to be_truthy
+ end
+
+ it 'should have loopback network (127.0.0.0)' do
+ expect(subject.call(['127.0.0.1'])).to be_truthy
+ end
+
+ it 'should not have other network' do
+ expect(subject.call(['192.168.1.0'])).to be_falsey
+ end
+ end
+end
+
diff --git a/puppet/modules/stdlib/spec/functions/has_key_spec.rb b/puppet/modules/stdlib/spec/functions/has_key_spec.rb
new file mode 100755
index 00000000..6b718005
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/has_key_spec.rb
@@ -0,0 +1,42 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe Puppet::Parser::Functions.function(:has_key) do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ describe 'when calling has_key from puppet' do
+ it "should not compile when no arguments are passed" do
+ skip("Fails on 2.6.x, see bug #15912") if Puppet.version =~ /^2\.6\./
+ Puppet[:code] = '$x = has_key()'
+ expect {
+ scope.compiler.compile
+ }.to raise_error(Puppet::ParseError, /wrong number of arguments/)
+ end
+
+ it "should not compile when 1 argument is passed" do
+ skip("Fails on 2.6.x, see bug #15912") if Puppet.version =~ /^2\.6\./
+ Puppet[:code] = "$x = has_key('foo')"
+ expect {
+ scope.compiler.compile
+ }.to raise_error(Puppet::ParseError, /wrong number of arguments/)
+ end
+
+ it "should require the first value to be a Hash" do
+ skip("Fails on 2.6.x, see bug #15912") if Puppet.version =~ /^2\.6\./
+ Puppet[:code] = "$x = has_key('foo', 'bar')"
+ expect {
+ scope.compiler.compile
+ }.to raise_error(Puppet::ParseError, /expects the first argument to be a hash/)
+ end
+ end
+
+ describe 'when calling the function has_key from a scope instance' do
+ it 'should detect existing keys' do
+ expect(scope.function_has_key([{'one' => 1}, 'one'])).to be_truthy
+ end
+
+ it 'should detect existing keys' do
+ expect(scope.function_has_key([{'one' => 1}, 'two'])).to be_falsey
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/hash_spec.rb b/puppet/modules/stdlib/spec/functions/hash_spec.rb
new file mode 100755
index 00000000..ec2988b0
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/hash_spec.rb
@@ -0,0 +1,19 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the hash function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("hash")).to eq("function_hash")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_hash([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should convert an array to a hash" do
+ result = scope.function_hash([['a',1,'b',2,'c',3]])
+ expect(result).to(eq({'a'=>1,'b'=>2,'c'=>3}))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/intersection_spec.rb b/puppet/modules/stdlib/spec/functions/intersection_spec.rb
new file mode 100755
index 00000000..6361304f
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/intersection_spec.rb
@@ -0,0 +1,19 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the intersection function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("intersection")).to eq("function_intersection")
+ end
+
+ it "should raise a ParseError if there are fewer than 2 arguments" do
+ expect { scope.function_intersection([]) }.to( raise_error(Puppet::ParseError) )
+ end
+
+ it "should return the intersection of two arrays" do
+ result = scope.function_intersection([["a","b","c"],["b","c","d"]])
+ expect(result).to(eq(["b","c"]))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/is_array_spec.rb b/puppet/modules/stdlib/spec/functions/is_array_spec.rb
new file mode 100755
index 00000000..94920a4c
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/is_array_spec.rb
@@ -0,0 +1,29 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the is_array function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("is_array")).to eq("function_is_array")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_is_array([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should return true if passed an array" do
+ result = scope.function_is_array([[1,2,3]])
+ expect(result).to(eq(true))
+ end
+
+ it "should return false if passed a hash" do
+ result = scope.function_is_array([{'a'=>1}])
+ expect(result).to(eq(false))
+ end
+
+ it "should return false if passed a string" do
+ result = scope.function_is_array(["asdf"])
+ expect(result).to(eq(false))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/is_bool_spec.rb b/puppet/modules/stdlib/spec/functions/is_bool_spec.rb
new file mode 100755
index 00000000..4a342ba4
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/is_bool_spec.rb
@@ -0,0 +1,44 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the is_bool function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("is_bool")).to eq("function_is_bool")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_is_bool([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should return true if passed a TrueClass" do
+ result = scope.function_is_bool([true])
+ expect(result).to(eq(true))
+ end
+
+ it "should return true if passed a FalseClass" do
+ result = scope.function_is_bool([false])
+ expect(result).to(eq(true))
+ end
+
+ it "should return false if passed the string 'true'" do
+ result = scope.function_is_bool(['true'])
+ expect(result).to(eq(false))
+ end
+
+ it "should return false if passed the string 'false'" do
+ result = scope.function_is_bool(['false'])
+ expect(result).to(eq(false))
+ end
+
+ it "should return false if passed an array" do
+ result = scope.function_is_bool([["a","b"]])
+ expect(result).to(eq(false))
+ end
+
+ it "should return false if passed a hash" do
+ result = scope.function_is_bool([{"a" => "b"}])
+ expect(result).to(eq(false))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/is_domain_name_spec.rb b/puppet/modules/stdlib/spec/functions/is_domain_name_spec.rb
new file mode 100755
index 00000000..4d05f5cd
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/is_domain_name_spec.rb
@@ -0,0 +1,64 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the is_domain_name function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("is_domain_name")).to eq("function_is_domain_name")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_is_domain_name([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should return true if a valid short domain name" do
+ result = scope.function_is_domain_name(["x.com"])
+ expect(result).to(be_truthy)
+ end
+
+ it "should return true if the domain is ." do
+ result = scope.function_is_domain_name(["."])
+ expect(result).to(be_truthy)
+ end
+
+ it "should return true if the domain is x.com." do
+ result = scope.function_is_domain_name(["x.com."])
+ expect(result).to(be_truthy)
+ end
+
+ it "should return true if a valid domain name" do
+ result = scope.function_is_domain_name(["foo.bar.com"])
+ expect(result).to(be_truthy)
+ end
+
+ it "should allow domain parts to start with numbers" do
+ result = scope.function_is_domain_name(["3foo.2bar.com"])
+ expect(result).to(be_truthy)
+ end
+
+ it "should allow domain to end with a dot" do
+ result = scope.function_is_domain_name(["3foo.2bar.com."])
+ expect(result).to(be_truthy)
+ end
+
+ it "should allow a single part domain" do
+ result = scope.function_is_domain_name(["orange"])
+ expect(result).to(be_truthy)
+ end
+
+ it "should return false if domain parts start with hyphens" do
+ result = scope.function_is_domain_name(["-3foo.2bar.com"])
+ expect(result).to(be_falsey)
+ end
+
+ it "should return true if domain contains hyphens" do
+ result = scope.function_is_domain_name(["3foo-bar.2bar-fuzz.com"])
+ expect(result).to(be_truthy)
+ end
+
+ it "should return false if domain name contains spaces" do
+ result = scope.function_is_domain_name(["not valid"])
+ expect(result).to(be_falsey)
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/is_float_spec.rb b/puppet/modules/stdlib/spec/functions/is_float_spec.rb
new file mode 100755
index 00000000..d926634e
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/is_float_spec.rb
@@ -0,0 +1,33 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the is_float function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("is_float")).to eq("function_is_float")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_is_float([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should return true if a float" do
+ result = scope.function_is_float(["0.12"])
+ expect(result).to(eq(true))
+ end
+
+ it "should return false if a string" do
+ result = scope.function_is_float(["asdf"])
+ expect(result).to(eq(false))
+ end
+
+ it "should return false if an integer" do
+ result = scope.function_is_float(["3"])
+ expect(result).to(eq(false))
+ end
+ it "should return true if a float is created from an arithmetical operation" do
+ result = scope.function_is_float([3.2*2])
+ expect(result).to(eq(true))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/is_function_available.rb b/puppet/modules/stdlib/spec/functions/is_function_available.rb
new file mode 100755
index 00000000..3a9aa1b2
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/is_function_available.rb
@@ -0,0 +1,31 @@
+#!/usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the is_function_available function" do
+ before :all do
+ Puppet::Parser::Functions.autoloader.loadall
+ end
+
+ before :each do
+ @scope = Puppet::Parser::Scope.new
+ end
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("is_function_available")).to eq("function_is_function_available")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { @scope.function_is_function_available([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should return false if a nonexistent function is passed" do
+ result = @scope.function_is_function_available(['jeff_mccunes_left_sock'])
+ expect(result).to(eq(false))
+ end
+
+ it "should return true if an available function is passed" do
+ result = @scope.function_is_function_available(['require'])
+ expect(result).to(eq(true))
+ end
+
+end
diff --git a/puppet/modules/stdlib/spec/functions/is_hash_spec.rb b/puppet/modules/stdlib/spec/functions/is_hash_spec.rb
new file mode 100755
index 00000000..a8494111
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/is_hash_spec.rb
@@ -0,0 +1,29 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the is_hash function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("is_hash")).to eq("function_is_hash")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_is_hash([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should return true if passed a hash" do
+ result = scope.function_is_hash([{"a"=>1,"b"=>2}])
+ expect(result).to(eq(true))
+ end
+
+ it "should return false if passed an array" do
+ result = scope.function_is_hash([["a","b"]])
+ expect(result).to(eq(false))
+ end
+
+ it "should return false if passed a string" do
+ result = scope.function_is_hash(["asdf"])
+ expect(result).to(eq(false))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/is_integer_spec.rb b/puppet/modules/stdlib/spec/functions/is_integer_spec.rb
new file mode 100755
index 00000000..f0cbca80
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/is_integer_spec.rb
@@ -0,0 +1,69 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the is_integer function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("is_integer")).to eq("function_is_integer")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_is_integer([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should return true if an integer" do
+ result = scope.function_is_integer(["3"])
+ expect(result).to(eq(true))
+ end
+
+ it "should return true if a negative integer" do
+ result = scope.function_is_integer(["-7"])
+ expect(result).to(eq(true))
+ end
+
+ it "should return false if a float" do
+ result = scope.function_is_integer(["3.2"])
+ expect(result).to(eq(false))
+ end
+
+ it "should return false if a string" do
+ result = scope.function_is_integer(["asdf"])
+ expect(result).to(eq(false))
+ end
+
+ it "should return true if an integer is created from an arithmetical operation" do
+ result = scope.function_is_integer([3*2])
+ expect(result).to(eq(true))
+ end
+
+ it "should return false if an array" do
+ result = scope.function_is_numeric([["asdf"]])
+ expect(result).to(eq(false))
+ end
+
+ it "should return false if a hash" do
+ result = scope.function_is_numeric([{"asdf" => false}])
+ expect(result).to(eq(false))
+ end
+
+ it "should return false if a boolean" do
+ result = scope.function_is_numeric([true])
+ expect(result).to(eq(false))
+ end
+
+ it "should return false if a whitespace is in the string" do
+ result = scope.function_is_numeric([" -1324"])
+ expect(result).to(eq(false))
+ end
+
+ it "should return false if it is zero prefixed" do
+ result = scope.function_is_numeric(["0001234"])
+ expect(result).to(eq(false))
+ end
+
+ it "should return false if it is wrapped inside an array" do
+ result = scope.function_is_numeric([[1234]])
+ expect(result).to(eq(false))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/is_ip_address_spec.rb b/puppet/modules/stdlib/spec/functions/is_ip_address_spec.rb
new file mode 100755
index 00000000..c16d12b1
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/is_ip_address_spec.rb
@@ -0,0 +1,39 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the is_ip_address function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("is_ip_address")).to eq("function_is_ip_address")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_is_ip_address([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should return true if an IPv4 address" do
+ result = scope.function_is_ip_address(["1.2.3.4"])
+ expect(result).to(eq(true))
+ end
+
+ it "should return true if a full IPv6 address" do
+ result = scope.function_is_ip_address(["fe80:0000:cd12:d123:e2f8:47ff:fe09:dd74"])
+ expect(result).to(eq(true))
+ end
+
+ it "should return true if a compressed IPv6 address" do
+ result = scope.function_is_ip_address(["fe00::1"])
+ expect(result).to(eq(true))
+ end
+
+ it "should return false if not valid" do
+ result = scope.function_is_ip_address(["asdf"])
+ expect(result).to(eq(false))
+ end
+
+ it "should return false if IP octets out of range" do
+ result = scope.function_is_ip_address(["1.1.1.300"])
+ expect(result).to(eq(false))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/is_mac_address_spec.rb b/puppet/modules/stdlib/spec/functions/is_mac_address_spec.rb
new file mode 100755
index 00000000..66edd197
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/is_mac_address_spec.rb
@@ -0,0 +1,29 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the is_mac_address function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("is_mac_address")).to eq("function_is_mac_address")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_is_mac_address([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should return true if a valid mac address" do
+ result = scope.function_is_mac_address(["00:a0:1f:12:7f:a0"])
+ expect(result).to(eq(true))
+ end
+
+ it "should return false if octets are out of range" do
+ result = scope.function_is_mac_address(["00:a0:1f:12:7f:g0"])
+ expect(result).to(eq(false))
+ end
+
+ it "should return false if not valid" do
+ result = scope.function_is_mac_address(["not valid"])
+ expect(result).to(eq(false))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/is_numeric_spec.rb b/puppet/modules/stdlib/spec/functions/is_numeric_spec.rb
new file mode 100755
index 00000000..4176961d
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/is_numeric_spec.rb
@@ -0,0 +1,119 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the is_numeric function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("is_numeric")).to eq("function_is_numeric")
+ end
+
+ it "should raise a ParseError if there is less than 1 argument" do
+ expect { scope.function_is_numeric([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should return true if an integer" do
+ result = scope.function_is_numeric(["3"])
+ expect(result).to(eq(true))
+ end
+
+ it "should return true if a float" do
+ result = scope.function_is_numeric(["3.2"])
+ expect(result).to(eq(true))
+ end
+
+ it "should return true if an integer is created from an arithmetical operation" do
+ result = scope.function_is_numeric([3*2])
+ expect(result).to(eq(true))
+ end
+
+ it "should return true if a float is created from an arithmetical operation" do
+ result = scope.function_is_numeric([3.2*2])
+ expect(result).to(eq(true))
+ end
+
+ it "should return false if a string" do
+ result = scope.function_is_numeric(["asdf"])
+ expect(result).to(eq(false))
+ end
+
+ it "should return false if an array" do
+ result = scope.function_is_numeric([["asdf"]])
+ expect(result).to(eq(false))
+ end
+
+ it "should return false if an array of integers" do
+ result = scope.function_is_numeric([[1,2,3,4]])
+ expect(result).to(eq(false))
+ end
+
+ it "should return false if a hash" do
+ result = scope.function_is_numeric([{"asdf" => false}])
+ expect(result).to(eq(false))
+ end
+
+ it "should return false if a hash with numbers in it" do
+ result = scope.function_is_numeric([{1 => 2}])
+ expect(result).to(eq(false))
+ end
+
+ it "should return false if a boolean" do
+ result = scope.function_is_numeric([true])
+ expect(result).to(eq(false))
+ end
+
+ it "should return true if a negative float with exponent" do
+ result = scope.function_is_numeric(["-342.2315e-12"])
+ expect(result).to(eq(true))
+ end
+
+ it "should return false if a negative integer with whitespaces before/after the dash" do
+ result = scope.function_is_numeric([" - 751"])
+ expect(result).to(eq(false))
+ end
+
+# it "should return true if a hexadecimal" do
+# result = scope.function_is_numeric(["0x52F8c"])
+# result.should(eq(true))
+# end
+#
+# it "should return true if a hexadecimal with uppercase 0X prefix" do
+# result = scope.function_is_numeric(["0X52F8c"])
+# result.should(eq(true))
+# end
+#
+# it "should return false if a hexadecimal without a prefix" do
+# result = scope.function_is_numeric(["52F8c"])
+# result.should(eq(false))
+# end
+#
+# it "should return true if a octal" do
+# result = scope.function_is_numeric(["0751"])
+# result.should(eq(true))
+# end
+#
+# it "should return true if a negative hexadecimal" do
+# result = scope.function_is_numeric(["-0x52F8c"])
+# result.should(eq(true))
+# end
+#
+# it "should return true if a negative octal" do
+# result = scope.function_is_numeric(["-0751"])
+# result.should(eq(true))
+# end
+#
+# it "should return false if a negative octal with whitespaces before/after the dash" do
+# result = scope.function_is_numeric([" - 0751"])
+# result.should(eq(false))
+# end
+#
+# it "should return false if a bad hexadecimal" do
+# result = scope.function_is_numeric(["0x23d7g"])
+# result.should(eq(false))
+# end
+#
+# it "should return false if a bad octal" do
+# result = scope.function_is_numeric(["0287"])
+# result.should(eq(false))
+# end
+end
diff --git a/puppet/modules/stdlib/spec/functions/is_string_spec.rb b/puppet/modules/stdlib/spec/functions/is_string_spec.rb
new file mode 100755
index 00000000..6a0801ae
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/is_string_spec.rb
@@ -0,0 +1,34 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the is_string function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("is_string")).to eq("function_is_string")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_is_string([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should return true if a string" do
+ result = scope.function_is_string(["asdf"])
+ expect(result).to(eq(true))
+ end
+
+ it "should return false if an integer" do
+ result = scope.function_is_string(["3"])
+ expect(result).to(eq(false))
+ end
+
+ it "should return false if a float" do
+ result = scope.function_is_string(["3.23"])
+ expect(result).to(eq(false))
+ end
+
+ it "should return false if an array" do
+ result = scope.function_is_string([["a","b","c"]])
+ expect(result).to(eq(false))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/join_keys_to_values_spec.rb b/puppet/modules/stdlib/spec/functions/join_keys_to_values_spec.rb
new file mode 100755
index 00000000..4a9ae87a
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/join_keys_to_values_spec.rb
@@ -0,0 +1,40 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the join_keys_to_values function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("join_keys_to_values")).to eq("function_join_keys_to_values")
+ end
+
+ it "should raise a ParseError if there are fewer than two arguments" do
+ expect { scope.function_join_keys_to_values([{}]) }.to raise_error Puppet::ParseError
+ end
+
+ it "should raise a ParseError if there are greater than two arguments" do
+ expect { scope.function_join_keys_to_values([{}, 'foo', 'bar']) }.to raise_error Puppet::ParseError
+ end
+
+ it "should raise a TypeError if the first argument is an array" do
+ expect { scope.function_join_keys_to_values([[1,2], ',']) }.to raise_error TypeError
+ end
+
+ it "should raise a TypeError if the second argument is an array" do
+ expect { scope.function_join_keys_to_values([{}, [1,2]]) }.to raise_error TypeError
+ end
+
+ it "should raise a TypeError if the second argument is a number" do
+ expect { scope.function_join_keys_to_values([{}, 1]) }.to raise_error TypeError
+ end
+
+ it "should return an empty array given an empty hash" do
+ result = scope.function_join_keys_to_values([{}, ":"])
+ expect(result).to eq([])
+ end
+
+ it "should join hash's keys to its values" do
+ result = scope.function_join_keys_to_values([{'a'=>1,2=>'foo',:b=>nil}, ":"])
+ expect(result).to match_array(['a:1','2:foo','b:'])
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/join_spec.rb b/puppet/modules/stdlib/spec/functions/join_spec.rb
new file mode 100755
index 00000000..793c36fa
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/join_spec.rb
@@ -0,0 +1,19 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the join function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("join")).to eq("function_join")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_join([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should join an array into a string" do
+ result = scope.function_join([["a","b","c"], ":"])
+ expect(result).to(eq("a:b:c"))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/keys_spec.rb b/puppet/modules/stdlib/spec/functions/keys_spec.rb
new file mode 100755
index 00000000..f2e7d428
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/keys_spec.rb
@@ -0,0 +1,21 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the keys function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("keys")).to eq("function_keys")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_keys([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should return an array of keys when given a hash" do
+ result = scope.function_keys([{'a'=>1, 'b'=>2}])
+ # =~ performs 'array with same elements' (set) matching
+ # For more info see RSpec::Matchers::MatchArray
+ expect(result).to match_array(['a','b'])
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/loadyaml_spec.rb b/puppet/modules/stdlib/spec/functions/loadyaml_spec.rb
new file mode 100755
index 00000000..cdc3d6f5
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/loadyaml_spec.rb
@@ -0,0 +1,25 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the loadyaml function" do
+ include PuppetlabsSpec::Files
+
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("loadyaml")).to eq("function_loadyaml")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_loadyaml([]) }.to raise_error(Puppet::ParseError)
+ end
+
+ it "should convert YAML file to a data structure" do
+ yaml_file = tmpfilename ('yamlfile')
+ File.open(yaml_file, 'w') do |fh|
+ fh.write("---\n aaa: 1\n bbb: 2\n ccc: 3\n ddd: 4\n")
+ end
+ result = scope.function_loadyaml([yaml_file])
+ expect(result).to eq({"aaa" => 1, "bbb" => 2, "ccc" => 3, "ddd" => 4 })
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/lstrip_spec.rb b/puppet/modules/stdlib/spec/functions/lstrip_spec.rb
new file mode 100755
index 00000000..68cca1c5
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/lstrip_spec.rb
@@ -0,0 +1,28 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the lstrip function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("lstrip")).to eq("function_lstrip")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_lstrip([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should lstrip a string" do
+ result = scope.function_lstrip([" asdf"])
+ expect(result).to(eq('asdf'))
+ end
+
+ it "should accept objects which extend String" do
+ class AlsoString < String
+ end
+
+ value = AlsoString.new(" asdf")
+ result = scope.function_lstrip([value])
+ result.should(eq("asdf"))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/max_spec.rb b/puppet/modules/stdlib/spec/functions/max_spec.rb
new file mode 100755
index 00000000..c3d8a132
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/max_spec.rb
@@ -0,0 +1,27 @@
+#! /usr/bin/env ruby -S rspec
+
+require 'spec_helper'
+
+describe "the max function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("max")).to eq("function_max")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_max([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should be able to compare strings" do
+ expect(scope.function_max(["albatross","dog","horse"])).to(eq("horse"))
+ end
+
+ it "should be able to compare numbers" do
+ expect(scope.function_max([6,8,4])).to(eq(8))
+ end
+
+ it "should be able to compare a number with a stringified number" do
+ expect(scope.function_max([1,"2"])).to(eq("2"))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/member_spec.rb b/puppet/modules/stdlib/spec/functions/member_spec.rb
new file mode 100755
index 00000000..1a1d7c66
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/member_spec.rb
@@ -0,0 +1,34 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the member function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("member")).to eq("function_member")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_member([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should return true if a member is in an array" do
+ result = scope.function_member([["a","b","c"], "a"])
+ expect(result).to(eq(true))
+ end
+
+ it "should return false if a member is not in an array" do
+ result = scope.function_member([["a","b","c"], "d"])
+ expect(result).to(eq(false))
+ end
+
+ it "should return true if a member array is in an array" do
+ result = scope.function_member([["a","b","c"], ["a", "b"]])
+ expect(result).to(eq(true))
+ end
+
+ it "should return false if a member array is not in an array" do
+ result = scope.function_member([["a","b","c"], ["d", "e"]])
+ expect(result).to(eq(false))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/merge_spec.rb b/puppet/modules/stdlib/spec/functions/merge_spec.rb
new file mode 100755
index 00000000..2abf9762
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/merge_spec.rb
@@ -0,0 +1,52 @@
+#! /usr/bin/env ruby -S rspec
+
+require 'spec_helper'
+
+describe Puppet::Parser::Functions.function(:merge) do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ describe 'when calling merge from puppet' do
+ it "should not compile when no arguments are passed" do
+ skip("Fails on 2.6.x, see bug #15912") if Puppet.version =~ /^2\.6\./
+ Puppet[:code] = '$x = merge()'
+ expect {
+ scope.compiler.compile
+ }.to raise_error(Puppet::ParseError, /wrong number of arguments/)
+ end
+
+ it "should not compile when 1 argument is passed" do
+ skip("Fails on 2.6.x, see bug #15912") if Puppet.version =~ /^2\.6\./
+ Puppet[:code] = "$my_hash={'one' => 1}\n$x = merge($my_hash)"
+ expect {
+ scope.compiler.compile
+ }.to raise_error(Puppet::ParseError, /wrong number of arguments/)
+ end
+ end
+
+ describe 'when calling merge on the scope instance' do
+ it 'should require all parameters are hashes' do
+ expect { new_hash = scope.function_merge([{}, '2'])}.to raise_error(Puppet::ParseError, /unexpected argument type String/)
+ expect { new_hash = scope.function_merge([{}, 2])}.to raise_error(Puppet::ParseError, /unexpected argument type Fixnum/)
+ end
+
+ it 'should accept empty strings as puppet undef' do
+ expect { new_hash = scope.function_merge([{}, ''])}.not_to raise_error
+ end
+
+ it 'should be able to merge two hashes' do
+ new_hash = scope.function_merge([{'one' => '1', 'two' => '1'}, {'two' => '2', 'three' => '2'}])
+ expect(new_hash['one']).to eq('1')
+ expect(new_hash['two']).to eq('2')
+ expect(new_hash['three']).to eq('2')
+ end
+
+ it 'should merge multiple hashes' do
+ hash = scope.function_merge([{'one' => 1}, {'one' => '2'}, {'one' => '3'}])
+ expect(hash['one']).to eq('3')
+ end
+
+ it 'should accept empty hashes' do
+ expect(scope.function_merge([{},{},{}])).to eq({})
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/min_spec.rb b/puppet/modules/stdlib/spec/functions/min_spec.rb
new file mode 100755
index 00000000..35a08900
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/min_spec.rb
@@ -0,0 +1,27 @@
+#! /usr/bin/env ruby -S rspec
+
+require 'spec_helper'
+
+describe "the min function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("min")).to eq("function_min")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_min([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should be able to compare strings" do
+ expect(scope.function_min(["albatross","dog","horse"])).to(eq("albatross"))
+ end
+
+ it "should be able to compare numbers" do
+ expect(scope.function_min([6,8,4])).to(eq(4))
+ end
+
+ it "should be able to compare a number with a stringified number" do
+ expect(scope.function_min([1,"2"])).to(eq(1))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/num2bool_spec.rb b/puppet/modules/stdlib/spec/functions/num2bool_spec.rb
new file mode 100755
index 00000000..d0ba9354
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/num2bool_spec.rb
@@ -0,0 +1,67 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the num2bool function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("num2bool")).to eq("function_num2bool")
+ end
+
+ it "should raise a ParseError if there are no arguments" do
+ expect { scope.function_num2bool([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should raise a ParseError if there are more than 1 arguments" do
+ expect { scope.function_num2bool(["foo","bar"]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should raise a ParseError if passed something non-numeric" do
+ expect { scope.function_num2bool(["xyzzy"]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should return true if passed string 1" do
+ result = scope.function_num2bool(["1"])
+ expect(result).to(be_truthy)
+ end
+
+ it "should return true if passed string 1.5" do
+ result = scope.function_num2bool(["1.5"])
+ expect(result).to(be_truthy)
+ end
+
+ it "should return true if passed number 1" do
+ result = scope.function_num2bool([1])
+ expect(result).to(be_truthy)
+ end
+
+ it "should return false if passed string 0" do
+ result = scope.function_num2bool(["0"])
+ expect(result).to(be_falsey)
+ end
+
+ it "should return false if passed number 0" do
+ result = scope.function_num2bool([0])
+ expect(result).to(be_falsey)
+ end
+
+ it "should return false if passed string -1" do
+ result = scope.function_num2bool(["-1"])
+ expect(result).to(be_falsey)
+ end
+
+ it "should return false if passed string -1.5" do
+ result = scope.function_num2bool(["-1.5"])
+ expect(result).to(be_falsey)
+ end
+
+ it "should return false if passed number -1" do
+ result = scope.function_num2bool([-1])
+ expect(result).to(be_falsey)
+ end
+
+ it "should return false if passed float -1.5" do
+ result = scope.function_num2bool([-1.5])
+ expect(result).to(be_falsey)
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/parsejson_spec.rb b/puppet/modules/stdlib/spec/functions/parsejson_spec.rb
new file mode 100755
index 00000000..1dd41b96
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/parsejson_spec.rb
@@ -0,0 +1,22 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the parsejson function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("parsejson")).to eq("function_parsejson")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_parsejson([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should convert JSON to a data structure" do
+ json = <<-EOS
+["aaa","bbb","ccc"]
+EOS
+ result = scope.function_parsejson([json])
+ expect(result).to(eq(['aaa','bbb','ccc']))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/parseyaml_spec.rb b/puppet/modules/stdlib/spec/functions/parseyaml_spec.rb
new file mode 100755
index 00000000..e5f145ba
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/parseyaml_spec.rb
@@ -0,0 +1,24 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the parseyaml function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("parseyaml")).to eq("function_parseyaml")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_parseyaml([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should convert YAML to a data structure" do
+ yaml = <<-EOS
+- aaa
+- bbb
+- ccc
+EOS
+ result = scope.function_parseyaml([yaml])
+ expect(result).to(eq(['aaa','bbb','ccc']))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/pick_default_spec.rb b/puppet/modules/stdlib/spec/functions/pick_default_spec.rb
new file mode 100755
index 00000000..db10cc35
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/pick_default_spec.rb
@@ -0,0 +1,58 @@
+#!/usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the pick_default function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("pick_default")).to eq("function_pick_default")
+ end
+
+ it 'should return the correct value' do
+ expect(scope.function_pick_default(['first', 'second'])).to eq('first')
+ end
+
+ it 'should return the correct value if the first value is empty' do
+ expect(scope.function_pick_default(['', 'second'])).to eq('second')
+ end
+
+ it 'should skip empty string values' do
+ expect(scope.function_pick_default(['', 'first'])).to eq('first')
+ end
+
+ it 'should skip :undef values' do
+ expect(scope.function_pick_default([:undef, 'first'])).to eq('first')
+ end
+
+ it 'should skip :undefined values' do
+ expect(scope.function_pick_default([:undefined, 'first'])).to eq('first')
+ end
+
+ it 'should return the empty string if it is the last possibility' do
+ expect(scope.function_pick_default([:undef, :undefined, ''])).to eq('')
+ end
+
+ it 'should return :undef if it is the last possibility' do
+ expect(scope.function_pick_default(['', :undefined, :undef])).to eq(:undef)
+ end
+
+ it 'should return :undefined if it is the last possibility' do
+ expect(scope.function_pick_default([:undef, '', :undefined])).to eq(:undefined)
+ end
+
+ it 'should return the empty string if it is the only possibility' do
+ expect(scope.function_pick_default([''])).to eq('')
+ end
+
+ it 'should return :undef if it is the only possibility' do
+ expect(scope.function_pick_default([:undef])).to eq(:undef)
+ end
+
+ it 'should return :undefined if it is the only possibility' do
+ expect(scope.function_pick_default([:undefined])).to eq(:undefined)
+ end
+
+ it 'should error if no values are passed' do
+ expect { scope.function_pick_default([]) }.to raise_error(Puppet::Error, /Must receive at least one argument./)
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/pick_spec.rb b/puppet/modules/stdlib/spec/functions/pick_spec.rb
new file mode 100755
index 00000000..8be8f587
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/pick_spec.rb
@@ -0,0 +1,34 @@
+#!/usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the pick function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("pick")).to eq("function_pick")
+ end
+
+ it 'should return the correct value' do
+ expect(scope.function_pick(['first', 'second'])).to eq('first')
+ end
+
+ it 'should return the correct value if the first value is empty' do
+ expect(scope.function_pick(['', 'second'])).to eq('second')
+ end
+
+ it 'should remove empty string values' do
+ expect(scope.function_pick(['', 'first'])).to eq('first')
+ end
+
+ it 'should remove :undef values' do
+ expect(scope.function_pick([:undef, 'first'])).to eq('first')
+ end
+
+ it 'should remove :undefined values' do
+ expect(scope.function_pick([:undefined, 'first'])).to eq('first')
+ end
+
+ it 'should error if no values are passed' do
+ expect { scope.function_pick([]) }.to( raise_error(Puppet::ParseError, "pick(): must receive at least one non empty value"))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/prefix_spec.rb b/puppet/modules/stdlib/spec/functions/prefix_spec.rb
new file mode 100755
index 00000000..34cac530
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/prefix_spec.rb
@@ -0,0 +1,28 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the prefix function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "raises a ParseError if there is less than 1 arguments" do
+ expect { scope.function_prefix([]) }.to raise_error(Puppet::ParseError, /number of arguments/)
+ end
+
+ it "raises an error if the first argument is not an array" do
+ expect {
+ scope.function_prefix([Object.new])
+ }.to raise_error(Puppet::ParseError, /expected first argument to be an Array/)
+ end
+
+
+ it "raises an error if the second argument is not a string" do
+ expect {
+ scope.function_prefix([['first', 'second'], 42])
+ }.to raise_error(Puppet::ParseError, /expected second argument to be a String/)
+ end
+
+ it "returns a prefixed array" do
+ result = scope.function_prefix([['a','b','c'], 'p'])
+ expect(result).to(eq(['pa','pb','pc']))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/private_spec.rb b/puppet/modules/stdlib/spec/functions/private_spec.rb
new file mode 100755
index 00000000..c70759fa
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/private_spec.rb
@@ -0,0 +1,55 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe Puppet::Parser::Functions.function(:private) do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ subject do
+ function_name = Puppet::Parser::Functions.function(:private)
+ scope.method(function_name)
+ end
+
+ context "when called from inside module" do
+ it "should not fail" do
+ scope.expects(:lookupvar).with('module_name').returns('foo')
+ scope.expects(:lookupvar).with('caller_module_name').returns('foo')
+ expect {
+ subject.call []
+ }.not_to raise_error
+ end
+ end
+
+ context "with an explicit failure message" do
+ it "prints the failure message on error" do
+ scope.expects(:lookupvar).with('module_name').returns('foo')
+ scope.expects(:lookupvar).with('caller_module_name').returns('bar')
+ expect {
+ subject.call ['failure message!']
+ }.to raise_error Puppet::ParseError, /failure message!/
+ end
+ end
+
+ context "when called from private class" do
+ it "should fail with a class error message" do
+ scope.expects(:lookupvar).with('module_name').returns('foo')
+ scope.expects(:lookupvar).with('caller_module_name').returns('bar')
+ scope.source.expects(:name).returns('foo::baz')
+ scope.source.expects(:type).returns('hostclass')
+ expect {
+ subject.call []
+ }.to raise_error Puppet::ParseError, /Class foo::baz is private/
+ end
+ end
+
+ context "when called from private definition" do
+ it "should fail with a class error message" do
+ scope.expects(:lookupvar).with('module_name').returns('foo')
+ scope.expects(:lookupvar).with('caller_module_name').returns('bar')
+ scope.source.expects(:name).returns('foo::baz')
+ scope.source.expects(:type).returns('definition')
+ expect {
+ subject.call []
+ }.to raise_error Puppet::ParseError, /Definition foo::baz is private/
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/range_spec.rb b/puppet/modules/stdlib/spec/functions/range_spec.rb
new file mode 100755
index 00000000..ef86f971
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/range_spec.rb
@@ -0,0 +1,86 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the range function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "exists" do
+ expect(Puppet::Parser::Functions.function("range")).to eq("function_range")
+ end
+
+ it "raises a ParseError if there is less than 1 arguments" do
+ expect { scope.function_range([]) }.to raise_error Puppet::ParseError, /Wrong number of arguments.*0 for 1/
+ end
+
+ describe 'with a letter range' do
+ it "returns a letter range" do
+ result = scope.function_range(["a","d"])
+ expect(result).to eq ['a','b','c','d']
+ end
+
+ it "returns a letter range given a step of 1" do
+ result = scope.function_range(["a","d","1"])
+ expect(result).to eq ['a','b','c','d']
+ end
+
+ it "returns a stepped letter range" do
+ result = scope.function_range(["a","d","2"])
+ expect(result).to eq ['a','c']
+ end
+
+ it "returns a stepped letter range given a negative step" do
+ result = scope.function_range(["a","d","-2"])
+ expect(result).to eq ['a','c']
+ end
+ end
+
+ describe 'with a number range' do
+ it "returns a number range" do
+ result = scope.function_range(["1","4"])
+ expect(result).to eq [1,2,3,4]
+ end
+
+ it "returns a number range given a step of 1" do
+ result = scope.function_range(["1","4","1"])
+ expect(result).to eq [1,2,3,4]
+ end
+
+ it "returns a stepped number range" do
+ result = scope.function_range(["1","4","2"])
+ expect(result).to eq [1,3]
+ end
+
+ it "returns a stepped number range given a negative step" do
+ result = scope.function_range(["1","4","-2"])
+ expect(result).to eq [1,3]
+ end
+ end
+
+ describe 'with a numeric-like string range' do
+ it "works with padded hostname like strings" do
+ expected = ("host01".."host10").to_a
+ expect(scope.function_range(["host01","host10"])).to eq expected
+ end
+
+ it "coerces zero padded digits to integers" do
+ expected = (0..10).to_a
+ expect(scope.function_range(["00", "10"])).to eq expected
+ end
+ end
+
+ describe 'with a numeric range' do
+ it "returns a range of numbers" do
+ expected = (1..10).to_a
+ expect(scope.function_range([1,10])).to eq expected
+ end
+ it "returns a range of numbers with step parameter" do
+ expected = (1..10).step(2).to_a
+ expect(scope.function_range([1,10,2])).to eq expected
+ end
+ it "works with mixed numeric like strings and numeric arguments" do
+ expected = (1..10).to_a
+ expect(scope.function_range(['1',10])).to eq expected
+ expect(scope.function_range([1,'10'])).to eq expected
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/reject_spec.rb b/puppet/modules/stdlib/spec/functions/reject_spec.rb
new file mode 100755
index 00000000..88a992ef
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/reject_spec.rb
@@ -0,0 +1,20 @@
+#!/usr/bin/env ruby
+
+require 'spec_helper'
+
+describe "the reject function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("reject")).to eq("function_reject")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_reject([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should reject contents from an array" do
+ result = scope.function_reject([["1111", "aaabbb","bbbccc","dddeee"], "bbb"])
+ expect(result).to(eq(["1111", "dddeee"]))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/reverse_spec.rb b/puppet/modules/stdlib/spec/functions/reverse_spec.rb
new file mode 100755
index 00000000..1f047943
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/reverse_spec.rb
@@ -0,0 +1,28 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the reverse function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("reverse")).to eq("function_reverse")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_reverse([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should reverse a string" do
+ result = scope.function_reverse(["asdfghijkl"])
+ expect(result).to(eq('lkjihgfdsa'))
+ end
+
+ it "should accept objects which extend String" do
+ class AlsoString < String
+ end
+
+ value = AlsoString.new('asdfghjkl')
+ result = scope.function_reverse([value])
+ result.should(eq('lkjhgfdsa'))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/rstrip_spec.rb b/puppet/modules/stdlib/spec/functions/rstrip_spec.rb
new file mode 100755
index 00000000..f6b48389
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/rstrip_spec.rb
@@ -0,0 +1,33 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the rstrip function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("rstrip")).to eq("function_rstrip")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_rstrip([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should rstrip a string" do
+ result = scope.function_rstrip(["asdf "])
+ expect(result).to(eq('asdf'))
+ end
+
+ it "should rstrip each element in an array" do
+ result = scope.function_rstrip([["a ","b ", "c "]])
+ expect(result).to(eq(['a','b','c']))
+ end
+
+ it "should accept objects which extend String" do
+ class AlsoString < String
+ end
+
+ value = AlsoString.new('asdf ')
+ result = scope.function_rstrip([value])
+ result.should(eq('asdf'))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/shuffle_spec.rb b/puppet/modules/stdlib/spec/functions/shuffle_spec.rb
new file mode 100755
index 00000000..a62c1fb5
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/shuffle_spec.rb
@@ -0,0 +1,33 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the shuffle function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("shuffle")).to eq("function_shuffle")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_shuffle([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should shuffle a string and the result should be the same size" do
+ result = scope.function_shuffle(["asdf"])
+ expect(result.size).to(eq(4))
+ end
+
+ it "should shuffle a string but the sorted contents should still be the same" do
+ result = scope.function_shuffle(["adfs"])
+ expect(result.split("").sort.join("")).to(eq("adfs"))
+ end
+
+ it "should accept objects which extend String" do
+ class AlsoString < String
+ end
+
+ value = AlsoString.new('asdf')
+ result = scope.function_shuffle([value])
+ result.size.should(eq(4))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/size_spec.rb b/puppet/modules/stdlib/spec/functions/size_spec.rb
new file mode 100755
index 00000000..18eb48f9
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/size_spec.rb
@@ -0,0 +1,24 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the size function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("size")).to eq("function_size")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_size([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should return the size of a string" do
+ result = scope.function_size(["asdf"])
+ expect(result).to(eq(4))
+ end
+
+ it "should return the size of an array" do
+ result = scope.function_size([["a","b","c"]])
+ expect(result).to(eq(3))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/sort_spec.rb b/puppet/modules/stdlib/spec/functions/sort_spec.rb
new file mode 100755
index 00000000..4c2a66cf
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/sort_spec.rb
@@ -0,0 +1,24 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the sort function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("sort")).to eq("function_sort")
+ end
+
+ it "should raise a ParseError if there is not 1 arguments" do
+ expect { scope.function_sort(['','']) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should sort an array" do
+ result = scope.function_sort([["a","c","b"]])
+ expect(result).to(eq(['a','b','c']))
+ end
+
+ it "should sort a string" do
+ result = scope.function_sort(["acb"])
+ expect(result).to(eq('abc'))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/squeeze_spec.rb b/puppet/modules/stdlib/spec/functions/squeeze_spec.rb
new file mode 100755
index 00000000..cd0eb37f
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/squeeze_spec.rb
@@ -0,0 +1,24 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the squeeze function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("squeeze")).to eq("function_squeeze")
+ end
+
+ it "should raise a ParseError if there is less than 2 arguments" do
+ expect { scope.function_squeeze([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should squeeze a string" do
+ result = scope.function_squeeze(["aaabbbbcccc"])
+ expect(result).to(eq('abc'))
+ end
+
+ it "should squeeze all elements in an array" do
+ result = scope.function_squeeze([["aaabbbbcccc","dddfff"]])
+ expect(result).to(eq(['abc','df']))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/str2bool_spec.rb b/puppet/modules/stdlib/spec/functions/str2bool_spec.rb
new file mode 100755
index 00000000..1d205d75
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/str2bool_spec.rb
@@ -0,0 +1,31 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the str2bool function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("str2bool")).to eq("function_str2bool")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_str2bool([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should convert string 'true' to true" do
+ result = scope.function_str2bool(["true"])
+ expect(result).to(eq(true))
+ end
+
+ it "should convert string 'undef' to false" do
+ result = scope.function_str2bool(["undef"])
+ expect(result).to(eq(false))
+ end
+
+ it "should return the boolean it was called with" do
+ result = scope.function_str2bool([true])
+ expect(result).to(eq(true))
+ result = scope.function_str2bool([false])
+ expect(result).to(eq(false))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/str2saltedsha512_spec.rb b/puppet/modules/stdlib/spec/functions/str2saltedsha512_spec.rb
new file mode 100755
index 00000000..ab7f57f1
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/str2saltedsha512_spec.rb
@@ -0,0 +1,45 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the str2saltedsha512 function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("str2saltedsha512")).to eq("function_str2saltedsha512")
+ end
+
+ it "should raise a ParseError if there is less than 1 argument" do
+ expect { scope.function_str2saltedsha512([]) }.to( raise_error(Puppet::ParseError) )
+ end
+
+ it "should raise a ParseError if there is more than 1 argument" do
+ expect { scope.function_str2saltedsha512(['foo', 'bar', 'baz']) }.to( raise_error(Puppet::ParseError) )
+ end
+
+ it "should return a salted-sha512 password hash 136 characters in length" do
+ result = scope.function_str2saltedsha512(["password"])
+ expect(result.length).to(eq(136))
+ end
+
+ it "should raise an error if you pass a non-string password" do
+ expect { scope.function_str2saltedsha512([1234]) }.to( raise_error(Puppet::ParseError) )
+ end
+
+ it "should generate a valid password" do
+ # Allow the function to generate a password based on the string 'password'
+ password_hash = scope.function_str2saltedsha512(["password"])
+
+ # Separate the Salt and Password from the Password Hash
+ salt = password_hash[0..7]
+ password = password_hash[8..-1]
+
+ # Convert the Salt and Password from Hex to Binary Data
+ str_salt = Array(salt.lines).pack('H*')
+ str_password = Array(password.lines).pack('H*')
+
+ # Combine the Binary Salt with 'password' and compare the end result
+ saltedpass = Digest::SHA512.digest(str_salt + 'password')
+ result = (str_salt + saltedpass).unpack('H*')[0]
+ expect(result).to eq(password_hash)
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/strftime_spec.rb b/puppet/modules/stdlib/spec/functions/strftime_spec.rb
new file mode 100755
index 00000000..ebec54b8
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/strftime_spec.rb
@@ -0,0 +1,29 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the strftime function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("strftime")).to eq("function_strftime")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_strftime([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "using %s should be higher then when I wrote this test" do
+ result = scope.function_strftime(["%s"])
+ expect(result.to_i).to(be > 1311953157)
+ end
+
+ it "using %s should be lower then 1.5 trillion" do
+ result = scope.function_strftime(["%s"])
+ expect(result.to_i).to(be < 1500000000)
+ end
+
+ it "should return a date when given %Y-%m-%d" do
+ result = scope.function_strftime(["%Y-%m-%d"])
+ expect(result).to match(/^\d{4}-\d{2}-\d{2}$/)
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/strip_spec.rb b/puppet/modules/stdlib/spec/functions/strip_spec.rb
new file mode 100755
index 00000000..4ac8daf8
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/strip_spec.rb
@@ -0,0 +1,27 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the strip function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("strip")).to eq("function_strip")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_strip([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should strip a string" do
+ result = scope.function_strip([" ab cd "])
+ expect(result).to(eq('ab cd'))
+ end
+
+ it "should accept objects which extend String" do
+ class AlsoString < String
+ end
+
+ value = AlsoString.new(' as df ')
+ result = scope.function_strip([value])
+ result.should(eq('as df'))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/suffix_spec.rb b/puppet/modules/stdlib/spec/functions/suffix_spec.rb
new file mode 100755
index 00000000..c7783c64
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/suffix_spec.rb
@@ -0,0 +1,27 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the suffix function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "raises a ParseError if there is less than 1 arguments" do
+ expect { scope.function_suffix([]) }.to raise_error(Puppet::ParseError, /number of arguments/)
+ end
+
+ it "raises an error if the first argument is not an array" do
+ expect {
+ scope.function_suffix([Object.new])
+ }.to raise_error(Puppet::ParseError, /expected first argument to be an Array/)
+ end
+
+ it "raises an error if the second argument is not a string" do
+ expect {
+ scope.function_suffix([['first', 'second'], 42])
+ }.to raise_error(Puppet::ParseError, /expected second argument to be a String/)
+ end
+
+ it "returns a suffixed array" do
+ result = scope.function_suffix([['a','b','c'], 'p'])
+ expect(result).to(eq(['ap','bp','cp']))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/swapcase_spec.rb b/puppet/modules/stdlib/spec/functions/swapcase_spec.rb
new file mode 100755
index 00000000..791d1dfa
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/swapcase_spec.rb
@@ -0,0 +1,28 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the swapcase function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("swapcase")).to eq("function_swapcase")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_swapcase([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should swapcase a string" do
+ result = scope.function_swapcase(["aaBBccDD"])
+ expect(result).to(eq('AAbbCCdd'))
+ end
+
+ it "should accept objects which extend String" do
+ class AlsoString < String
+ end
+
+ value = AlsoString.new("aaBBccDD")
+ result = scope.function_swapcase([value])
+ result.should(eq("AAbbCCdd"))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/time_spec.rb b/puppet/modules/stdlib/spec/functions/time_spec.rb
new file mode 100755
index 00000000..6e22515f
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/time_spec.rb
@@ -0,0 +1,29 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the time function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("time")).to eq("function_time")
+ end
+
+ it "should raise a ParseError if there is more than 2 arguments" do
+ expect { scope.function_time(['','']) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should return a number" do
+ result = scope.function_time([])
+ expect(result).to be_an(Integer)
+ end
+
+ it "should be higher then when I wrote this test" do
+ result = scope.function_time([])
+ expect(result).to(be > 1311953157)
+ end
+
+ it "should be lower then 1.5 trillion" do
+ result = scope.function_time([])
+ expect(result).to(be < 1500000000)
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/to_bytes_spec.rb b/puppet/modules/stdlib/spec/functions/to_bytes_spec.rb
new file mode 100755
index 00000000..0f6ade91
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/to_bytes_spec.rb
@@ -0,0 +1,83 @@
+#! /usr/bin/env ruby -S rspec
+
+require 'spec_helper'
+
+describe "the to_bytes function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("to_bytes")).to eq("function_to_bytes")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_to_bytes([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should convert kB to B" do
+ result = scope.function_to_bytes(["4 kB"])
+ expect(result).to(eq(4096))
+ end
+
+ it "should convert MB to B" do
+ result = scope.function_to_bytes(["4 MB"])
+ expect(result).to(eq(4194304))
+ end
+
+ it "should convert GB to B" do
+ result = scope.function_to_bytes(["4 GB"])
+ expect(result).to(eq(4294967296))
+ end
+
+ it "should convert TB to B" do
+ result = scope.function_to_bytes(["4 TB"])
+ expect(result).to(eq(4398046511104))
+ end
+
+ it "should convert PB to B" do
+ result = scope.function_to_bytes(["4 PB"])
+ expect(result).to(eq(4503599627370496))
+ end
+
+ it "should convert PB to B" do
+ result = scope.function_to_bytes(["4 EB"])
+ expect(result).to(eq(4611686018427387904))
+ end
+
+ it "should work without B in unit" do
+ result = scope.function_to_bytes(["4 k"])
+ expect(result).to(eq(4096))
+ end
+
+ it "should work without a space before unit" do
+ result = scope.function_to_bytes(["4k"])
+ expect(result).to(eq(4096))
+ end
+
+ it "should work without a unit" do
+ result = scope.function_to_bytes(["5678"])
+ expect(result).to(eq(5678))
+ end
+
+ it "should convert fractions" do
+ result = scope.function_to_bytes(["1.5 kB"])
+ expect(result).to(eq(1536))
+ end
+
+ it "should convert scientific notation" do
+ result = scope.function_to_bytes(["1.5e2 B"])
+ expect(result).to(eq(150))
+ end
+
+ it "should do nothing with a positive number" do
+ result = scope.function_to_bytes([5678])
+ expect(result).to(eq(5678))
+ end
+
+ it "should should raise a ParseError if input isn't a number" do
+ expect { scope.function_to_bytes(["foo"]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should should raise a ParseError if prefix is unknown" do
+ expect { scope.function_to_bytes(["5 uB"]) }.to( raise_error(Puppet::ParseError))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/type3x_spec.rb b/puppet/modules/stdlib/spec/functions/type3x_spec.rb
new file mode 100644
index 00000000..d21236a6
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/type3x_spec.rb
@@ -0,0 +1,43 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the type3x function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("type3x")).to eq("function_type3x")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_type3x([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should return string when given a string" do
+ result = scope.function_type3x(["aaabbbbcccc"])
+ expect(result).to(eq('string'))
+ end
+
+ it "should return array when given an array" do
+ result = scope.function_type3x([["aaabbbbcccc","asdf"]])
+ expect(result).to(eq('array'))
+ end
+
+ it "should return hash when given a hash" do
+ result = scope.function_type3x([{"a"=>1,"b"=>2}])
+ expect(result).to(eq('hash'))
+ end
+
+ it "should return integer when given an integer" do
+ result = scope.function_type3x(["1"])
+ expect(result).to(eq('integer'))
+ end
+
+ it "should return float when given a float" do
+ result = scope.function_type3x(["1.34"])
+ expect(result).to(eq('float'))
+ end
+
+ it "should return boolean when given a boolean" do
+ result = scope.function_type3x([true])
+ expect(result).to(eq('boolean'))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/type_spec.rb b/puppet/modules/stdlib/spec/functions/type_spec.rb
new file mode 100755
index 00000000..b683fcfa
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/type_spec.rb
@@ -0,0 +1,44 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the type function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("type")).to eq("function_type")
+ end
+
+ it "should give a deprecation warning when called" do
+ scope.expects(:warning).with("type() DEPRECATED: This function will cease to function on Puppet 4; please use type3x() before upgrading to puppet 4 for backwards-compatibility, or migrate to the new parser's typing system.")
+ scope.function_type(["aoeu"])
+ end
+
+ it "should return string when given a string" do
+ result = scope.function_type(["aaabbbbcccc"])
+ expect(result).to(eq('string'))
+ end
+
+ it "should return array when given an array" do
+ result = scope.function_type([["aaabbbbcccc","asdf"]])
+ expect(result).to(eq('array'))
+ end
+
+ it "should return hash when given a hash" do
+ result = scope.function_type([{"a"=>1,"b"=>2}])
+ expect(result).to(eq('hash'))
+ end
+
+ it "should return integer when given an integer" do
+ result = scope.function_type(["1"])
+ expect(result).to(eq('integer'))
+ end
+
+ it "should return float when given a float" do
+ result = scope.function_type(["1.34"])
+ expect(result).to(eq('float'))
+ end
+
+ it "should return boolean when given a boolean" do
+ result = scope.function_type([true])
+ expect(result).to(eq('boolean'))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/union_spec.rb b/puppet/modules/stdlib/spec/functions/union_spec.rb
new file mode 100755
index 00000000..706f4cbc
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/union_spec.rb
@@ -0,0 +1,19 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the union function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("union")).to eq("function_union")
+ end
+
+ it "should raise a ParseError if there are fewer than 2 arguments" do
+ expect { scope.function_union([]) }.to( raise_error(Puppet::ParseError) )
+ end
+
+ it "should join two arrays together" do
+ result = scope.function_union([["a","b","c"],["b","c","d"]])
+ expect(result).to(eq(["a","b","c","d"]))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/unique_spec.rb b/puppet/modules/stdlib/spec/functions/unique_spec.rb
new file mode 100755
index 00000000..7cd3a566
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/unique_spec.rb
@@ -0,0 +1,33 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the unique function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("unique")).to eq("function_unique")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_unique([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should remove duplicate elements in a string" do
+ result = scope.function_unique(["aabbc"])
+ expect(result).to(eq('abc'))
+ end
+
+ it "should remove duplicate elements in an array" do
+ result = scope.function_unique([["a","a","b","b","c"]])
+ expect(result).to(eq(['a','b','c']))
+ end
+
+ it "should accept objects which extend String" do
+ class AlsoString < String
+ end
+
+ value = AlsoString.new('aabbc')
+ result = scope.function_unique([value])
+ result.should(eq('abc'))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/upcase_spec.rb b/puppet/modules/stdlib/spec/functions/upcase_spec.rb
new file mode 100755
index 00000000..3cf8b055
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/upcase_spec.rb
@@ -0,0 +1,33 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the upcase function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("upcase")).to eq("function_upcase")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_upcase([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should upcase a string" do
+ result = scope.function_upcase(["abc"])
+ expect(result).to(eq('ABC'))
+ end
+
+ it "should do nothing if a string is already upcase" do
+ result = scope.function_upcase(["ABC"])
+ expect(result).to(eq('ABC'))
+ end
+
+ it "should accept objects which extend String" do
+ class AlsoString < String
+ end
+
+ value = AlsoString.new('abc')
+ result = scope.function_upcase([value])
+ result.should(eq('ABC'))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/uriescape_spec.rb b/puppet/modules/stdlib/spec/functions/uriescape_spec.rb
new file mode 100755
index 00000000..2321e5ab
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/uriescape_spec.rb
@@ -0,0 +1,33 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the uriescape function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("uriescape")).to eq("function_uriescape")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_uriescape([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should uriescape a string" do
+ result = scope.function_uriescape([":/?#[]@!$&'()*+,;= \"{}"])
+ expect(result).to(eq(':/?%23[]@!$&\'()*+,;=%20%22%7B%7D'))
+ end
+
+ it "should do nothing if a string is already safe" do
+ result = scope.function_uriescape(["ABCdef"])
+ expect(result).to(eq('ABCdef'))
+ end
+
+ it "should accept objects which extend String" do
+ class AlsoString < String
+ end
+
+ value = AlsoString.new('abc')
+ result = scope.function_uriescape([value])
+ result.should(eq('abc'))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/validate_absolute_path_spec.rb b/puppet/modules/stdlib/spec/functions/validate_absolute_path_spec.rb
new file mode 100755
index 00000000..36c836bd
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/validate_absolute_path_spec.rb
@@ -0,0 +1,104 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe Puppet::Parser::Functions.function(:validate_absolute_path) do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ # The subject of these examples is the method itself.
+ subject do
+ # This makes sure the function is loaded within each test
+ function_name = Puppet::Parser::Functions.function(:validate_absolute_path)
+ scope.method(function_name)
+ end
+
+ describe "Valid Paths" do
+ def self.valid_paths
+ %w{
+ C:/
+ C:\\
+ C:\\WINDOWS\\System32
+ C:/windows/system32
+ X:/foo/bar
+ X:\\foo\\bar
+ /var/tmp
+ /var/lib/puppet
+ /var/opt/../lib/puppet
+ }
+ end
+
+ context "Without Puppet::Util.absolute_path? (e.g. Puppet <= 2.6)" do
+ before :each do
+ # The intent here is to mock Puppet to behave like Puppet 2.6 does.
+ # Puppet 2.6 does not have the absolute_path? method. This is only a
+ # convenience test, stdlib should be run with the Puppet 2.6.x in the
+ # $LOAD_PATH in addition to 2.7.x and master.
+ Puppet::Util.expects(:respond_to?).with(:absolute_path?).returns(false)
+ end
+ valid_paths.each do |path|
+ it "validate_absolute_path(#{path.inspect}) should not fail" do
+ expect { subject.call [path] }.not_to raise_error
+ end
+ end
+ valid_paths do
+ it "validate_absolute_path(#{valid_paths.inspect}) should not fail" do
+ expect { subject.call [valid_paths] }.not_to raise_error
+ end
+ end
+ end
+
+ context "Puppet without mocking" do
+ valid_paths.each do |path|
+ it "validate_absolute_path(#{path.inspect}) should not fail" do
+ expect { subject.call [path] }.not_to raise_error
+ end
+ end
+ valid_paths do
+ it "validate_absolute_path(#{valid_paths.inspect}) should not fail" do
+ expect { subject.call [valid_paths] }.not_to raise_error
+ end
+ end
+ end
+ end
+
+ describe 'Invalid paths' do
+ context 'Garbage inputs' do
+ [
+ nil,
+ [ nil ],
+ [ nil, nil ],
+ { 'foo' => 'bar' },
+ { },
+ '',
+ ].each do |path|
+ it "validate_absolute_path(#{path.inspect}) should fail" do
+ expect { subject.call [path] }.to raise_error Puppet::ParseError
+ end
+ end
+ end
+
+ context 'Relative paths' do
+ def self.rel_paths
+ %w{
+ relative1
+ .
+ ..
+ ./foo
+ ../foo
+ etc/puppetlabs/puppet
+ opt/puppet/bin
+ }
+ end
+ rel_paths.each do |path|
+ it "validate_absolute_path(#{path.inspect}) should fail" do
+ expect { subject.call [path] }.to raise_error Puppet::ParseError
+ end
+ end
+ rel_paths do
+ it "validate_absolute_path(#{rel_paths.inspect}) should fail" do
+ expect { subject.call [rel_paths] }.to raise_error Puppet::ParseError
+ end
+ end
+ end
+ end
+end
+
diff --git a/puppet/modules/stdlib/spec/functions/validate_array_spec.rb b/puppet/modules/stdlib/spec/functions/validate_array_spec.rb
new file mode 100755
index 00000000..4b31cfde
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/validate_array_spec.rb
@@ -0,0 +1,38 @@
+#! /usr/bin/env ruby -S rspec
+
+require 'spec_helper'
+
+describe Puppet::Parser::Functions.function(:validate_array) do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+ describe 'when calling validate_array from puppet' do
+
+ %w{ true false }.each do |the_string|
+ it "should not compile when #{the_string} is a string" do
+ Puppet[:code] = "validate_array('#{the_string}')"
+ expect { scope.compiler.compile }.to raise_error(Puppet::ParseError, /is not an Array/)
+ end
+
+ it "should not compile when #{the_string} is a bare word" do
+ Puppet[:code] = "validate_array(#{the_string})"
+ expect { scope.compiler.compile }.to raise_error(Puppet::ParseError, /is not an Array/)
+ end
+ end
+
+ it "should compile when multiple array arguments are passed" do
+ Puppet[:code] = <<-'ENDofPUPPETcode'
+ $foo = [ ]
+ $bar = [ 'one', 'two' ]
+ validate_array($foo, $bar)
+ ENDofPUPPETcode
+ scope.compiler.compile
+ end
+
+ it "should not compile when an undef variable is passed" do
+ Puppet[:code] = <<-'ENDofPUPPETcode'
+ $foo = undef
+ validate_array($foo)
+ ENDofPUPPETcode
+ expect { scope.compiler.compile }.to raise_error(Puppet::ParseError, /is not an Array/)
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/validate_augeas_spec.rb b/puppet/modules/stdlib/spec/functions/validate_augeas_spec.rb
new file mode 100755
index 00000000..c695ba2e
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/validate_augeas_spec.rb
@@ -0,0 +1,103 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe Puppet::Parser::Functions.function(:validate_augeas), :if => Puppet.features.augeas? do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ # The subject of these examplres is the method itself.
+ subject do
+ # This makes sure the function is loaded within each test
+ function_name = Puppet::Parser::Functions.function(:validate_augeas)
+ scope.method(function_name)
+ end
+
+ context 'Using Puppet::Parser::Scope.new' do
+
+ describe 'Garbage inputs' do
+ inputs = [
+ [ nil ],
+ [ [ nil ] ],
+ [ { 'foo' => 'bar' } ],
+ [ { } ],
+ [ '' ],
+ [ "one", "one", "MSG to User", "4th arg" ],
+ ]
+
+ inputs.each do |input|
+ it "validate_augeas(#{input.inspect}) should fail" do
+ expect { subject.call [input] }.to raise_error Puppet::ParseError
+ end
+ end
+ end
+
+ describe 'Valid inputs' do
+ inputs = [
+ [ "root:x:0:0:root:/root:/bin/bash\n", 'Passwd.lns' ],
+ [ "proc /proc proc nodev,noexec,nosuid 0 0\n", 'Fstab.lns'],
+ ]
+
+ inputs.each do |input|
+ it "validate_augeas(#{input.inspect}) should not fail" do
+ expect { subject.call input }.not_to raise_error
+ end
+ end
+ end
+
+ describe "Valid inputs which should raise an exception without a message" do
+ # The intent here is to make sure valid inputs raise exceptions when they
+ # don't specify an error message to display. This is the behvior in
+ # 2.2.x and prior.
+ inputs = [
+ [ "root:x:0:0:root\n", 'Passwd.lns' ],
+ [ "127.0.1.1\n", 'Hosts.lns' ],
+ ]
+
+ inputs.each do |input|
+ it "validate_augeas(#{input.inspect}) should fail" do
+ expect { subject.call input }.to raise_error /validate_augeas.*?matched less than it should/
+ end
+ end
+ end
+
+ describe "Nicer Error Messages" do
+ # The intent here is to make sure the function returns the 3rd argument
+ # in the exception thrown
+ inputs = [
+ [ "root:x:0:0:root\n", 'Passwd.lns', [], 'Failed to validate passwd content' ],
+ [ "127.0.1.1\n", 'Hosts.lns', [], 'Wrong hosts content' ],
+ ]
+
+ inputs.each do |input|
+ it "validate_augeas(#{input.inspect}) should fail" do
+ expect { subject.call input }.to raise_error /#{input[2]}/
+ end
+ end
+ end
+
+ describe "Passing simple unit tests" do
+ inputs = [
+ [ "root:x:0:0:root:/root:/bin/bash\n", 'Passwd.lns', ['$file/foobar']],
+ [ "root:x:0:0:root:/root:/bin/bash\n", 'Passwd.lns', ['$file/root/shell[.="/bin/sh"]', 'foobar']],
+ ]
+
+ inputs.each do |input|
+ it "validate_augeas(#{input.inspect}) should fail" do
+ expect { subject.call input }.not_to raise_error
+ end
+ end
+ end
+
+ describe "Failing simple unit tests" do
+ inputs = [
+ [ "foobar:x:0:0:root:/root:/bin/bash\n", 'Passwd.lns', ['$file/foobar']],
+ [ "root:x:0:0:root:/root:/bin/sh\n", 'Passwd.lns', ['$file/root/shell[.="/bin/sh"]', 'foobar']],
+ ]
+
+ inputs.each do |input|
+ it "validate_augeas(#{input.inspect}) should fail" do
+ expect { subject.call input }.to raise_error /testing path/
+ end
+ end
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/validate_bool_spec.rb b/puppet/modules/stdlib/spec/functions/validate_bool_spec.rb
new file mode 100755
index 00000000..a352d3b5
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/validate_bool_spec.rb
@@ -0,0 +1,51 @@
+#! /usr/bin/env ruby -S rspec
+
+require 'spec_helper'
+
+describe Puppet::Parser::Functions.function(:validate_bool) do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+ describe 'when calling validate_bool from puppet' do
+
+ %w{ true false }.each do |the_string|
+
+ it "should not compile when #{the_string} is a string" do
+ Puppet[:code] = "validate_bool('#{the_string}')"
+ expect { scope.compiler.compile }.to raise_error(Puppet::ParseError, /is not a boolean/)
+ end
+
+ it "should compile when #{the_string} is a bare word" do
+ Puppet[:code] = "validate_bool(#{the_string})"
+ scope.compiler.compile
+ end
+
+ end
+
+ it "should not compile when an arbitrary string is passed" do
+ Puppet[:code] = 'validate_bool("jeff and dan are awesome")'
+ expect { scope.compiler.compile }.to raise_error(Puppet::ParseError, /is not a boolean/)
+ end
+
+ it "should not compile when no arguments are passed" do
+ Puppet[:code] = 'validate_bool()'
+ expect { scope.compiler.compile }.to raise_error(Puppet::ParseError, /wrong number of arguments/)
+ end
+
+ it "should compile when multiple boolean arguments are passed" do
+ Puppet[:code] = <<-'ENDofPUPPETcode'
+ $foo = true
+ $bar = false
+ validate_bool($foo, $bar, true, false)
+ ENDofPUPPETcode
+ scope.compiler.compile
+ end
+
+ it "should compile when multiple boolean arguments are passed" do
+ Puppet[:code] = <<-'ENDofPUPPETcode'
+ $foo = true
+ $bar = false
+ validate_bool($foo, $bar, true, false, 'jeff')
+ ENDofPUPPETcode
+ expect { scope.compiler.compile }.to raise_error(Puppet::ParseError, /is not a boolean/)
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/validate_cmd_spec.rb b/puppet/modules/stdlib/spec/functions/validate_cmd_spec.rb
new file mode 100755
index 00000000..7cb9782d
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/validate_cmd_spec.rb
@@ -0,0 +1,85 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+TESTEXE = File.exists?('/usr/bin/test') ? '/usr/bin/test' : '/bin/test'
+TOUCHEXE = File.exists?('/usr/bin/touch') ? '/usr/bin/touch' : '/bin/touch'
+
+describe Puppet::Parser::Functions.function(:validate_cmd) do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ subject do
+ function_name = Puppet::Parser::Functions.function(:validate_cmd)
+ scope.method(function_name)
+ end
+
+ context 'with no % placeholder' do
+ describe "with an explicit failure message" do
+ it "prints the failure message on error" do
+ expect {
+ subject.call ['', '/bin/false', 'failure message!']
+ }.to raise_error Puppet::ParseError, /failure message!/
+ end
+ end
+
+ describe "on validation failure" do
+ it "includes the command error output" do
+ expect {
+ subject.call ['', "#{TOUCHEXE} /cant/touch/this"]
+ }.to raise_error Puppet::ParseError, /(cannot touch|o such file or)/
+ end
+
+ it "includes the command return value" do
+ expect {
+ subject.call ['', '/cant/run/this']
+ }.to raise_error Puppet::ParseError, /returned 1\b/
+ end
+ end
+
+ describe "when performing actual validation" do
+ it "can positively validate file content" do
+ expect { subject.call ["non-empty", "#{TESTEXE} -s"] }.to_not raise_error
+ end
+
+ it "can negatively validate file content" do
+ expect {
+ subject.call ["", "#{TESTEXE} -s"]
+ }.to raise_error Puppet::ParseError, /failed to validate.*test -s/
+ end
+ end
+ end
+
+ context 'with % placeholder' do
+ describe "with an explicit failure message" do
+ it "prints the failure message on error" do
+ expect {
+ subject.call ['', '/bin/false % -f', 'failure message!']
+ }.to raise_error Puppet::ParseError, /failure message!/
+ end
+ end
+ describe "on validation failure" do
+ it "includes the command error output" do
+ expect {
+ subject.call ['', "#{TOUCHEXE} /cant/touch/this"]
+ }.to raise_error Puppet::ParseError, /(cannot touch|o such file or)/
+ end
+
+ it "includes the command return value" do
+ expect {
+ subject.call ['', '/cant/run/this % -z']
+ }.to raise_error Puppet::ParseError, /Execution of '\/cant\/run\/this .+ -z' returned 1/
+ end
+ end
+
+ describe "when performing actual validation" do
+ it "can positively validate file content" do
+ expect { subject.call ["non-empty", "#{TESTEXE} -s %"] }.to_not raise_error
+ end
+
+ it "can negatively validate file content" do
+ expect {
+ subject.call ["", "#{TESTEXE} -s %"]
+ }.to raise_error Puppet::ParseError, /failed to validate.*test -s/
+ end
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/validate_hash_spec.rb b/puppet/modules/stdlib/spec/functions/validate_hash_spec.rb
new file mode 100755
index 00000000..a0c35c23
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/validate_hash_spec.rb
@@ -0,0 +1,43 @@
+#! /usr/bin/env ruby -S rspec
+
+require 'spec_helper'
+
+describe Puppet::Parser::Functions.function(:validate_hash) do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ describe 'when calling validate_hash from puppet' do
+
+ %w{ true false }.each do |the_string|
+
+ it "should not compile when #{the_string} is a string" do
+ Puppet[:code] = "validate_hash('#{the_string}')"
+ expect { scope.compiler.compile }.to raise_error(Puppet::ParseError, /is not a Hash/)
+ end
+
+ it "should not compile when #{the_string} is a bare word" do
+ Puppet[:code] = "validate_hash(#{the_string})"
+ expect { scope.compiler.compile }.to raise_error(Puppet::ParseError, /is not a Hash/)
+ end
+
+ end
+
+ it "should compile when multiple hash arguments are passed" do
+ Puppet[:code] = <<-'ENDofPUPPETcode'
+ $foo = {}
+ $bar = { 'one' => 'two' }
+ validate_hash($foo, $bar)
+ ENDofPUPPETcode
+ scope.compiler.compile
+ end
+
+ it "should not compile when an undef variable is passed" do
+ Puppet[:code] = <<-'ENDofPUPPETcode'
+ $foo = undef
+ validate_hash($foo)
+ ENDofPUPPETcode
+ expect { scope.compiler.compile }.to raise_error(Puppet::ParseError, /is not a Hash/)
+ end
+
+ end
+
+end
diff --git a/puppet/modules/stdlib/spec/functions/validate_ipv4_address_spec.rb b/puppet/modules/stdlib/spec/functions/validate_ipv4_address_spec.rb
new file mode 100755
index 00000000..45401a42
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/validate_ipv4_address_spec.rb
@@ -0,0 +1,64 @@
+#! /usr/bin/env ruby -S rspec
+
+require "spec_helper"
+
+describe Puppet::Parser::Functions.function(:validate_ipv4_address) do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ describe "when calling validate_ipv4_address from puppet" do
+ describe "when given IPv4 address strings" do
+ it "should compile with one argument" do
+ Puppet[:code] = "validate_ipv4_address('1.2.3.4')"
+ scope.compiler.compile
+ end
+
+ it "should compile with multiple arguments" do
+ Puppet[:code] = "validate_ipv4_address('1.2.3.4', '5.6.7.8')"
+ scope.compiler.compile
+ end
+ end
+
+ describe "when given an IPv6 address" do
+ it "should not compile" do
+ Puppet[:code] = "validate_ipv4_address('3ffe:505')"
+ expect {
+ scope.compiler.compile
+ }.to raise_error(Puppet::ParseError, /not a valid IPv4 address/)
+ end
+ end
+
+ describe "when given other strings" do
+ it "should not compile" do
+ Puppet[:code] = "validate_ipv4_address('hello', 'world')"
+ expect {
+ scope.compiler.compile
+ }.to raise_error(Puppet::ParseError, /not a valid IPv4 address/)
+ end
+ end
+
+ describe "when given numbers" do
+ it "should not compile" do
+ Puppet[:code] = "validate_ipv4_address(1, 2)"
+ expect {
+ scope.compiler.compile
+ }.to raise_error(Puppet::ParseError, /is not a valid IPv4 address/)
+ end
+ end
+
+ describe "when given booleans" do
+ it "should not compile" do
+ Puppet[:code] = "validate_ipv4_address(true, false)"
+ expect {
+ scope.compiler.compile
+ }.to raise_error(Puppet::ParseError, /is not a string/)
+ end
+ end
+
+ it "should not compile when no arguments are passed" do
+ Puppet[:code] = "validate_ipv4_address()"
+ expect {
+ scope.compiler.compile
+ }.to raise_error(Puppet::ParseError, /wrong number of arguments/)
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/validate_ipv6_address_spec.rb b/puppet/modules/stdlib/spec/functions/validate_ipv6_address_spec.rb
new file mode 100755
index 00000000..a839d902
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/validate_ipv6_address_spec.rb
@@ -0,0 +1,67 @@
+#! /usr/bin/env ruby -S rspec
+
+require "spec_helper"
+
+describe Puppet::Parser::Functions.function(:validate_ipv6_address) do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ describe "when calling validate_ipv6_address from puppet" do
+ describe "when given IPv6 address strings" do
+ it "should compile with one argument" do
+ Puppet[:code] = "validate_ipv6_address('3ffe:0505:0002::')"
+ scope.compiler.compile
+ end
+
+ it "should compile with multiple arguments" do
+ Puppet[:code] = "validate_ipv6_address('3ffe:0505:0002::', '3ffe:0505:0001::')"
+ scope.compiler.compile
+ end
+ end
+
+ describe "when given an ipv4 address" do
+ it "should not compile" do
+ Puppet[:code] = "validate_ipv6_address('1.2.3.4')"
+ expect {
+ scope.compiler.compile
+ }.to raise_error(Puppet::ParseError, /not a valid IPv6 address/)
+ end
+ end
+
+ describe "when given other strings" do
+ it "should not compile" do
+ Puppet[:code] = "validate_ipv6_address('hello', 'world')"
+ expect {
+ scope.compiler.compile
+ }.to raise_error(Puppet::ParseError, /not a valid IPv6 address/)
+ end
+ end
+
+ # 1.8.7 is EOL'd and also absolutely insane about ipv6
+ unless RUBY_VERSION == '1.8.7'
+ describe "when given numbers" do
+ it "should not compile" do
+ Puppet[:code] = "validate_ipv6_address(1, 2)"
+ expect {
+ scope.compiler.compile
+ }.to raise_error(Puppet::ParseError, /not a valid IPv6 address/)
+ end
+ end
+ end
+
+ describe "when given booleans" do
+ it "should not compile" do
+ Puppet[:code] = "validate_ipv6_address(true, false)"
+ expect {
+ scope.compiler.compile
+ }.to raise_error(Puppet::ParseError, /is not a string/)
+ end
+ end
+
+ it "should not compile when no arguments are passed" do
+ Puppet[:code] = "validate_ipv6_address()"
+ expect {
+ scope.compiler.compile
+ }.to raise_error(Puppet::ParseError, /wrong number of arguments/)
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/validate_re_spec.rb b/puppet/modules/stdlib/spec/functions/validate_re_spec.rb
new file mode 100755
index 00000000..d29988bf
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/validate_re_spec.rb
@@ -0,0 +1,77 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe Puppet::Parser::Functions.function(:validate_re) do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ # The subject of these examplres is the method itself.
+ subject do
+ # This makes sure the function is loaded within each test
+ function_name = Puppet::Parser::Functions.function(:validate_re)
+ scope.method(function_name)
+ end
+
+ context 'Using Puppet::Parser::Scope.new' do
+
+ describe 'Garbage inputs' do
+ inputs = [
+ [ nil ],
+ [ [ nil ] ],
+ [ { 'foo' => 'bar' } ],
+ [ { } ],
+ [ '' ],
+ [ "one", "one", "MSG to User", "4th arg" ],
+ ]
+
+ inputs.each do |input|
+ it "validate_re(#{input.inspect}) should fail" do
+ expect { subject.call [input] }.to raise_error Puppet::ParseError
+ end
+ end
+ end
+
+ describe 'Valid inputs' do
+ inputs = [
+ [ '/full/path/to/something', '^/full' ],
+ [ '/full/path/to/something', 'full' ],
+ [ '/full/path/to/something', ['full', 'absent'] ],
+ [ '/full/path/to/something', ['full', 'absent'], 'Message to the user' ],
+ ]
+
+ inputs.each do |input|
+ it "validate_re(#{input.inspect}) should not fail" do
+ expect { subject.call input }.not_to raise_error
+ end
+ end
+ end
+ describe "Valid inputs which should raise an exception without a message" do
+ # The intent here is to make sure valid inputs raise exceptions when they
+ # don't specify an error message to display. This is the behvior in
+ # 2.2.x and prior.
+ inputs = [
+ [ "hello", [ "bye", "later", "adios" ] ],
+ [ "greetings", "salutations" ],
+ ]
+
+ inputs.each do |input|
+ it "validate_re(#{input.inspect}) should fail" do
+ expect { subject.call input }.to raise_error /validate_re.*?does not match/
+ end
+ end
+ end
+ describe "Nicer Error Messages" do
+ # The intent here is to make sure the function returns the 3rd argument
+ # in the exception thrown
+ inputs = [
+ [ "hello", [ "bye", "later", "adios" ], "MSG to User" ],
+ [ "greetings", "salutations", "Error, greetings does not match salutations" ],
+ ]
+
+ inputs.each do |input|
+ it "validate_re(#{input.inspect}) should fail" do
+ expect { subject.call input }.to raise_error /#{input[2]}/
+ end
+ end
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/validate_slength_spec.rb b/puppet/modules/stdlib/spec/functions/validate_slength_spec.rb
new file mode 100755
index 00000000..e23f61a2
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/validate_slength_spec.rb
@@ -0,0 +1,67 @@
+#! /usr/bin/env ruby -S rspec
+
+require 'spec_helper'
+
+describe "the validate_slength function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("validate_slength")).to eq("function_validate_slength")
+ end
+
+ describe "validating the input argument types" do
+ it "raises an error if there are less than two arguments" do
+ expect { scope.function_validate_slength([]) }.to raise_error Puppet::ParseError, /Wrong number of arguments/
+ end
+
+ it "raises an error if there are more than three arguments" do
+ expect { scope.function_validate_slength(['input', 1, 2, 3]) }.to raise_error Puppet::ParseError, /Wrong number of arguments/
+ end
+
+ it "raises an error if the first argument is not a string" do
+ expect { scope.function_validate_slength([Object.new, 2, 1]) }.to raise_error Puppet::ParseError, /Expected first argument.*got .*Object/
+ end
+
+ it "raises an error if the second argument cannot be cast to an Integer" do
+ expect { scope.function_validate_slength(['input', Object.new]) }.to raise_error Puppet::ParseError, /Expected second argument.*got .*Object/
+ end
+
+ it "raises an error if the third argument cannot be cast to an Integer" do
+ expect { scope.function_validate_slength(['input', 1, Object.new]) }.to raise_error Puppet::ParseError, /Expected third argument.*got .*Object/
+ end
+
+ it "raises an error if the second argument is smaller than the third argument" do
+ expect { scope.function_validate_slength(['input', 1, 2]) }.to raise_error Puppet::ParseError, /Expected second argument to be larger than third argument/
+ end
+ end
+
+ describe "validating the input string length" do
+ describe "when the input is a string" do
+ it "fails validation if the string is larger than the max length" do
+ expect { scope.function_validate_slength(['input', 1]) }.to raise_error Puppet::ParseError, /Expected length .* between 0 and 1, was 5/
+ end
+
+ it "fails validation if the string is less than the min length" do
+ expect { scope.function_validate_slength(['input', 10, 6]) }.to raise_error Puppet::ParseError, /Expected length .* between 6 and 10, was 5/
+ end
+
+ it "doesn't raise an error if the string is under the max length" do
+ scope.function_validate_slength(['input', 10])
+ end
+
+ it "doesn't raise an error if the string is equal to the max length" do
+ scope.function_validate_slength(['input', 5])
+ end
+
+ it "doesn't raise an error if the string is equal to the min length" do
+ scope.function_validate_slength(['input', 10, 5])
+ end
+ end
+
+ describe "when the input is an array" do
+ it "fails validation if one of the array elements is not a string" do
+ expect { scope.function_validate_slength([["a", "b", Object.new], 2]) }.to raise_error Puppet::ParseError, /Expected element at array position 2 .*String, got .*Object/
+ end
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/validate_string_spec.rb b/puppet/modules/stdlib/spec/functions/validate_string_spec.rb
new file mode 100755
index 00000000..3b4fb3e1
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/validate_string_spec.rb
@@ -0,0 +1,60 @@
+#! /usr/bin/env ruby -S rspec
+
+require 'spec_helper'
+
+describe Puppet::Parser::Functions.function(:validate_string) do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ describe 'when calling validate_string from puppet' do
+
+ %w{ foo bar baz }.each do |the_string|
+
+ it "should compile when #{the_string} is a string" do
+ Puppet[:code] = "validate_string('#{the_string}')"
+ scope.compiler.compile
+ end
+
+ it "should compile when #{the_string} is a bare word" do
+ Puppet[:code] = "validate_string(#{the_string})"
+ scope.compiler.compile
+ end
+
+ end
+
+ %w{ true false }.each do |the_string|
+ it "should compile when #{the_string} is a string" do
+ Puppet[:code] = "validate_string('#{the_string}')"
+ scope.compiler.compile
+ end
+
+ it "should not compile when #{the_string} is a bare word" do
+ Puppet[:code] = "validate_string(#{the_string})"
+ expect { scope.compiler.compile }.to raise_error(Puppet::ParseError, /is not a string/)
+ end
+ end
+
+ it "should compile when multiple string arguments are passed" do
+ Puppet[:code] = <<-'ENDofPUPPETcode'
+ $foo = ''
+ $bar = 'two'
+ validate_string($foo, $bar)
+ ENDofPUPPETcode
+ scope.compiler.compile
+ end
+
+ it "should compile when an explicitly undef variable is passed (NOTE THIS MAY NOT BE DESIRABLE)" do
+ Puppet[:code] = <<-'ENDofPUPPETcode'
+ $foo = undef
+ validate_string($foo)
+ ENDofPUPPETcode
+ scope.compiler.compile
+ end
+
+ it "should compile when an undefined variable is passed (NOTE THIS MAY NOT BE DESIRABLE)" do
+ Puppet[:code] = <<-'ENDofPUPPETcode'
+ validate_string($foobarbazishouldnotexist)
+ ENDofPUPPETcode
+ scope.compiler.compile
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/values_at_spec.rb b/puppet/modules/stdlib/spec/functions/values_at_spec.rb
new file mode 100755
index 00000000..86e3c31c
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/values_at_spec.rb
@@ -0,0 +1,38 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the values_at function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("values_at")).to eq("function_values_at")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_values_at([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should raise a ParseError if you try to use a range where stop is greater then start" do
+ expect { scope.function_values_at([['a','b'],["3-1"]]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should return a value at from an array" do
+ result = scope.function_values_at([['a','b','c'],"1"])
+ expect(result).to(eq(['b']))
+ end
+
+ it "should return a value at from an array when passed a range" do
+ result = scope.function_values_at([['a','b','c'],"0-1"])
+ expect(result).to(eq(['a','b']))
+ end
+
+ it "should return chosen values from an array when passed number of indexes" do
+ result = scope.function_values_at([['a','b','c'],["0","2"]])
+ expect(result).to(eq(['a','c']))
+ end
+
+ it "should return chosen values from an array when passed ranges and multiple indexes" do
+ result = scope.function_values_at([['a','b','c','d','e','f','g'],["0","2","4-5"]])
+ expect(result).to(eq(['a','c','e','f']))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/values_spec.rb b/puppet/modules/stdlib/spec/functions/values_spec.rb
new file mode 100755
index 00000000..08d21b03
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/values_spec.rb
@@ -0,0 +1,31 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the values function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("values")).to eq("function_values")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_values([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should return values from a hash" do
+ result = scope.function_values([{'a'=>'1','b'=>'2','c'=>'3'}])
+ # =~ is the RSpec::Matchers::MatchArray matcher.
+ # A.K.A. "array with same elements" (multiset) matching
+ expect(result).to match_array(%w{ 1 2 3 })
+ end
+
+ it "should return a multiset" do
+ result = scope.function_values([{'a'=>'1','b'=>'3','c'=>'3'}])
+ expect(result).to match_array(%w{ 1 3 3 })
+ expect(result).not_to match_array(%w{ 1 3 })
+ end
+
+ it "should raise a ParseError unless a Hash is provided" do
+ expect { scope.function_values([['a','b','c']]) }.to( raise_error(Puppet::ParseError))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/functions/zip_spec.rb b/puppet/modules/stdlib/spec/functions/zip_spec.rb
new file mode 100755
index 00000000..f265fcee
--- /dev/null
+++ b/puppet/modules/stdlib/spec/functions/zip_spec.rb
@@ -0,0 +1,31 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the zip function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_zip([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should be able to zip an array" do
+ result = scope.function_zip([['1','2','3'],['4','5','6']])
+ expect(result).to(eq([["1", "4"], ["2", "5"], ["3", "6"]]))
+ result = scope.function_zip([['1','2','3'],['4','5','6'], false])
+ result.should(eq([["1", "4"], ["2", "5"], ["3", "6"]]))
+ end
+
+ it "should be able to zip an array and flatten" do
+ result = scope.function_zip([['1','2','3'],['4','5','6'], true])
+ result.should(eq(["1", "4", "2", "5", "3", "6"]))
+ end
+
+ it "should accept objects which extend String for the second argument" do
+ class AlsoString < String
+ end
+
+ value = AlsoString.new('false')
+ result = scope.function_zip([['1','2','3'],['4','5','6'],value])
+ result.should(eq([["1", "4"], ["2", "5"], ["3", "6"]]))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/lib/puppet_spec/compiler.rb b/puppet/modules/stdlib/spec/lib/puppet_spec/compiler.rb
new file mode 100755
index 00000000..2f0ae4d7
--- /dev/null
+++ b/puppet/modules/stdlib/spec/lib/puppet_spec/compiler.rb
@@ -0,0 +1,47 @@
+#! /usr/bin/env ruby -S rspec
+module PuppetSpec::Compiler
+ def compile_to_catalog(string, node = Puppet::Node.new('foonode'))
+ Puppet[:code] = string
+ Puppet::Parser::Compiler.compile(node)
+ end
+
+ def compile_to_ral(manifest)
+ catalog = compile_to_catalog(manifest)
+ ral = catalog.to_ral
+ ral.finalize
+ ral
+ end
+
+ def compile_to_relationship_graph(manifest, prioritizer = Puppet::Graph::SequentialPrioritizer.new)
+ ral = compile_to_ral(manifest)
+ graph = Puppet::Graph::RelationshipGraph.new(prioritizer)
+ graph.populate_from(ral)
+ graph
+ end
+
+ if Puppet.version.to_f >= 3.3
+ def apply_compiled_manifest(manifest, prioritizer = Puppet::Graph::SequentialPrioritizer.new)
+ transaction = Puppet::Transaction.new(compile_to_ral(manifest),
+ Puppet::Transaction::Report.new("apply"),
+ prioritizer)
+ transaction.evaluate
+ transaction.report.finalize_report
+
+ transaction
+ end
+ else
+ def apply_compiled_manifest(manifest)
+ transaction = Puppet::Transaction.new(compile_to_ral(manifest), Puppet::Transaction::Report.new("apply"))
+ transaction.evaluate
+ transaction.report.finalize_report
+
+ transaction
+ end
+ end
+
+ def order_resources_traversed_in(relationships)
+ order_seen = []
+ relationships.traverse { |resource| order_seen << resource.ref }
+ order_seen
+ end
+end
diff --git a/puppet/modules/stdlib/spec/lib/puppet_spec/database.rb b/puppet/modules/stdlib/spec/lib/puppet_spec/database.rb
new file mode 100755
index 00000000..f5c23417
--- /dev/null
+++ b/puppet/modules/stdlib/spec/lib/puppet_spec/database.rb
@@ -0,0 +1,30 @@
+#! /usr/bin/env ruby -S rspec
+# This just makes some nice things available at global scope, and for setup of
+# tests to use a real fake database, rather than a fake stubs-that-don't-work
+# version of the same. Fun times.
+def sqlite?
+ if $sqlite.nil?
+ begin
+ require 'sqlite3'
+ $sqlite = true
+ rescue LoadError
+ $sqlite = false
+ end
+ end
+ $sqlite
+end
+
+def can_use_scratch_database?
+ sqlite? and Puppet.features.rails?
+end
+
+
+# This is expected to be called in your `before :each` block, and will get you
+# ready to roll with a serious database and all. Cleanup is handled
+# automatically for you. Nothing to do there.
+def setup_scratch_database
+ Puppet[:dbadapter] = 'sqlite3'
+ Puppet[:dblocation] = ':memory:'
+ Puppet[:railslog] = PuppetSpec::Files.tmpfile('storeconfigs.log')
+ Puppet::Rails.init
+end
diff --git a/puppet/modules/stdlib/spec/lib/puppet_spec/files.rb b/puppet/modules/stdlib/spec/lib/puppet_spec/files.rb
new file mode 100755
index 00000000..71b38ffe
--- /dev/null
+++ b/puppet/modules/stdlib/spec/lib/puppet_spec/files.rb
@@ -0,0 +1,61 @@
+#! /usr/bin/env ruby -S rspec
+require 'fileutils'
+require 'tempfile'
+require 'tmpdir'
+require 'pathname'
+
+# A support module for testing files.
+module PuppetSpec::Files
+ def self.cleanup
+ $global_tempfiles ||= []
+ while path = $global_tempfiles.pop do
+ begin
+ Dir.unstub(:entries)
+ FileUtils.rm_rf path, :secure => true
+ rescue Errno::ENOENT
+ # nothing to do
+ end
+ end
+ end
+
+ def make_absolute(path) PuppetSpec::Files.make_absolute(path) end
+ def self.make_absolute(path)
+ path = File.expand_path(path)
+ path[0] = 'c' if Puppet.features.microsoft_windows?
+ path
+ end
+
+ def tmpfile(name, dir = nil) PuppetSpec::Files.tmpfile(name, dir) end
+ def self.tmpfile(name, dir = nil)
+ # Generate a temporary file, just for the name...
+ source = dir ? Tempfile.new(name, dir) : Tempfile.new(name)
+ path = source.path
+ source.close!
+
+ record_tmp(File.expand_path(path))
+
+ path
+ end
+
+ def file_containing(name, contents) PuppetSpec::Files.file_containing(name, contents) end
+ def self.file_containing(name, contents)
+ file = tmpfile(name)
+ File.open(file, 'wb') { |f| f.write(contents) }
+ file
+ end
+
+ def tmpdir(name) PuppetSpec::Files.tmpdir(name) end
+ def self.tmpdir(name)
+ dir = Dir.mktmpdir(name)
+
+ record_tmp(dir)
+
+ dir
+ end
+
+ def self.record_tmp(tmp)
+ # ...record it for cleanup,
+ $global_tempfiles ||= []
+ $global_tempfiles << tmp
+ end
+end
diff --git a/puppet/modules/stdlib/spec/lib/puppet_spec/fixtures.rb b/puppet/modules/stdlib/spec/lib/puppet_spec/fixtures.rb
new file mode 100755
index 00000000..81e9775f
--- /dev/null
+++ b/puppet/modules/stdlib/spec/lib/puppet_spec/fixtures.rb
@@ -0,0 +1,29 @@
+#! /usr/bin/env ruby -S rspec
+module PuppetSpec::Fixtures
+ def fixtures(*rest)
+ File.join(PuppetSpec::FIXTURE_DIR, *rest)
+ end
+ def my_fixture_dir
+ callers = caller
+ while line = callers.shift do
+ next unless found = line.match(%r{/spec/(.*)_spec\.rb:})
+ return fixtures(found[1])
+ end
+ fail "sorry, I couldn't work out your path from the caller stack!"
+ end
+ def my_fixture(name)
+ file = File.join(my_fixture_dir, name)
+ unless File.readable? file then
+ fail Puppet::DevError, "fixture '#{name}' for #{my_fixture_dir} is not readable"
+ end
+ return file
+ end
+ def my_fixtures(glob = '*', flags = 0)
+ files = Dir.glob(File.join(my_fixture_dir, glob), flags)
+ unless files.length > 0 then
+ fail Puppet::DevError, "fixture '#{glob}' for #{my_fixture_dir} had no files!"
+ end
+ block_given? and files.each do |file| yield file end
+ files
+ end
+end
diff --git a/puppet/modules/stdlib/spec/lib/puppet_spec/matchers.rb b/puppet/modules/stdlib/spec/lib/puppet_spec/matchers.rb
new file mode 100755
index 00000000..093d77c8
--- /dev/null
+++ b/puppet/modules/stdlib/spec/lib/puppet_spec/matchers.rb
@@ -0,0 +1,121 @@
+#! /usr/bin/env ruby -S rspec
+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 :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
+
+class HavePrintedMatcher
+ attr_accessor :expected, :actual
+
+ def initialize(expected)
+ case expected
+ when String, Regexp
+ @expected = expected
+ else
+ @expected = expected.to_s
+ end
+ end
+
+ def matches?(block)
+ begin
+ $stderr = $stdout = StringIO.new
+ $stdout.set_encoding('UTF-8') if $stdout.respond_to?(:set_encoding)
+ block.call
+ $stdout.rewind
+ @actual = $stdout.read
+ ensure
+ $stdout = STDOUT
+ $stderr = STDERR
+ end
+
+ if @actual then
+ case @expected
+ when String
+ @actual.include? @expected
+ when Regexp
+ @expected.match @actual
+ end
+ else
+ false
+ end
+ end
+
+ def failure_message_for_should
+ if @actual.nil? then
+ "expected #{@expected.inspect}, but nothing was printed"
+ else
+ "expected #{@expected.inspect} to be printed; got:\n#{@actual}"
+ end
+ end
+
+ def failure_message_for_should_not
+ "expected #{@expected.inspect} to not be printed; got:\n#{@actual}"
+ end
+
+ def description
+ "expect #{@expected.inspect} to be printed"
+ end
+end
+
+def have_printed(what)
+ HavePrintedMatcher.new(what)
+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 :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/puppet/modules/stdlib/spec/lib/puppet_spec/modules.rb b/puppet/modules/stdlib/spec/lib/puppet_spec/modules.rb
new file mode 100755
index 00000000..910c6d94
--- /dev/null
+++ b/puppet/modules/stdlib/spec/lib/puppet_spec/modules.rb
@@ -0,0 +1,27 @@
+#! /usr/bin/env ruby -S rspec
+module PuppetSpec::Modules
+ class << self
+ def create(name, dir, options = {})
+ module_dir = File.join(dir, name)
+ FileUtils.mkdir_p(module_dir)
+
+ environment = options[:environment]
+
+ if metadata = options[:metadata]
+ metadata[:source] ||= 'github'
+ metadata[:author] ||= 'puppetlabs'
+ metadata[:version] ||= '9.9.9'
+ metadata[:license] ||= 'to kill'
+ metadata[:dependencies] ||= []
+
+ metadata[:name] = "#{metadata[:author]}/#{name}"
+
+ File.open(File.join(module_dir, 'metadata.json'), 'w') do |f|
+ f.write(metadata.to_pson)
+ end
+ end
+
+ Puppet::Module.new(name, module_dir, environment)
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/lib/puppet_spec/pops.rb b/puppet/modules/stdlib/spec/lib/puppet_spec/pops.rb
new file mode 100755
index 00000000..e056a52b
--- /dev/null
+++ b/puppet/modules/stdlib/spec/lib/puppet_spec/pops.rb
@@ -0,0 +1,17 @@
+#! /usr/bin/env ruby -S rspec
+module PuppetSpec::Pops
+ extend RSpec::Matchers::DSL
+
+ # Checks if an Acceptor has a specific issue in its list of diagnostics
+ matcher :have_issue do |expected|
+ match do |actual|
+ actual.diagnostics.index { |i| i.issue == expected } != nil
+ end
+ failure_message_for_should do |actual|
+ "expected Acceptor[#{actual.diagnostics.collect { |i| i.issue.issue_code }.join(',')}] to contain issue #{expected.issue_code}"
+ end
+ failure_message_for_should_not do |actual|
+ "expected Acceptor[#{actual.diagnostics.collect { |i| i.issue.issue_code }.join(',')}] to not contain issue #{expected.issue_code}"
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/lib/puppet_spec/scope.rb b/puppet/modules/stdlib/spec/lib/puppet_spec/scope.rb
new file mode 100755
index 00000000..3847ede1
--- /dev/null
+++ b/puppet/modules/stdlib/spec/lib/puppet_spec/scope.rb
@@ -0,0 +1,15 @@
+#! /usr/bin/env ruby -S rspec
+
+module PuppetSpec::Scope
+ # Initialize a new scope suitable for testing.
+ #
+ def create_test_scope_for_node(node_name)
+ node = Puppet::Node.new(node_name)
+ compiler = Puppet::Parser::Compiler.new(node)
+ scope = Puppet::Parser::Scope.new(compiler)
+ scope.source = Puppet::Resource::Type.new(:node, node_name)
+ scope.parent = compiler.topscope
+ scope
+ end
+
+end \ No newline at end of file
diff --git a/puppet/modules/stdlib/spec/lib/puppet_spec/settings.rb b/puppet/modules/stdlib/spec/lib/puppet_spec/settings.rb
new file mode 100755
index 00000000..8ddcb975
--- /dev/null
+++ b/puppet/modules/stdlib/spec/lib/puppet_spec/settings.rb
@@ -0,0 +1,16 @@
+#! /usr/bin/env ruby -S rspec
+module PuppetSpec::Settings
+
+ # It would probably be preferable to refactor defaults.rb such that the real definitions of
+ # these settings were available as a variable, which was then accessible for use during tests.
+ # However, I'm not doing that yet because I don't want to introduce any additional moving parts
+ # to this already very large changeset.
+ # Would be nice to clean this up later. --cprice 2012-03-20
+ TEST_APP_DEFAULT_DEFINITIONS = {
+ :name => { :default => "test", :desc => "name" },
+ :logdir => { :type => :directory, :default => "test", :desc => "logdir" },
+ :confdir => { :type => :directory, :default => "test", :desc => "confdir" },
+ :vardir => { :type => :directory, :default => "test", :desc => "vardir" },
+ :rundir => { :type => :directory, :default => "test", :desc => "rundir" },
+ }
+end
diff --git a/puppet/modules/stdlib/spec/lib/puppet_spec/verbose.rb b/puppet/modules/stdlib/spec/lib/puppet_spec/verbose.rb
new file mode 100755
index 00000000..b2683df0
--- /dev/null
+++ b/puppet/modules/stdlib/spec/lib/puppet_spec/verbose.rb
@@ -0,0 +1,10 @@
+#! /usr/bin/env ruby -S rspec
+# Support code for running stuff with warnings disabled.
+module Kernel
+ def with_verbose_disabled
+ verbose, $VERBOSE = $VERBOSE, nil
+ result = yield
+ $VERBOSE = verbose
+ return result
+ end
+end
diff --git a/puppet/modules/stdlib/spec/monkey_patches/alias_should_to_must.rb b/puppet/modules/stdlib/spec/monkey_patches/alias_should_to_must.rb
new file mode 100755
index 00000000..505e2409
--- /dev/null
+++ b/puppet/modules/stdlib/spec/monkey_patches/alias_should_to_must.rb
@@ -0,0 +1,9 @@
+#! /usr/bin/env ruby -S rspec
+require 'rspec'
+
+class Object
+ # This is necessary because the RAL has a 'should'
+ # method.
+ alias :must :should
+ alias :must_not :should_not
+end
diff --git a/puppet/modules/stdlib/spec/monkey_patches/publicize_methods.rb b/puppet/modules/stdlib/spec/monkey_patches/publicize_methods.rb
new file mode 100755
index 00000000..3ae59f97
--- /dev/null
+++ b/puppet/modules/stdlib/spec/monkey_patches/publicize_methods.rb
@@ -0,0 +1,11 @@
+#! /usr/bin/env ruby -S rspec
+# Some monkey-patching to allow us to test private methods.
+class Class
+ def publicize_methods(*methods)
+ saved_private_instance_methods = methods.empty? ? self.private_instance_methods : methods
+
+ self.class_eval { public(*saved_private_instance_methods) }
+ yield
+ self.class_eval { private(*saved_private_instance_methods) }
+ end
+end
diff --git a/puppet/modules/stdlib/spec/spec.opts b/puppet/modules/stdlib/spec/spec.opts
new file mode 100644
index 00000000..91cd6427
--- /dev/null
+++ b/puppet/modules/stdlib/spec/spec.opts
@@ -0,0 +1,6 @@
+--format
+s
+--colour
+--loadby
+mtime
+--backtrace
diff --git a/puppet/modules/stdlib/spec/spec_helper.rb b/puppet/modules/stdlib/spec/spec_helper.rb
new file mode 100755
index 00000000..b490ca3c
--- /dev/null
+++ b/puppet/modules/stdlib/spec/spec_helper.rb
@@ -0,0 +1,34 @@
+#! /usr/bin/env ruby -S rspec
+dir = File.expand_path(File.dirname(__FILE__))
+$LOAD_PATH.unshift File.join(dir, 'lib')
+
+# So everyone else doesn't have to include this base constant.
+module PuppetSpec
+ FIXTURE_DIR = File.join(dir = File.expand_path(File.dirname(__FILE__)), "fixtures") unless defined?(FIXTURE_DIR)
+end
+
+require 'puppet'
+require 'rspec-puppet'
+require 'puppetlabs_spec_helper/module_spec_helper'
+require 'puppet_spec/verbose'
+require 'puppet_spec/files'
+require 'puppet_spec/settings'
+require 'puppet_spec/fixtures'
+require 'puppet_spec/matchers'
+require 'puppet_spec/database'
+require 'monkey_patches/alias_should_to_must'
+require 'mocha/setup'
+
+
+
+RSpec.configure do |config|
+ config.before :each do
+ # Ensure that we don't accidentally cache facts and environment between
+ # test cases. This requires each example group to explicitly load the
+ # facts being exercised with something like
+ # Facter.collection.loader.load(:ipaddress)
+ Facter::Util::Loader.any_instance.stubs(:load_all)
+ Facter.clear
+ Facter.clear_messages
+ end
+end
diff --git a/puppet/modules/stdlib/spec/spec_helper_acceptance.rb b/puppet/modules/stdlib/spec/spec_helper_acceptance.rb
new file mode 100755
index 00000000..3203ce9f
--- /dev/null
+++ b/puppet/modules/stdlib/spec/spec_helper_acceptance.rb
@@ -0,0 +1,50 @@
+#! /usr/bin/env ruby -S rspec
+require 'beaker-rspec'
+
+UNSUPPORTED_PLATFORMS = []
+
+unless ENV['RS_PROVISION'] == 'no' or ENV['BEAKER_provision'] == 'no'
+ foss_opts = {
+ :default_action => 'gem_install',
+ :version => (ENV['PUPPET_VERSION'] ? ENV['PUPPET_VERSION'] : '3.7.2'),
+ }
+
+ if default.is_pe?; then install_pe; else install_puppet( foss_opts ); end
+
+ hosts.each do |host|
+ if host['platform'] !~ /windows/i
+ if host.is_pe?
+ on host, 'mkdir -p /etc/puppetlabs/facter/facts.d'
+ else
+ on host, "/bin/touch #{host['puppetpath']}/hiera.yaml"
+ on host, "mkdir -p #{host['distmoduledir']}"
+ on host, 'mkdir -p /etc/facter/facts.d'
+ end
+ end
+ end
+end
+
+RSpec.configure do |c|
+ # Project root
+ proj_root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
+
+ # Readable test descriptions
+ c.formatter = :documentation
+
+ # Configure all nodes in nodeset
+ c.before :suite do
+ if ENV['FUTURE_PARSER'] == 'true'
+ default[:default_apply_opts] ||= {}
+ default[:default_apply_opts].merge!({:parser => 'future'})
+ end
+
+ copy_root_module_to(default, :source => proj_root, :module_name => 'stdlib')
+ end
+end
+
+def is_future_parser_enabled?
+ if default[:default_apply_opts]
+ return default[:default_apply_opts][:parser] == 'future'
+ end
+ return false
+end
diff --git a/puppet/modules/stdlib/spec/unit/facter/facter_dot_d_spec.rb b/puppet/modules/stdlib/spec/unit/facter/facter_dot_d_spec.rb
new file mode 100755
index 00000000..0afadb25
--- /dev/null
+++ b/puppet/modules/stdlib/spec/unit/facter/facter_dot_d_spec.rb
@@ -0,0 +1,32 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+require 'facter/facter_dot_d'
+
+describe Facter::Util::DotD do
+
+ context 'returns a simple fact' do
+ before :each do
+ Facter.stubs(:version).returns('1.6.1')
+ subject.stubs(:entries).returns(['/etc/facter/facts.d/fake_fact.txt'])
+ File.stubs(:readlines).with('/etc/facter/facts.d/fake_fact.txt').returns(['fake_fact=fake fact'])
+ subject.create
+ end
+
+ it 'should return successfully' do
+ expect(Facter.fact(:fake_fact).value).to eq('fake fact')
+ end
+ end
+
+ context 'returns a fact with equals signs' do
+ before :each do
+ Facter.stubs(:version).returns('1.6.1')
+ subject.stubs(:entries).returns(['/etc/facter/facts.d/foo.txt'])
+ File.stubs(:readlines).with('/etc/facter/facts.d/foo.txt').returns(['foo=1+1=2'])
+ subject.create
+ end
+
+ it 'should return successfully' do
+ expect(Facter.fact(:foo).value).to eq('1+1=2')
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/unit/facter/pe_version_spec.rb b/puppet/modules/stdlib/spec/unit/facter/pe_version_spec.rb
new file mode 100755
index 00000000..4d0349e6
--- /dev/null
+++ b/puppet/modules/stdlib/spec/unit/facter/pe_version_spec.rb
@@ -0,0 +1,76 @@
+#!/usr/bin/env rspec
+
+require 'spec_helper'
+
+describe "PE Version specs" do
+ before :each do
+ # Explicitly load the pe_version.rb file which contains generated facts
+ # that cannot be automatically loaded. Puppet 2.x implements
+ # Facter.collection.load while Facter 1.x markes Facter.collection.load as
+ # a private method.
+ if Facter.collection.respond_to? :load
+ Facter.collection.load(:pe_version)
+ else
+ Facter.collection.loader.load(:pe_version)
+ end
+ end
+
+ context "If PE is installed" do
+ %w{ 2.6.1 2.10.300 }.each do |version|
+ puppetversion = "2.7.19 (Puppet Enterprise #{version})"
+ context "puppetversion => #{puppetversion}" do
+ before :each do
+ Facter.fact(:puppetversion).stubs(:value).returns(puppetversion)
+ end
+
+ (major,minor,patch) = version.split(".")
+
+ it "Should return true" do
+ expect(Facter.fact(:is_pe).value).to eq(true)
+ end
+
+ it "Should have a version of #{version}" do
+ expect(Facter.fact(:pe_version).value).to eq(version)
+ end
+
+ it "Should have a major version of #{major}" do
+ expect(Facter.fact(:pe_major_version).value).to eq(major)
+ end
+
+ it "Should have a minor version of #{minor}" do
+ expect(Facter.fact(:pe_minor_version).value).to eq(minor)
+ end
+
+ it "Should have a patch version of #{patch}" do
+ expect(Facter.fact(:pe_patch_version).value).to eq(patch)
+ end
+ end
+ end
+ end
+
+ context "When PE is not installed" do
+ before :each do
+ Facter.fact(:puppetversion).stubs(:value).returns("2.7.19")
+ end
+
+ it "is_pe is false" do
+ expect(Facter.fact(:is_pe).value).to eq(false)
+ end
+
+ it "pe_version is nil" do
+ expect(Facter.fact(:pe_version).value).to be_nil
+ end
+
+ it "pe_major_version is nil" do
+ expect(Facter.fact(:pe_major_version).value).to be_nil
+ end
+
+ it "pe_minor_version is nil" do
+ expect(Facter.fact(:pe_minor_version).value).to be_nil
+ end
+
+ it "Should have a patch version" do
+ expect(Facter.fact(:pe_patch_version).value).to be_nil
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/unit/facter/root_home_spec.rb b/puppet/modules/stdlib/spec/unit/facter/root_home_spec.rb
new file mode 100755
index 00000000..98fe1419
--- /dev/null
+++ b/puppet/modules/stdlib/spec/unit/facter/root_home_spec.rb
@@ -0,0 +1,52 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+require 'facter/root_home'
+
+describe Facter::Util::RootHome do
+ context "solaris" do
+ let(:root_ent) { "root:x:0:0:Super-User:/:/sbin/sh" }
+ let(:expected_root_home) { "/" }
+
+ it "should return /" do
+ Facter::Util::Resolution.expects(:exec).with("getent passwd root").returns(root_ent)
+ expect(Facter::Util::RootHome.get_root_home).to eq(expected_root_home)
+ end
+ end
+ context "linux" do
+ let(:root_ent) { "root:x:0:0:root:/root:/bin/bash" }
+ let(:expected_root_home) { "/root" }
+
+ it "should return /root" do
+ Facter::Util::Resolution.expects(:exec).with("getent passwd root").returns(root_ent)
+ expect(Facter::Util::RootHome.get_root_home).to eq(expected_root_home)
+ end
+ end
+ context "windows" do
+ before :each do
+ Facter::Util::Resolution.expects(:exec).with("getent passwd root").returns(nil)
+ end
+ it "should be nil on windows" do
+ expect(Facter::Util::RootHome.get_root_home).to be_nil
+ end
+ end
+end
+
+describe 'root_home', :type => :fact do
+ before { Facter.clear }
+ after { Facter.clear }
+
+ context "macosx" do
+ before do
+ Facter.fact(:kernel).stubs(:value).returns("Darwin")
+ Facter.fact(:osfamily).stubs(:value).returns("Darwin")
+ end
+ let(:expected_root_home) { "/var/root" }
+ sample_dscacheutil = File.read(fixtures('dscacheutil','root'))
+
+ it "should return /var/root" do
+ Facter::Util::Resolution.stubs(:exec).with("dscacheutil -q user -a name root").returns(sample_dscacheutil)
+ expect(Facter.fact(:root_home).value).to eq(expected_root_home)
+ end
+ end
+
+end
diff --git a/puppet/modules/stdlib/spec/unit/facter/util/puppet_settings_spec.rb b/puppet/modules/stdlib/spec/unit/facter/util/puppet_settings_spec.rb
new file mode 100755
index 00000000..c06137d7
--- /dev/null
+++ b/puppet/modules/stdlib/spec/unit/facter/util/puppet_settings_spec.rb
@@ -0,0 +1,36 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+require 'facter/util/puppet_settings'
+
+describe Facter::Util::PuppetSettings do
+
+ describe "#with_puppet" do
+ context "Without Puppet loaded" do
+ before(:each) do
+ Module.expects(:const_get).with("Puppet").raises(NameError)
+ end
+
+ it 'should be nil' do
+ expect(subject.with_puppet { Puppet[:vardir] }).to be_nil
+ end
+ it 'should not yield to the block' do
+ Puppet.expects(:[]).never
+ expect(subject.with_puppet { Puppet[:vardir] }).to be_nil
+ end
+ end
+ context "With Puppet loaded" do
+ module Puppet; end
+ let(:vardir) { "/var/lib/puppet" }
+
+ before :each do
+ Puppet.expects(:[]).with(:vardir).returns vardir
+ end
+ it 'should yield to the block' do
+ subject.with_puppet { Puppet[:vardir] }
+ end
+ it 'should return the nodes vardir' do
+ expect(subject.with_puppet { Puppet[:vardir] }).to eq vardir
+ end
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/unit/puppet/functions/type_of_spec.rb b/puppet/modules/stdlib/spec/unit/puppet/functions/type_of_spec.rb
new file mode 100644
index 00000000..8afb6246
--- /dev/null
+++ b/puppet/modules/stdlib/spec/unit/puppet/functions/type_of_spec.rb
@@ -0,0 +1,33 @@
+#! /usr/bin/env ruby -S rspec
+
+require 'spec_helper'
+
+if ENV["FUTURE_PARSER"] == 'yes' or Puppet.version >= "4"
+ require 'puppet/pops'
+ require 'puppet/loaders'
+
+ describe 'the type_of function' do
+ before(:all) do
+ loaders = Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, [File.join(fixtures, "modules")]))
+ Puppet.push_context({:loaders => loaders}, "test-examples")
+ end
+
+ after(:all) do
+ Puppet::Pops::Loaders.clear
+ Puppet::pop_context()
+ end
+
+ let(:func) do
+ # Load the function from the environment modulepath's modules (ie, fixtures)
+ Puppet.lookup(:loaders).private_environment_loader.load(:function, 'type_of')
+ end
+
+ it 'gives the type of a string' do
+ expect(func.call({}, 'hello world')).to be_kind_of(Puppet::Pops::Types::PStringType)
+ end
+
+ it 'gives the type of an integer' do
+ expect(func.call({}, 5)).to be_kind_of(Puppet::Pops::Types::PIntegerType)
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/unit/puppet/parser/functions/basename_spec.rb b/puppet/modules/stdlib/spec/unit/puppet/parser/functions/basename_spec.rb
new file mode 100755
index 00000000..8a2d0dc3
--- /dev/null
+++ b/puppet/modules/stdlib/spec/unit/puppet/parser/functions/basename_spec.rb
@@ -0,0 +1,46 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the basename function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ Puppet::Parser::Functions.function("basename").should == "function_basename"
+ end
+
+ it "should raise a ParseError if there is less than 1 argument" do
+ lambda { scope.function_basename([]) }.should( raise_error(Puppet::ParseError))
+ end
+
+ it "should raise a ParseError if there are more than 2 arguments" do
+ lambda { scope.function_basename(['a', 'b', 'c']) }.should( raise_error(Puppet::ParseError))
+ end
+
+ it "should return basename for an absolute path" do
+ result = scope.function_basename(['/path/to/a/file.ext'])
+ result.should(eq('file.ext'))
+ end
+
+ it "should return basename for a relative path" do
+ result = scope.function_basename(['path/to/a/file.ext'])
+ result.should(eq('file.ext'))
+ end
+
+ it "should strip extention when extension specified (absolute path)" do
+ result = scope.function_basename(['/path/to/a/file.ext', '.ext'])
+ result.should(eq('file'))
+ end
+
+ it "should strip extention when extension specified (relative path)" do
+ result = scope.function_basename(['path/to/a/file.ext', '.ext'])
+ result.should(eq('file'))
+ end
+
+ it "should complain about non-string first argument" do
+ lambda { scope.function_basename([[]]) }.should( raise_error(Puppet::ParseError))
+ end
+
+ it "should complain about non-string second argument" do
+ lambda { scope.function_basename(['/path/to/a/file.ext', []]) }.should( raise_error(Puppet::ParseError))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/unit/puppet/parser/functions/bool2str_spec.rb b/puppet/modules/stdlib/spec/unit/puppet/parser/functions/bool2str_spec.rb
new file mode 100755
index 00000000..b8788918
--- /dev/null
+++ b/puppet/modules/stdlib/spec/unit/puppet/parser/functions/bool2str_spec.rb
@@ -0,0 +1,46 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the bool2str function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("bool2str")).to eq("function_bool2str")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_bool2str([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should convert true to 'true'" do
+ result = scope.function_bool2str([true])
+ expect(result).to(eq('true'))
+ end
+
+ it "should convert true to a string" do
+ result = scope.function_bool2str([true])
+ expect(result.class).to(eq(String))
+ end
+
+ it "should convert false to 'false'" do
+ result = scope.function_bool2str([false])
+ expect(result).to(eq('false'))
+ end
+
+ it "should convert false to a string" do
+ result = scope.function_bool2str([false])
+ expect(result.class).to(eq(String))
+ end
+
+ it "should not accept a string" do
+ expect { scope.function_bool2str(["false"]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should not accept a nil value" do
+ expect { scope.function_bool2str([nil]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should not accept an undef" do
+ expect { scope.function_bool2str([:undef]) }.to( raise_error(Puppet::ParseError))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/unit/puppet/parser/functions/camelcase_spec.rb b/puppet/modules/stdlib/spec/unit/puppet/parser/functions/camelcase_spec.rb
new file mode 100755
index 00000000..70382adb
--- /dev/null
+++ b/puppet/modules/stdlib/spec/unit/puppet/parser/functions/camelcase_spec.rb
@@ -0,0 +1,24 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the camelcase function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ expect(Puppet::Parser::Functions.function("camelcase")).to eq("function_camelcase")
+ end
+
+ it "should raise a ParseError if there is less than 1 arguments" do
+ expect { scope.function_camelcase([]) }.to( raise_error(Puppet::ParseError))
+ end
+
+ it "should capitalize the beginning of a normal string" do
+ result = scope.function_camelcase(["abc"])
+ expect(result).to(eq("Abc"))
+ end
+
+ it "should camelcase an underscore-delimited string" do
+ result = scope.function_camelcase(["aa_bb_cc"])
+ expect(result).to(eq("AaBbCc"))
+ end
+end
diff --git a/puppet/modules/stdlib/spec/unit/puppet/parser/functions/str2saltedsha1_spec.rb b/puppet/modules/stdlib/spec/unit/puppet/parser/functions/str2saltedsha1_spec.rb
new file mode 100644
index 00000000..753cb24a
--- /dev/null
+++ b/puppet/modules/stdlib/spec/unit/puppet/parser/functions/str2saltedsha1_spec.rb
@@ -0,0 +1,45 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+
+describe "the str2saltedsha1 function" do
+ let(:scope) { PuppetlabsSpec::PuppetInternals.scope }
+
+ it "should exist" do
+ Puppet::Parser::Functions.function("str2saltedsha1").should == "function_str2saltedsha1"
+ end
+
+ it "should raise a ParseError if there is less than 1 argument" do
+ expect { scope.function_str2saltedsha1([]) }.should( raise_error(Puppet::ParseError) )
+ end
+
+ it "should raise a ParseError if there is more than 1 argument" do
+ expect { scope.function_str2saltedsha1(['foo', 'bar', 'baz']) }.should( raise_error(Puppet::ParseError) )
+ end
+
+ it "should return a salted-sha1 password hash 136 characters in length" do
+ result = scope.function_str2saltedsha1(["password"])
+ result.length.should(eq(136))
+ end
+
+ it "should raise an error if you pass a non-string password" do
+ expect { scope.function_str2saltedsha1([1234]) }.should( raise_error(Puppet::ParseError) )
+ end
+
+ it "should generate a valid password" do
+ # Allow the function to generate a password based on the string 'password'
+ password_hash = scope.function_str2saltedsha1(["password"])
+
+ # Separate the Salt and Password from the Password Hash
+ salt = password_hash[0..7]
+ password = password_hash[8..-1]
+
+ # Convert the Salt and Password from Hex to Binary Data
+ str_salt = Array(salt.lines).pack('H*')
+ str_password = Array(password.lines).pack('H*')
+
+ # Combine the Binary Salt with 'password' and compare the end result
+ saltedpass = Digest::SHA1.digest(str_salt + 'password')
+ result = (str_salt + saltedpass).unpack('H*')[0]
+ result.should == password_hash
+ end
+end
diff --git a/puppet/modules/stdlib/spec/unit/puppet/provider/file_line/ruby_spec.rb b/puppet/modules/stdlib/spec/unit/puppet/provider/file_line/ruby_spec.rb
new file mode 100755
index 00000000..d2a129c3
--- /dev/null
+++ b/puppet/modules/stdlib/spec/unit/puppet/provider/file_line/ruby_spec.rb
@@ -0,0 +1,225 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+require 'tempfile'
+provider_class = Puppet::Type.type(:file_line).provider(:ruby)
+describe provider_class do
+ context "when adding" do
+ let :tmpfile do
+ tmp = Tempfile.new('tmp')
+ path = tmp.path
+ tmp.close!
+ path
+ end
+ let :resource do
+ Puppet::Type::File_line.new(
+ {:name => 'foo', :path => tmpfile, :line => 'foo'}
+ )
+ end
+ let :provider do
+ provider_class.new(resource)
+ end
+
+ it 'should detect if the line exists in the file' do
+ File.open(tmpfile, 'w') do |fh|
+ fh.write('foo')
+ end
+ expect(provider.exists?).to be_truthy
+ end
+ it 'should detect if the line does not exist in the file' do
+ File.open(tmpfile, 'w') do |fh|
+ fh.write('foo1')
+ end
+ expect(provider.exists?).to be_nil
+ end
+ it 'should append to an existing file when creating' do
+ provider.create
+ expect(File.read(tmpfile).chomp).to eq('foo')
+ end
+ end
+
+ context "when matching" do
+ before :each do
+ # TODO: these should be ported over to use the PuppetLabs spec_helper
+ # file fixtures once the following pull request has been merged:
+ # https://github.com/puppetlabs/puppetlabs-stdlib/pull/73/files
+ tmp = Tempfile.new('tmp')
+ @tmpfile = tmp.path
+ tmp.close!
+ @resource = Puppet::Type::File_line.new(
+ {
+ :name => 'foo',
+ :path => @tmpfile,
+ :line => 'foo = bar',
+ :match => '^foo\s*=.*$',
+ }
+ )
+ @provider = provider_class.new(@resource)
+ end
+
+ describe 'using match' do
+ it 'should raise an error if more than one line matches, and should not have modified the file' do
+ File.open(@tmpfile, 'w') do |fh|
+ fh.write("foo1\nfoo=blah\nfoo2\nfoo=baz")
+ end
+ expect(@provider.exists?).to be_nil
+ expect { @provider.create }.to raise_error(Puppet::Error, /More than one line.*matches/)
+ expect(File.read(@tmpfile)).to eql("foo1\nfoo=blah\nfoo2\nfoo=baz")
+ end
+
+ it 'should replace all lines that matches' do
+ @resource = Puppet::Type::File_line.new(
+ {
+ :name => 'foo',
+ :path => @tmpfile,
+ :line => 'foo = bar',
+ :match => '^foo\s*=.*$',
+ :multiple => true
+ }
+ )
+ @provider = provider_class.new(@resource)
+ File.open(@tmpfile, 'w') do |fh|
+ fh.write("foo1\nfoo=blah\nfoo2\nfoo=baz")
+ end
+ expect(@provider.exists?).to be_nil
+ @provider.create
+ expect(File.read(@tmpfile).chomp).to eql("foo1\nfoo = bar\nfoo2\nfoo = bar")
+ end
+
+ it 'should raise an error with invalid values' do
+ expect {
+ @resource = Puppet::Type::File_line.new(
+ {
+ :name => 'foo',
+ :path => @tmpfile,
+ :line => 'foo = bar',
+ :match => '^foo\s*=.*$',
+ :multiple => 'asgadga'
+ }
+ )
+ }.to raise_error(Puppet::Error, /Invalid value "asgadga"\. Valid values are true, false\./)
+ end
+
+ it 'should replace a line that matches' do
+ File.open(@tmpfile, 'w') do |fh|
+ fh.write("foo1\nfoo=blah\nfoo2")
+ end
+ expect(@provider.exists?).to be_nil
+ @provider.create
+ expect(File.read(@tmpfile).chomp).to eql("foo1\nfoo = bar\nfoo2")
+ end
+ it 'should add a new line if no lines match' do
+ File.open(@tmpfile, 'w') do |fh|
+ fh.write("foo1\nfoo2")
+ end
+ expect(@provider.exists?).to be_nil
+ @provider.create
+ expect(File.read(@tmpfile)).to eql("foo1\nfoo2\nfoo = bar\n")
+ end
+ it 'should do nothing if the exact line already exists' do
+ File.open(@tmpfile, 'w') do |fh|
+ fh.write("foo1\nfoo = bar\nfoo2")
+ end
+ expect(@provider.exists?).to be_truthy
+ @provider.create
+ expect(File.read(@tmpfile).chomp).to eql("foo1\nfoo = bar\nfoo2")
+ end
+ end
+
+ describe 'using after' do
+ let :resource do
+ Puppet::Type::File_line.new(
+ {
+ :name => 'foo',
+ :path => @tmpfile,
+ :line => 'inserted = line',
+ :after => '^foo1',
+ }
+ )
+ end
+
+ let :provider do
+ provider_class.new(resource)
+ end
+
+ context 'with one line matching the after expression' do
+ before :each do
+ File.open(@tmpfile, 'w') do |fh|
+ fh.write("foo1\nfoo = blah\nfoo2\nfoo = baz")
+ end
+ end
+
+ it 'inserts the specified line after the line matching the "after" expression' do
+ provider.create
+ expect(File.read(@tmpfile).chomp).to eql("foo1\ninserted = line\nfoo = blah\nfoo2\nfoo = baz")
+ end
+ end
+
+ context 'with two lines matching the after expression' do
+ before :each do
+ File.open(@tmpfile, 'w') do |fh|
+ fh.write("foo1\nfoo = blah\nfoo2\nfoo1\nfoo = baz")
+ end
+ end
+
+ it 'errors out stating "One or no line must match the pattern"' do
+ expect { provider.create }.to raise_error(Puppet::Error, /One or no line must match the pattern/)
+ end
+ end
+
+ context 'with no lines matching the after expression' do
+ let :content do
+ "foo3\nfoo = blah\nfoo2\nfoo = baz\n"
+ end
+
+ before :each do
+ File.open(@tmpfile, 'w') do |fh|
+ fh.write(content)
+ end
+ end
+
+ it 'appends the specified line to the file' do
+ provider.create
+ expect(File.read(@tmpfile)).to eq(content << resource[:line] << "\n")
+ end
+ end
+ end
+ end
+
+ context "when removing" do
+ before :each do
+ # TODO: these should be ported over to use the PuppetLabs spec_helper
+ # file fixtures once the following pull request has been merged:
+ # https://github.com/puppetlabs/puppetlabs-stdlib/pull/73/files
+ tmp = Tempfile.new('tmp')
+ @tmpfile = tmp.path
+ tmp.close!
+ @resource = Puppet::Type::File_line.new(
+ {:name => 'foo', :path => @tmpfile, :line => 'foo', :ensure => 'absent' }
+ )
+ @provider = provider_class.new(@resource)
+ end
+ it 'should remove the line if it exists' do
+ File.open(@tmpfile, 'w') do |fh|
+ fh.write("foo1\nfoo\nfoo2")
+ end
+ @provider.destroy
+ expect(File.read(@tmpfile)).to eql("foo1\nfoo2")
+ end
+
+ it 'should remove the line without touching the last new line' do
+ File.open(@tmpfile, 'w') do |fh|
+ fh.write("foo1\nfoo\nfoo2\n")
+ end
+ @provider.destroy
+ expect(File.read(@tmpfile)).to eql("foo1\nfoo2\n")
+ end
+
+ it 'should remove any occurence of the line' do
+ File.open(@tmpfile, 'w') do |fh|
+ fh.write("foo1\nfoo\nfoo2\nfoo\nfoo")
+ end
+ @provider.destroy
+ expect(File.read(@tmpfile)).to eql("foo1\nfoo2\n")
+ end
+ end
+end
diff --git a/puppet/modules/stdlib/spec/unit/puppet/type/anchor_spec.rb b/puppet/modules/stdlib/spec/unit/puppet/type/anchor_spec.rb
new file mode 100755
index 00000000..c738a272
--- /dev/null
+++ b/puppet/modules/stdlib/spec/unit/puppet/type/anchor_spec.rb
@@ -0,0 +1,11 @@
+#!/usr/bin/env ruby
+
+require 'spec_helper'
+
+anchor = Puppet::Type.type(:anchor).new(:name => "ntp::begin")
+
+describe anchor do
+ it "should stringify normally" do
+ expect(anchor.to_s).to eq("Anchor[ntp::begin]")
+ end
+end
diff --git a/puppet/modules/stdlib/spec/unit/puppet/type/file_line_spec.rb b/puppet/modules/stdlib/spec/unit/puppet/type/file_line_spec.rb
new file mode 100755
index 00000000..410d0bfe
--- /dev/null
+++ b/puppet/modules/stdlib/spec/unit/puppet/type/file_line_spec.rb
@@ -0,0 +1,70 @@
+#! /usr/bin/env ruby -S rspec
+require 'spec_helper'
+require 'tempfile'
+describe Puppet::Type.type(:file_line) do
+ let :file_line do
+ Puppet::Type.type(:file_line).new(:name => 'foo', :line => 'line', :path => '/tmp/path')
+ end
+ it 'should accept a line and path' do
+ file_line[:line] = 'my_line'
+ expect(file_line[:line]).to eq('my_line')
+ file_line[:path] = '/my/path'
+ expect(file_line[:path]).to eq('/my/path')
+ end
+ it 'should accept a match regex' do
+ file_line[:match] = '^foo.*$'
+ expect(file_line[:match]).to eq('^foo.*$')
+ end
+ it 'should accept a match regex that does not match the specified line' do
+ expect {
+ Puppet::Type.type(:file_line).new(
+ :name => 'foo',
+ :path => '/my/path',
+ :line => 'foo=bar',
+ :match => '^bar=blah$'
+ )}.not_to raise_error
+ end
+ it 'should accept a match regex that does match the specified line' do
+ expect {
+ Puppet::Type.type(:file_line).new(
+ :name => 'foo',
+ :path => '/my/path',
+ :line => 'foo=bar',
+ :match => '^\s*foo=.*$'
+ )}.not_to raise_error
+ end
+ it 'should accept posix filenames' do
+ file_line[:path] = '/tmp/path'
+ expect(file_line[:path]).to eq('/tmp/path')
+ end
+ it 'should not accept unqualified path' do
+ expect { file_line[:path] = 'file' }.to raise_error(Puppet::Error, /File paths must be fully qualified/)
+ end
+ it 'should require that a line is specified' do
+ expect { Puppet::Type.type(:file_line).new(:name => 'foo', :path => '/tmp/file') }.to raise_error(Puppet::Error, /Both line and path are required attributes/)
+ end
+ it 'should require that a file is specified' do
+ expect { Puppet::Type.type(:file_line).new(:name => 'foo', :line => 'path') }.to raise_error(Puppet::Error, /Both line and path are required attributes/)
+ end
+ it 'should default to ensure => present' do
+ expect(file_line[:ensure]).to eq :present
+ end
+
+ it "should autorequire the file it manages" do
+ catalog = Puppet::Resource::Catalog.new
+ file = Puppet::Type.type(:file).new(:name => "/tmp/path")
+ catalog.add_resource file
+ catalog.add_resource file_line
+
+ relationship = file_line.autorequire.find do |rel|
+ (rel.source.to_s == "File[/tmp/path]") and (rel.target.to_s == file_line.to_s)
+ end
+ expect(relationship).to be_a Puppet::Relationship
+ end
+
+ it "should not autorequire the file it manages if it is not managed" do
+ catalog = Puppet::Resource::Catalog.new
+ catalog.add_resource file_line
+ expect(file_line.autorequire).to be_empty
+ end
+end
diff --git a/puppet/modules/stdlib/tests/file_line.pp b/puppet/modules/stdlib/tests/file_line.pp
new file mode 100644
index 00000000..eea693e1
--- /dev/null
+++ b/puppet/modules/stdlib/tests/file_line.pp
@@ -0,0 +1,9 @@
+# This is a simple smoke test
+# of the file_line resource type.
+file { '/tmp/dansfile':
+ ensure => present
+}->
+file_line { 'dans_line':
+ line => 'dan is awesome',
+ path => '/tmp/dansfile',
+}
diff --git a/puppet/modules/stdlib/tests/has_interface_with.pp b/puppet/modules/stdlib/tests/has_interface_with.pp
new file mode 100644
index 00000000..e1f1353c
--- /dev/null
+++ b/puppet/modules/stdlib/tests/has_interface_with.pp
@@ -0,0 +1,10 @@
+include stdlib
+info('has_interface_with(\'lo\'):', has_interface_with('lo'))
+info('has_interface_with(\'loX\'):', has_interface_with('loX'))
+info('has_interface_with(\'ipaddress\', \'127.0.0.1\'):', has_interface_with('ipaddress', '127.0.0.1'))
+info('has_interface_with(\'ipaddress\', \'127.0.0.100\'):', has_interface_with('ipaddress', '127.0.0.100'))
+info('has_interface_with(\'network\', \'127.0.0.0\'):', has_interface_with('network', '127.0.0.0'))
+info('has_interface_with(\'network\', \'128.0.0.0\'):', has_interface_with('network', '128.0.0.0'))
+info('has_interface_with(\'netmask\', \'255.0.0.0\'):', has_interface_with('netmask', '255.0.0.0'))
+info('has_interface_with(\'netmask\', \'256.0.0.0\'):', has_interface_with('netmask', '256.0.0.0'))
+
diff --git a/puppet/modules/stdlib/tests/has_ip_address.pp b/puppet/modules/stdlib/tests/has_ip_address.pp
new file mode 100644
index 00000000..8429a885
--- /dev/null
+++ b/puppet/modules/stdlib/tests/has_ip_address.pp
@@ -0,0 +1,3 @@
+include stdlib
+info('has_ip_address(\'192.168.1.256\'):', has_ip_address('192.168.1.256'))
+info('has_ip_address(\'127.0.0.1\'):', has_ip_address('127.0.0.1'))
diff --git a/puppet/modules/stdlib/tests/has_ip_network.pp b/puppet/modules/stdlib/tests/has_ip_network.pp
new file mode 100644
index 00000000..a15d8c01
--- /dev/null
+++ b/puppet/modules/stdlib/tests/has_ip_network.pp
@@ -0,0 +1,4 @@
+include stdlib
+info('has_ip_network(\'127.0.0.0\'):', has_ip_network('127.0.0.0'))
+info('has_ip_network(\'128.0.0.0\'):', has_ip_network('128.0.0.0'))
+
diff --git a/puppet/modules/stdlib/tests/init.pp b/puppet/modules/stdlib/tests/init.pp
new file mode 100644
index 00000000..9675d837
--- /dev/null
+++ b/puppet/modules/stdlib/tests/init.pp
@@ -0,0 +1 @@
+include stdlib
diff --git a/puppet/modules/stunnel/LICENSE b/puppet/modules/stunnel/LICENSE
new file mode 100644
index 00000000..94a9ed02
--- /dev/null
+++ b/puppet/modules/stunnel/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/puppet/modules/stunnel/README b/puppet/modules/stunnel/README
new file mode 100644
index 00000000..b6a3124c
--- /dev/null
+++ b/puppet/modules/stunnel/README
@@ -0,0 +1,77 @@
+Overview
+========
+
+This module manages stunnel4. It installs and configures the software, makes
+sure it is running, and enables you to create different stunnels.
+
+
+! Upgrade Notice !
+==================
+
+Previous versions of this module were not using parameterized classes. If you
+were using a previous version, you may need to change how you are using the
+module to accomodate for that. If you were previously setting some stunnel
+variables before including the class, you will now need to pass those variables
+to the class as parameters. If you were just simply doing 'include stunnel',
+then you will not need to change anything.
+
+Classes
+=======
+
+stunnel
+-------
+
+This is the main class which brings you stunnel support. You will need to
+instantiate it by doing the following:
+
+class { 'stunnel': }
+
+Class parameters:
+
+* ensure_version - If this parameter is passed, you can force a particular
+ version of stunnel to be installed, if it is available with your packaging
+ system, for example:
+
+ class { 'stunnel': ensure_version = '3:4.53-1' }
+
+ If you do not pass this parameter, it will default to just be 'present'.
+
+* startboot (Debian) - This parameter controls if stunnel should be started at
+ boot or not, if you do not pass this paramter, by default it will be started
+
+* default_extra (Debian) - This parameter lets you add arbitrary extra text to
+ the bottom of /etc/default/stunnel4, this can be useful to set ulimit for
+ example
+
+
+Defines
+=======
+
+stunnel::service
+----------------
+
+This define lets you setup any number of stunnels, it allows you to pass every
+stunnel configuration variable (see manifests/server.pp) which will be used to
+create the /etc/stunnel/${name}.conf file, and then notify the stunnel service
+so it will restart.
+
+If you pass $manage_nagios to this define, it will create a nagios::service
+entry for stunnel_${name} which will watch for the appropriate number processes
+with that configuration name
+
+Note that if you need to use some specific logic to decide whether or not to
+create a nagios service check, you should set $manage_nagios to false, and
+use stunnel::service::nagios from within your own manifests.
+
+stunnel::service::nagios
+------------------------
+
+This define creates a nagios service check for a specific tunnel. The resource
+name should be the name of the tunnel's configuration file without the '.conf'
+suffix. For example:
+
+ stunnel::service::nagios { 'carpal': }
+
+The above example would verify that the tunnel defined in
+`/etc/stunnel/carpal.conf'.
+
diff --git a/puppet/modules/stunnel/files/CentOS/stunnel.init b/puppet/modules/stunnel/files/CentOS/stunnel.init
new file mode 100644
index 00000000..d5c60fd8
--- /dev/null
+++ b/puppet/modules/stunnel/files/CentOS/stunnel.init
@@ -0,0 +1,143 @@
+#!/bin/bash
+#
+# Script to run stunnel in daemon mode at boot time.
+#
+# Check http://www.gaztronics.net/ for the
+# most up-to-date version of this script.
+#
+# This script is realeased under the terms of the GPL.
+# You can source a copy at:
+# http://www.fsf.org/copyleft/copyleft.html
+#
+# Please feel free to modify the script to suite your own needs.
+# I always welcome email feedback with suggestions for improvements.
+# Please do not email for general support. I do not have time to answer
+# personal help requests.
+
+# Author: Gary Myers MIIE MBCS
+# email: http://www.gaztronics.net/webform/
+# Revision 1.0 - 4th March 2005
+
+#====================================================================
+# Run level information:
+#
+# chkconfig: 2345 99 99
+# description: Secure Tunnel
+# processname: stunnel
+#
+# Run "/sbin/chkconfig --add stunnel" to add the Run levels.
+# This will setup the symlinks and set the process to run at boot.
+#====================================================================
+
+#====================================================================
+# Paths and variables and system checks.
+
+# Source function library (It's a Red Hat thing!)
+. /etc/rc.d/init.d/functions
+
+# Check that networking is up.
+#
+[ ${NETWORKING} ="yes" ] || exit 0
+
+# Path to the executable.
+#
+SEXE=/usr/sbin/stunnel
+
+# Path to the configuration file.
+#
+CONF=/etc/stunnel/stunnel.conf
+
+# Check the configuration file exists.
+#
+if [ ! -f $CONF ] ; then
+ echo "The configuration file cannot be found!"
+exit 0
+fi
+
+CHROOT=`grep '^chroot' /etc/stunnel/stunnel.conf | head -n 1 | sed 's/ //g' | awk -F= '{ print $2 }'`
+PIDFILE=`grep '^pid' /etc/stunnel/stunnel.conf | head -n 1 | sed 's/ //g' | awk -F= '{ print $2 }'`
+if [ -n "$CHROOT" ]; then
+ PIDFILE=$CHROOT/$PIDFILE
+fi
+
+# Path to the lock file.
+#
+LOCK_FILE=/var/lock/subsys/stunnel
+
+#====================================================================
+
+#====================================================================
+# Run controls:
+
+prog=$"stunnel"
+
+RETVAL=0
+
+# Start stunnel as daemon.
+#
+start() {
+ if [ -f $LOCK_FILE ]; then
+ echo "stunnel is already running!"
+ exit 0
+ else
+ echo -n $"Starting $prog: "
+ $SEXE $CONF
+ fi
+
+ RETVAL=$?
+ [ $RETVAL -eq 0 ] && success
+ echo
+ [ $RETVAL -eq 0 ] && touch $LOCK_FILE
+ return $RETVAL
+}
+
+
+# Stop stunnel.
+#
+stop() {
+ if [ ! -f $LOCK_FILE ]; then
+ echo "stunnel is not running!"
+ exit 0
+
+ else
+
+ echo -n $"Shutting down $prog: "
+ killproc -p $PIDFILE stunnel
+ RETVAL=$?
+ [ $RETVAL -eq 0 ]
+ rm -f $LOCK_FILE
+ echo
+ return $RETVAL
+
+ fi
+}
+
+# See how we were called.
+case "$1" in
+ start)
+ start
+ ;;
+ stop)
+ stop
+ ;;
+ restart)
+ stop
+ start
+ ;;
+ condrestart)
+ if [ -f $LOCK_FILE ]; then
+ stop
+ start
+ RETVAL=$?
+ fi
+ ;;
+ status)
+ status -p $PIDFILE stunnel
+ RETVAL=$?
+ ;;
+ *)
+ echo $"Usage: $0 {start|stop|restart|condrestart|status}"
+ RETVAL=1
+esac
+
+exit $RETVAL
diff --git a/puppet/modules/stunnel/manifests/base.pp b/puppet/modules/stunnel/manifests/base.pp
new file mode 100644
index 00000000..9fed2de7
--- /dev/null
+++ b/puppet/modules/stunnel/manifests/base.pp
@@ -0,0 +1,13 @@
+class stunnel::base {
+
+ file { '/etc/stunnel':
+ ensure => directory;
+ }
+
+ service { 'stunnel':
+ ensure => running,
+ name => 'stunnel',
+ enable => true,
+ hasstatus => false;
+ }
+}
diff --git a/puppet/modules/stunnel/manifests/centos.pp b/puppet/modules/stunnel/manifests/centos.pp
new file mode 100644
index 00000000..3b0a6e2a
--- /dev/null
+++ b/puppet/modules/stunnel/manifests/centos.pp
@@ -0,0 +1,35 @@
+class stunnel::centos inherits stunnel::linux {
+
+ file { '/etc/init.d/stunnel':
+ source => "puppet:///modules/stunnel/${::operatingsystem}/stunnel.init",
+ require => Package['stunnel'],
+ before => Service['stunnel'],
+ owner => root,
+ group => 0,
+ mode => '0755';
+ }
+
+ user::managed { 'stunnel':
+ homedir => '/var/run/stunnel',
+ shell => '/sbin/nologin',
+ uid => 105,
+ gid => 105;
+ }
+
+ Service['stunnel']{
+ hasstatus => true,
+ require => [ User['stunnel'], File['/etc/init.d/stunnel'] ]
+ }
+
+ file { '/etc/stunnel/stunnel.conf':
+ source => [ "puppet:///modules/site-stunnel/${::fqdn}/stunnel.conf",
+ "puppet:///modules/site-stunnel/${stunnel::cluster}/stunnel.conf",
+ 'puppet:///modules/site-stunnel/stunnel.conf',
+ "puppet:///modules/stunnel/${::operatingsystem}/stunnel.conf" ],
+ require => Package['stunnel'],
+ notify => Service['stunnel'],
+ owner => root,
+ group => 0,
+ mode => '0600';
+ }
+}
diff --git a/puppet/modules/stunnel/manifests/debian.pp b/puppet/modules/stunnel/manifests/debian.pp
new file mode 100644
index 00000000..bde1e219
--- /dev/null
+++ b/puppet/modules/stunnel/manifests/debian.pp
@@ -0,0 +1,21 @@
+class stunnel::debian inherits stunnel::linux {
+
+ Package['stunnel'] {
+ name => 'stunnel4',
+ }
+
+ Service['stunnel'] {
+ name => 'stunnel4',
+ pattern => '/usr/bin/stunnel4',
+ }
+
+ file { '/etc/default/stunnel4':
+ content => template('stunnel/Debian/default'),
+ require => Package['stunnel4'],
+ notify => Service['stunnel4'],
+ owner => root,
+ group => 0,
+ mode => '0644';
+ }
+}
+
diff --git a/puppet/modules/stunnel/manifests/init.pp b/puppet/modules/stunnel/manifests/init.pp
new file mode 100644
index 00000000..c5ad70b1
--- /dev/null
+++ b/puppet/modules/stunnel/manifests/init.pp
@@ -0,0 +1,66 @@
+#
+# stunnel puppet module
+#
+# Copyright 2009, Riseup Networks <micah@riseup.net>
+#
+#
+# This program is free software; you can redistribute
+# it and/or modify it under the terms of the GNU
+# General Public License version 3 as published by
+# the Free Software Foundation.
+#
+# 1. include stunnel: this will automatically include stunnel::debian,
+# which automatically includes stunnel::linux, which automatically
+# includes stunnel::base
+# 2. stunnel::client allows you to configure different /etc/stunnel/*.conf files
+# to provide various stunnel configurations
+
+# TODO: warn on cert/key issues, fail on false accept?
+
+class stunnel (
+ $ensure_version = 'present',
+ $startboot = '1',
+ $default_extra = '',
+ $cluster = '' )
+{
+
+ case $::operatingsystem {
+ debian: { class { 'stunnel::debian': } }
+ centos: { class { 'stunnel::centos': } }
+ default: { class { 'stunnel::default': } }
+ }
+
+ $stunnel_staging = "${::puppet_vardir}/stunnel4"
+ $stunnel_compdir = "${stunnel_staging}/configs"
+
+ file {
+ [ $stunnel_staging, "${stunnel_staging}/bin" ]:
+ ensure => directory,
+ owner => 0,
+ group => 0,
+ mode => '0750';
+
+ "${stunnel_staging}/configs":
+ ensure => directory,
+ owner => 0,
+ group => 0,
+ mode => '0750',
+ recurse => true,
+ purge => true,
+ force => true,
+ source => undef;
+
+ "${stunnel_staging}/bin/refresh_stunnel.sh":
+ owner => 0,
+ group => 0,
+ mode => '0755',
+ content => template('stunnel/refresh_stunnel.sh.erb');
+ }
+
+ exec { 'refresh_stunnel':
+ refreshonly => true,
+ require => [ Service['stunnel'], Package['stunnel'], File[$stunnel_compdir] ],
+ subscribe => File[$stunnel_compdir],
+ command => "${stunnel_staging}/bin/refresh_stunnel.sh"
+ }
+}
diff --git a/puppet/modules/stunnel/manifests/linux.pp b/puppet/modules/stunnel/manifests/linux.pp
new file mode 100644
index 00000000..a4a926e4
--- /dev/null
+++ b/puppet/modules/stunnel/manifests/linux.pp
@@ -0,0 +1,6 @@
+class stunnel::linux inherits stunnel::base {
+
+ package { 'stunnel':
+ ensure => $stunnel::ensure_version
+ }
+}
diff --git a/puppet/modules/stunnel/manifests/service.pp b/puppet/modules/stunnel/manifests/service.pp
new file mode 100644
index 00000000..8a98d8ff
--- /dev/null
+++ b/puppet/modules/stunnel/manifests/service.pp
@@ -0,0 +1,79 @@
+define stunnel::service (
+ $ensure = present,
+ $accept = false,
+ $capath = false,
+ $cafile = false,
+ $cert = false,
+ $chroot = false,
+ $ciphers = false,
+ $client = false,
+ $compress = false,
+ $connect = false,
+ $crlpath = false,
+ $crlfile = false,
+ $debuglevel = false,
+ $delay = false,
+ $egd = false,
+ $engine = false,
+ $engineCtrl = false,
+ $enginenum = false,
+ $exec = false,
+ $execargs = false,
+ $failover = false,
+ $ident = false,
+ $key = false,
+ $local = false,
+ $oscp = false,
+ $ocspflag = false,
+ $options = false,
+ $output = false,
+ $pid = false,
+ $protocol = false,
+ $protocolauthentication = false,
+ $protocolhost = false,
+ $protocolpassword = false,
+ $protocolusername = false,
+ $pty = false,
+ $retry = false,
+ $rndbytes = false,
+ $rndfile = false,
+ $rndoverwrite = false,
+ $service = false,
+ $session = false,
+ $setuid = 'stunnel4',
+ $setgid = 'stunnel4',
+ $socket = [ 'l:TCP_NODELAY=1', 'r:TCP_NODELAY=1'],
+ $sslversion = 'SSLv3',
+ $stack = false,
+ $syslog = false,
+ $timeoutbusy = false,
+ $timeoutclose = false,
+ $timeoutconnect = false,
+ $timeoutidle = false,
+ $transparent = false,
+ $manage_nagios = false,
+ $verify = false
+) {
+
+ include stunnel
+
+ $real_client = $client ? { default => 'yes' }
+ $real_pid = $pid ? { false => "/${name}.pid", default => $pid }
+
+ $stunnel_compdir = "${::puppet_vardir}/stunnel4/configs"
+
+ file {
+ "${stunnel_compdir}/${name}.conf":
+ ensure => $ensure,
+ content => template('stunnel/service.conf.erb'),
+ require => Package['stunnel'],
+ notify => Exec['refresh_stunnel'],
+ owner => 'root',
+ group => 0,
+ mode => '0600';
+ }
+
+ if $manage_nagios {
+ stunnel::service::nagios { $name: }
+ }
+}
diff --git a/puppet/modules/stunnel/manifests/service/nagios.pp b/puppet/modules/stunnel/manifests/service/nagios.pp
new file mode 100644
index 00000000..578b417e
--- /dev/null
+++ b/puppet/modules/stunnel/manifests/service/nagios.pp
@@ -0,0 +1,12 @@
+# Put a Nagios service check in place for a specific tunnel.
+#
+# The resource name will be used to point to the corresponding stunnel
+# configuration file.
+#
+define stunnel::service::nagios () {
+
+ nagios::service { "stunnel_${name}":
+ check_command => "nagios-stat-proc!/usr/bin/stunnel4 /etc/stunnel/${name}.conf!6!5!proc";
+ }
+
+}
diff --git a/puppet/modules/stunnel/templates/Debian/default b/puppet/modules/stunnel/templates/Debian/default
new file mode 100644
index 00000000..9e2f4d37
--- /dev/null
+++ b/puppet/modules/stunnel/templates/Debian/default
@@ -0,0 +1,13 @@
+# /etc/default/stunnel
+# Julien LEMOINE <speedblue@debian.org>
+# September 2003
+
+# Change to one to enable stunnel automatic startup
+ENABLED=<%= scope.lookupvar('stunnel::startboot') %>
+FILES="/etc/stunnel/*.conf"
+OPTIONS=""
+
+# Change to one to enable ppp restart scripts
+PPP_RESTART=0
+
+<%= scope.lookupvar('stunnel::default_extra') %>
diff --git a/puppet/modules/stunnel/templates/refresh_stunnel.sh.erb b/puppet/modules/stunnel/templates/refresh_stunnel.sh.erb
new file mode 100644
index 00000000..1af0cff7
--- /dev/null
+++ b/puppet/modules/stunnel/templates/refresh_stunnel.sh.erb
@@ -0,0 +1,22 @@
+#!/bin/sh -x
+
+for difference in `diff -q /etc/stunnel <%= @stunnel_staging %>/configs | grep differ | awk '{print $2}'`
+do
+ old_config=`basename $difference`
+ /etc/init.d/stunnel4 stop $(basename $old_config .conf)
+ rm $difference
+done
+
+for only in `diff -q /etc/stunnel <%= @stunnel_staging %>/configs | grep 'Only in /etc/stunnel:' | awk '{print $4}'`
+do
+ old_config=`basename $only`
+ /etc/init.d/stunnel4 stop $(basename $only .conf)
+ rm /etc/stunnel/${only}
+done
+
+cp <%= @stunnel_staging %>/configs/*.conf /etc/stunnel
+
+/etc/init.d/stunnel4 start
+
+
+
diff --git a/puppet/modules/stunnel/templates/service.conf.erb b/puppet/modules/stunnel/templates/service.conf.erb
new file mode 100644
index 00000000..47f1c9d2
--- /dev/null
+++ b/puppet/modules/stunnel/templates/service.conf.erb
@@ -0,0 +1,47 @@
+; templated stunnel configuration file to be used by puppet stunnel module
+; NOTE: any changes you make to this file will be overwritten the next time
+; puppet runs, please make configuration changes to this service in puppet
+
+; Global configuration options
+<%= 'debug = ' + @debuglevel %>
+<%= 'pid = ' + @real_pid %>
+<%- %w{chroot setuid setgid service compression}.each do |v|
+ if has_variable?(v) and instance_variable_get("@#{v}").to_s != "false" -%>
+<%= v + " = " + instance_variable_get("@#{v}").to_s %>
+<%-
+ end
+end -%>
+
+; Some performance tunings
+<% if @socket.is_a? String -%>
+<%= 'socket = ' + @socket %>
+<% elsif @socket.is_a? Array -%>
+<%= @socket.map { |i| "socket = #{i}" }. join("\n") %>
+<% end -%>
+
+<%- %w{output syslog}.each do |v|
+ if has_variable?(v) and instance_variable_get("@#{v}").to_s != "false" -%>
+<%= v + " = " + instance_variable_get("@#{v}").to_s %>
+<%-
+ end
+end -%>
+
+<%- %w{egd engine enginectrl rndbytes rndfile rndoverwrite}.each do |v|
+ if has_variable?(v) and instance_variable_get("@#{v}").to_s != "false" -%>
+<%= v + " = " + instance_variable_get("@#{v}").to_s %>
+<%-
+ end
+end -%>
+
+; Service-level configuration
+<%= '[' + @name + ']' %>
+<%- %w{accept connect capath cafile cert ciphers crlpath crlfile delay enginenum exec
+ execargs failover ident key local oscp ocspflag options protocol protocolauthentication
+ protocolhost protocolpassword protocolusername pty retry session sslversion stack
+ timeoutbusy timeoutclose timeoutconnect timeoutidle transparent verify}.each do |v|
+ if has_variable?(v) and instance_variable_get("@#{v}").to_s != "false" -%>
+<%= v + ' = ' + instance_variable_get("@#{v}").to_s %>
+<%-
+ end
+end -%>
+client = <%= @client ? 'yes' : 'no' %>
diff --git a/puppet/modules/sysctl/README b/puppet/modules/sysctl/README
new file mode 100644
index 00000000..a3980f70
--- /dev/null
+++ b/puppet/modules/sysctl/README
@@ -0,0 +1,20 @@
+sysctl module
+-------------
+
+This puppet module handles the setting of variables in sysctl.conf, its
+a simple module that utilizes the puppet augeas built-in type and the
+sysctl binary. You must have the augeas ruby libraries installed to
+use this type.
+
+You can set a value and a comment for that value using this module,
+some examples:
+
+sysctl::config { "vm.mmap_min_addr":
+ value => 32768,
+ comment => "Never mmap into the first 32k of memory",
+}
+
+sysctl::config { "fs.file-max":
+ value => 65536,
+ comment => "Maximum number of filehandles",
+}
diff --git a/puppet/modules/sysctl/manifests/config.pp b/puppet/modules/sysctl/manifests/config.pp
new file mode 100644
index 00000000..79ddd295
--- /dev/null
+++ b/puppet/modules/sysctl/manifests/config.pp
@@ -0,0 +1,18 @@
+define sysctl::config ($value, $comment) {
+
+ include sysctl
+
+ augeas { "sysctl_${name}":
+ context => '/files/etc/sysctl.conf',
+ changes => [ "set ${name} ${value}", "insert #comment before ${name}",
+ "set #comment[last()] '${comment}'" ],
+ onlyif => "get ${name} != ${value}",
+ notify => Exec["sysctl_${name}"],
+ }
+
+ exec { "sysctl_${name}":
+ command => '/sbin/sysctl -p',
+ subscribe => File['/etc/sysctl.conf'],
+ refreshonly => true,
+ }
+}
diff --git a/puppet/modules/sysctl/manifests/init.pp b/puppet/modules/sysctl/manifests/init.pp
new file mode 100644
index 00000000..43d9299e
--- /dev/null
+++ b/puppet/modules/sysctl/manifests/init.pp
@@ -0,0 +1,10 @@
+class sysctl {
+
+ file { '/etc/sysctl.conf':
+ ensure => present,
+ mode => '0644',
+ owner => root,
+ group => root
+ }
+}
+
diff --git a/puppet/modules/systemd/.gitignore b/puppet/modules/systemd/.gitignore
new file mode 100644
index 00000000..65839fa0
--- /dev/null
+++ b/puppet/modules/systemd/.gitignore
@@ -0,0 +1,10 @@
+pkg/
+Gemfile.lock
+vendor/
+spec/fixtures/
+.vagrant/
+.bundle/
+coverage/
+log/
+.*.swp
+*~
diff --git a/puppet/modules/systemd/.puppet-lint.rc b/puppet/modules/systemd/.puppet-lint.rc
new file mode 100644
index 00000000..d8f5c59e
--- /dev/null
+++ b/puppet/modules/systemd/.puppet-lint.rc
@@ -0,0 +1,5 @@
+--fail-on-warnings
+--relative
+--no-80chars
+--no-documentation
+--no-class_inherits_from_params_class-check
diff --git a/.sync.yml b/puppet/modules/systemd/.sync.yml
index 5fffcb05..5fffcb05 100644
--- a/.sync.yml
+++ b/puppet/modules/systemd/.sync.yml
diff --git a/.travis.yml b/puppet/modules/systemd/.travis.yml
index 467045c5..467045c5 100644
--- a/.travis.yml
+++ b/puppet/modules/systemd/.travis.yml
diff --git a/CHANGELOG.md b/puppet/modules/systemd/CHANGELOG.md
index 11e84399..11e84399 100644
--- a/CHANGELOG.md
+++ b/puppet/modules/systemd/CHANGELOG.md
diff --git a/puppet/modules/systemd/Gemfile b/puppet/modules/systemd/Gemfile
new file mode 100644
index 00000000..0cb59337
--- /dev/null
+++ b/puppet/modules/systemd/Gemfile
@@ -0,0 +1,47 @@
+source ENV['GEM_SOURCE'] || "https://rubygems.org"
+
+group :development, :unit_tests do
+ gem 'rake', :require => false
+ gem 'rspec', '< 3.2', :require => false if RUBY_VERSION =~ /^1.8/
+ gem 'rspec-puppet', :require => false
+ gem 'puppetlabs_spec_helper', :require => false
+ gem 'metadata-json-lint', :require => false
+ gem 'puppet-lint', :require => false
+ gem 'puppet-lint-unquoted_string-check', :require => false
+ gem 'puppet-lint-empty_string-check', :require => false
+ gem 'puppet-lint-spaceship_operator_without_tag-check', :require => false
+ gem 'puppet-lint-variable_contains_upcase', :require => false
+ gem 'puppet-lint-absolute_classname-check', :require => false
+ gem 'puppet-lint-undef_in_function-check', :require => false
+ gem 'puppet-lint-leading_zero-check', :require => false
+ gem 'puppet-lint-trailing_comma-check', :require => false
+ gem 'puppet-lint-file_ensure-check', :require => false
+ gem 'puppet-lint-version_comparison-check', :require => false
+ gem 'puppet-lint-fileserver-check', :require => false
+ gem 'puppet-lint-file_source_rights-check', :require => false
+ gem 'puppet-lint-alias-check', :require => false
+ gem 'rspec-puppet-facts', :require => false
+ gem 'github_changelog_generator', :require => false, :git => 'https://github.com/raphink/github-changelog-generator.git', :branch => 'dev/all_patches' if RUBY_VERSION !~ /^1.8/
+ gem 'puppet-blacksmith', :require => false if RUBY_VERSION !~ /^1.8/
+end
+
+group :system_tests do
+ gem 'beaker', :require => false
+ gem 'beaker-rspec', :require => false
+ gem 'beaker_spec_helper', :require => false
+ gem 'serverspec', :require => false
+end
+
+if facterversion = ENV['FACTER_GEM_VERSION']
+ gem 'facter', facterversion, :require => false
+else
+ gem 'facter', :require => false
+end
+
+if puppetversion = ENV['PUPPET_GEM_VERSION']
+ gem 'puppet', puppetversion, :require => false
+else
+ gem 'puppet', :require => false
+end
+
+# vim:ft=ruby
diff --git a/HISTORY.md b/puppet/modules/systemd/HISTORY.md
index c7bf2b4e..c7bf2b4e 100644
--- a/HISTORY.md
+++ b/puppet/modules/systemd/HISTORY.md
diff --git a/puppet/modules/systemd/README.md b/puppet/modules/systemd/README.md
new file mode 100644
index 00000000..f70bcb0c
--- /dev/null
+++ b/puppet/modules/systemd/README.md
@@ -0,0 +1,38 @@
+# Systemd
+
+[![Puppet Forge](http://img.shields.io/puppetforge/v/camptocamp/systemd.svg)](https://forge.puppetlabs.com/camptocamp/systemd)
+[![Build Status](https://travis-ci.org/camptocamp/puppet-systemd.png?branch=master)](https://travis-ci.org/camptocamp/puppet-systemd)
+
+## Overview
+
+This module declares exec resources that you can use when you change systemd units or configuration files.
+
+## Examples
+
+### systemctl --daemon-reload
+
+```puppet
+include ::systemd
+file { '/usr/lib/systemd/system/foo.service':
+ ensure => file,
+ owner => 'root',
+ group => 'root',
+ mode => '0644',
+ source => "puppet:///modules/${module_name}/foo.service",
+} ~>
+Exec['systemctl-daemon-reload']
+```
+
+### systemd-tmpfiles --create
+
+```puppet
+include ::systemd
+file { '/etc/tmpfiles.d/foo.conf':
+ ensure => file,
+ owner => 'root',
+ group => 'root',
+ mode => '0644',
+ source => "puppet:///modules/${module_name}/foo.conf",
+} ~>
+Exec['systemd-tmpfiles-create']
+```
diff --git a/puppet/modules/systemd/Rakefile b/puppet/modules/systemd/Rakefile
new file mode 100644
index 00000000..adcac180
--- /dev/null
+++ b/puppet/modules/systemd/Rakefile
@@ -0,0 +1,23 @@
+require 'puppetlabs_spec_helper/rake_tasks'
+require 'puppet-lint/tasks/puppet-lint'
+
+Rake::Task[:lint].clear
+PuppetLint::RakeTask.new :lint do |config|
+ config.ignore_paths = ["spec/**/*.pp", "pkg/**/*.pp", "vendor/**/*.pp"]
+ config.disable_checks = ['80chars']
+ config.fail_on_warnings = true
+end
+
+PuppetSyntax.exclude_paths = ["spec/fixtures/**/*.pp", "vendor/**/*"]
+
+# Publishing tasks
+unless RUBY_VERSION =~ /^1\.8/
+ require 'puppet_blacksmith'
+ require 'puppet_blacksmith/rake_tasks'
+ require 'github_changelog_generator/task'
+ GitHubChangelogGenerator::RakeTask.new :changelog do |config|
+ m = Blacksmith::Modulefile.new
+ config.future_release = m.version
+ config.release_url = "https://forge.puppetlabs.com/#{m.author}/#{m.name}/%s"
+ end
+end
diff --git a/manifests/init.pp b/puppet/modules/systemd/manifests/init.pp
index 5e6ad792..5e6ad792 100644
--- a/manifests/init.pp
+++ b/puppet/modules/systemd/manifests/init.pp
diff --git a/metadata.json b/puppet/modules/systemd/metadata.json
index abdd481e..abdd481e 100644
--- a/metadata.json
+++ b/puppet/modules/systemd/metadata.json
diff --git a/spec/acceptance/nodesets/centos-5-x86_64-docker.yml b/puppet/modules/systemd/spec/acceptance/nodesets/centos-5-x86_64-docker.yml
index 679afb04..679afb04 100644
--- a/spec/acceptance/nodesets/centos-5-x86_64-docker.yml
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/centos-5-x86_64-docker.yml
diff --git a/spec/acceptance/nodesets/centos-6-x86_64-docker.yml b/puppet/modules/systemd/spec/acceptance/nodesets/centos-6-x86_64-docker.yml
index 9cab03d0..9cab03d0 100644
--- a/spec/acceptance/nodesets/centos-6-x86_64-docker.yml
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/centos-6-x86_64-docker.yml
diff --git a/spec/acceptance/nodesets/centos-6-x86_64-openstack.yml b/puppet/modules/systemd/spec/acceptance/nodesets/centos-6-x86_64-openstack.yml
index e325b9e9..e325b9e9 100644
--- a/spec/acceptance/nodesets/centos-6-x86_64-openstack.yml
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/centos-6-x86_64-openstack.yml
diff --git a/spec/acceptance/nodesets/centos-6-x86_64-vagrant.yml b/puppet/modules/systemd/spec/acceptance/nodesets/centos-6-x86_64-vagrant.yml
index f06036ec..f06036ec 100644
--- a/spec/acceptance/nodesets/centos-6-x86_64-vagrant.yml
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/centos-6-x86_64-vagrant.yml
diff --git a/spec/acceptance/nodesets/centos-7-x86_64-docker.yml b/puppet/modules/systemd/spec/acceptance/nodesets/centos-7-x86_64-docker.yml
index 0bc97271..0bc97271 100644
--- a/spec/acceptance/nodesets/centos-7-x86_64-docker.yml
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/centos-7-x86_64-docker.yml
diff --git a/spec/acceptance/nodesets/centos-7-x86_64-openstack.yml b/puppet/modules/systemd/spec/acceptance/nodesets/centos-7-x86_64-openstack.yml
index 9003c867..9003c867 100644
--- a/spec/acceptance/nodesets/centos-7-x86_64-openstack.yml
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/centos-7-x86_64-openstack.yml
diff --git a/spec/acceptance/nodesets/centos-7-x86_64-vagrant.yml b/puppet/modules/systemd/spec/acceptance/nodesets/centos-7-x86_64-vagrant.yml
index 95402e54..95402e54 100644
--- a/spec/acceptance/nodesets/centos-7-x86_64-vagrant.yml
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/centos-7-x86_64-vagrant.yml
diff --git a/spec/acceptance/nodesets/debian-6-x86_64-docker.yml b/puppet/modules/systemd/spec/acceptance/nodesets/debian-6-x86_64-docker.yml
index 359dae7d..359dae7d 100644
--- a/spec/acceptance/nodesets/debian-6-x86_64-docker.yml
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/debian-6-x86_64-docker.yml
diff --git a/spec/acceptance/nodesets/debian-6-x86_64-openstack.yml b/puppet/modules/systemd/spec/acceptance/nodesets/debian-6-x86_64-openstack.yml
index c6c192fe..c6c192fe 100644
--- a/spec/acceptance/nodesets/debian-6-x86_64-openstack.yml
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/debian-6-x86_64-openstack.yml
diff --git a/spec/acceptance/nodesets/debian-6-x86_64-vagrant.yml b/puppet/modules/systemd/spec/acceptance/nodesets/debian-6-x86_64-vagrant.yml
index 03db0fa7..03db0fa7 100644
--- a/spec/acceptance/nodesets/debian-6-x86_64-vagrant.yml
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/debian-6-x86_64-vagrant.yml
diff --git a/spec/acceptance/nodesets/debian-7-x86_64-docker.yml b/puppet/modules/systemd/spec/acceptance/nodesets/debian-7-x86_64-docker.yml
index fc11f574..fc11f574 100644
--- a/spec/acceptance/nodesets/debian-7-x86_64-docker.yml
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/debian-7-x86_64-docker.yml
diff --git a/spec/acceptance/nodesets/debian-7-x86_64-openstack.yml b/puppet/modules/systemd/spec/acceptance/nodesets/debian-7-x86_64-openstack.yml
index 017b4c74..017b4c74 100644
--- a/spec/acceptance/nodesets/debian-7-x86_64-openstack.yml
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/debian-7-x86_64-openstack.yml
diff --git a/spec/acceptance/nodesets/debian-7-x86_64-vagrant.yml b/puppet/modules/systemd/spec/acceptance/nodesets/debian-7-x86_64-vagrant.yml
index 8ed1264d..8ed1264d 100644
--- a/spec/acceptance/nodesets/debian-7-x86_64-vagrant.yml
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/debian-7-x86_64-vagrant.yml
diff --git a/spec/acceptance/nodesets/debian-8-x86_64-docker.yml b/puppet/modules/systemd/spec/acceptance/nodesets/debian-8-x86_64-docker.yml
index 86a55e15..86a55e15 100644
--- a/spec/acceptance/nodesets/debian-8-x86_64-docker.yml
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/debian-8-x86_64-docker.yml
diff --git a/spec/acceptance/nodesets/debian-8-x86_64-openstack.yml b/puppet/modules/systemd/spec/acceptance/nodesets/debian-8-x86_64-openstack.yml
index 003b6f4b..003b6f4b 100644
--- a/spec/acceptance/nodesets/debian-8-x86_64-openstack.yml
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/debian-8-x86_64-openstack.yml
diff --git a/spec/acceptance/nodesets/debian-8-x86_64-vagrant.yml b/puppet/modules/systemd/spec/acceptance/nodesets/debian-8-x86_64-vagrant.yml
index 5cc7f0c5..5cc7f0c5 100644
--- a/spec/acceptance/nodesets/debian-8-x86_64-vagrant.yml
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/debian-8-x86_64-vagrant.yml
diff --git a/spec/acceptance/nodesets/ubuntu-10.04-x86_64-docker.yml b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-10.04-x86_64-docker.yml
index 933dee60..933dee60 100644
--- a/spec/acceptance/nodesets/ubuntu-10.04-x86_64-docker.yml
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-10.04-x86_64-docker.yml
diff --git a/spec/acceptance/nodesets/ubuntu-12.04-x86_64-docker.yml b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-12.04-x86_64-docker.yml
index f0ec72b8..f0ec72b8 100644
--- a/spec/acceptance/nodesets/ubuntu-12.04-x86_64-docker.yml
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-12.04-x86_64-docker.yml
diff --git a/spec/acceptance/nodesets/ubuntu-12.04-x86_64-openstack.yml b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-12.04-x86_64-openstack.yml
index f81b04b7..f81b04b7 100644
--- a/spec/acceptance/nodesets/ubuntu-12.04-x86_64-openstack.yml
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-12.04-x86_64-openstack.yml
diff --git a/spec/acceptance/nodesets/ubuntu-14.04-x86_64-docker.yml b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.04-x86_64-docker.yml
index 6fb9281e..6fb9281e 100644
--- a/spec/acceptance/nodesets/ubuntu-14.04-x86_64-docker.yml
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.04-x86_64-docker.yml
diff --git a/spec/acceptance/nodesets/ubuntu-14.04-x86_64-openstack.yml b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.04-x86_64-openstack.yml
index 2eeb912d..2eeb912d 100644
--- a/spec/acceptance/nodesets/ubuntu-14.04-x86_64-openstack.yml
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.04-x86_64-openstack.yml
diff --git a/spec/acceptance/nodesets/ubuntu-14.04-x86_64-vagrant.yml b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.04-x86_64-vagrant.yml
index 3b376953..3b376953 100644
--- a/spec/acceptance/nodesets/ubuntu-14.04-x86_64-vagrant.yml
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.04-x86_64-vagrant.yml
diff --git a/spec/acceptance/nodesets/ubuntu-14.10-x86_64-docker.yml b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.10-x86_64-docker.yml
index 2be425c5..2be425c5 100644
--- a/spec/acceptance/nodesets/ubuntu-14.10-x86_64-docker.yml
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.10-x86_64-docker.yml
diff --git a/spec/acceptance/nodesets/ubuntu-14.10-x86_64-openstack.yml b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.10-x86_64-openstack.yml
index 58a2acd2..58a2acd2 100644
--- a/spec/acceptance/nodesets/ubuntu-14.10-x86_64-openstack.yml
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.10-x86_64-openstack.yml
diff --git a/spec/acceptance/nodesets/ubuntu-15.04-x86_64-docker.yml b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-15.04-x86_64-docker.yml
index caed722c..caed722c 100644
--- a/spec/acceptance/nodesets/ubuntu-15.04-x86_64-docker.yml
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-15.04-x86_64-docker.yml
diff --git a/spec/acceptance/nodesets/ubuntu-15.04-x86_64-openstack.yml b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-15.04-x86_64-openstack.yml
index 22ef76c4..22ef76c4 100644
--- a/spec/acceptance/nodesets/ubuntu-15.04-x86_64-openstack.yml
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-15.04-x86_64-openstack.yml
diff --git a/puppet/modules/systemd/spec/spec.opts b/puppet/modules/systemd/spec/spec.opts
new file mode 100644
index 00000000..91cd6427
--- /dev/null
+++ b/puppet/modules/systemd/spec/spec.opts
@@ -0,0 +1,6 @@
+--format
+s
+--colour
+--loadby
+mtime
+--backtrace
diff --git a/spec/spec_helper.rb b/puppet/modules/systemd/spec/spec_helper.rb
index 94d30d5c..94d30d5c 100644
--- a/spec/spec_helper.rb
+++ b/puppet/modules/systemd/spec/spec_helper.rb
diff --git a/puppet/modules/templatewlv/Modulefile b/puppet/modules/templatewlv/Modulefile
new file mode 100644
index 00000000..8007a070
--- /dev/null
+++ b/puppet/modules/templatewlv/Modulefile
@@ -0,0 +1,11 @@
+name 'duritong-templatewlv'
+version '0.0.1'
+source 'https://github.com/duritong/puppet-templatewlv.git'
+author 'duritong'
+license 'Apache License, Version 2.0'
+summary 'Template With Local Variables'
+description 'Pass local variables to templates'
+project_page 'https://github.com/duritong/puppet-templatewlv'
+
+## Add dependencies, if any:
+# dependency 'username/name', '>= 1.2.0'
diff --git a/puppet/modules/templatewlv/README.md b/puppet/modules/templatewlv/README.md
new file mode 100644
index 00000000..5ab01e45
--- /dev/null
+++ b/puppet/modules/templatewlv/README.md
@@ -0,0 +1,21 @@
+# templatewlv
+
+## Template With Local Variables
+
+A wrapper around puppet's template function. See
+[the templating docs](http://docs.puppetlabs.com/guides/templating.html) for
+the basic functionality.
+
+Additionally, you can pass a hash, as the last argument, which will be turned into
+local variables and available to the template itself. This will allow you to define
+variables in a template and pass them down to a template you include in the current
+template. An example:
+
+ scope.function_templatewlv(['sub_template', { 'local_var' => 'value' }])
+
+Note that if multiple templates are specified, their output is all
+concatenated and returned as the output of the function.
+
+# Who - License
+
+duritong - Apache License, Version 2.0
diff --git a/puppet/modules/templatewlv/lib/puppet/parser/functions/templatewlv.rb b/puppet/modules/templatewlv/lib/puppet/parser/functions/templatewlv.rb
new file mode 100644
index 00000000..c9579e2c
--- /dev/null
+++ b/puppet/modules/templatewlv/lib/puppet/parser/functions/templatewlv.rb
@@ -0,0 +1,41 @@
+require File.join(File.dirname(__FILE__),'../templatewrapperwlv')
+Puppet::Parser::Functions::newfunction(:templatewlv, :type => :rvalue, :arity => -2, :doc =>
+ "A wrapper around puppet's template function. See
+ [the templating docs](http://docs.puppetlabs.com/guides/templating.html) for
+ the basic functionality.
+
+ Additionally, you can pass a hash, as the last argument, which will be turned into
+ local variables and available to the template itself. This will allow you to define
+ variables in a template and pass them down to a template you include in the current
+ template. An example:
+
+ scope.function_templatewlv(['sub_template', { 'local_var' => 'value' }])
+
+ Note that if multiple templates are specified, their output is all
+ concatenated and returned as the output of the function.") do |vals|
+
+ if vals.last.is_a?(Hash)
+ local_vars = vals.last
+ local_vals = vals[0..-2]
+ else
+ local_vars = {}
+ local_vals = vals
+ end
+
+ result = nil
+ local_vals.collect do |file|
+ # Use a wrapper, so the template can't get access to the full
+ # Scope object.
+ debug "Retrieving template #{file}"
+
+ wrapper = Puppet::Parser::TemplateWrapperWlv.new(self,local_vars)
+ wrapper.file = file
+ begin
+ wrapper.result
+ rescue => detail
+ info = detail.backtrace.first.split(':')
+ raise Puppet::ParseError,
+ "Failed to parse template #{file}:\n Filepath: #{info[0]}\n Line: #{info[1]}\n Detail: #{detail}\n"
+ end
+ end.join("")
+end
diff --git a/puppet/modules/templatewlv/lib/puppet/parser/templatewrapperwlv.rb b/puppet/modules/templatewlv/lib/puppet/parser/templatewrapperwlv.rb
new file mode 100644
index 00000000..f1753e18
--- /dev/null
+++ b/puppet/modules/templatewlv/lib/puppet/parser/templatewrapperwlv.rb
@@ -0,0 +1,39 @@
+# A wrapper for templates, that allows you to additionally define
+# local variables
+class Puppet::Parser::TemplateWrapperWlv < Puppet::Parser::TemplateWrapper
+ attr_reader :local_vars
+ def initialize(scope, local_vars)
+ super(scope)
+ @local_vars = local_vars
+ end
+
+ # Should return true if a variable is defined, false if it is not
+ def has_variable?(name)
+ super(name) || local_vars.keys.include?(name.to_s)
+ end
+
+ def method_missing(name, *args)
+ if local_vars.keys.include?(n=name.to_s)
+ local_vars[n]
+ else
+ super(name, *args)
+ end
+ end
+
+ def result(string = nil)
+ # Expose all the variables in our scope as instance variables of the
+ # current object, making it possible to access them without conflict
+ # to the regular methods.
+ benchmark(:debug, "Bound local template variables for #{@__file__}") do
+ local_vars.each do |name, value|
+ if name.kind_of?(String)
+ realname = name.gsub(/[^\w]/, "_")
+ else
+ realname = name
+ end
+ instance_variable_set("@#{realname}", value)
+ end
+ end
+ super(string)
+ end
+end
diff --git a/puppet/modules/tor/.gitignore b/puppet/modules/tor/.gitignore
new file mode 100644
index 00000000..1377554e
--- /dev/null
+++ b/puppet/modules/tor/.gitignore
@@ -0,0 +1 @@
+*.swp
diff --git a/puppet/modules/tor/LICENSE b/puppet/modules/tor/LICENSE
new file mode 100644
index 00000000..dba13ed2
--- /dev/null
+++ b/puppet/modules/tor/LICENSE
@@ -0,0 +1,661 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
diff --git a/puppet/modules/tor/README b/puppet/modules/tor/README
new file mode 100644
index 00000000..7777438a
--- /dev/null
+++ b/puppet/modules/tor/README
@@ -0,0 +1,214 @@
+puppet module for managing tor
+==============================
+
+This module tries to manage tor, making sure it is installed, running, has munin
+graphs if desired and allows for configuration of relays, hidden services, exit
+policies, etc.
+
+! Upgrade Notice !
+
+ previously, if you did not set the $outbound_bindaddress variable, it was being
+ automatically set to the $listen_address variable. Now this is not being done
+ and instead you will need to set the $outbound_bindaddress explicitly for it to
+ be set.
+
+ the tor::relay{} variables $bandwidth_rate and $bandwidth_burst were previously
+ used for the tor configuration variables RelayBandwidthRate and
+ RelayBandwidthBurst, these have been renamed to $relay_bandwidth_rate and
+ $relay_bandwidth_burst. If you were using these, please rename your variables in
+ your configuration.
+
+ The variables $bandwidth_rate and $bandwidth_burst are now used for the tor
+ configuration variables BandwidthRate and BandwidthBurst. If you used
+ $bandwidth_rate or $bandwidth_burst please be aware that these values have
+ changed and adjust your configuration as necessary.
+
+ The $tor_ensure_version was converted to a parameter for the tor and
+ tor::daemon classes.
+
+ The $torsocks_ensure_version was converted to a parameter for the
+ tor::torsocks class.
+
+ The options that used to be settable with the
+ tor::daemon::global_opts define now are parameters for the
+ tor::daemon class, and tor::daemon::global_opts was
+ removed accordingly.
+
+
+Dependencies
+============
+
+This module needs:
+
+- the concat module: git://labs.riseup.net/shared-concat
+
+Usage
+=====
+
+Installing tor
+--------------
+
+To install tor, simply include the 'tor' class in your manifests:
+
+ class { 'tor': }
+
+You can specify the $ensure_version class parameter to get a specific
+version installed.
+
+However, if you want to make configuration changes to your tor daemon, you will
+want to instead include the 'tor::daemon' class in your manifests, which will
+inherit the 'tor' class from above:
+
+ class { '::tor::daemon': }
+
+You have the following class parameters that you can specify:
+
+data_dir (default: '/var/lib/tor')
+config_file (default: '/etc/tor/torrc')
+use_bridges (default: 0)
+automap_hosts_on_resolve (default: 0)
+log_rules (default: ['notice file /var/log/tor/notices.log'])
+
+The data_dir will be used for the tor user's $HOME, and the tor DataDirectory
+value.
+
+The config_file will be managed and the daemon restarted when
+it changed.
+
+use_bridges and automap_hosts_on_resolve are used to set the
+UseBridges and AutomapHostsOnResolve torrc settings.
+
+The log_rules can be an array of different Log lines, each will be added to the
+config, for example the following will use syslog:
+
+ class { '::tor::daemon':
+ log_rules => [ 'notice syslog' ],
+ }
+
+If you want to set specific options for the tor class,
+you may pass them directly to the tor::daemon in your manifests,
+e.g.:
+
+ class { '::tor::daemon':
+ use_munin => true,
+ automap_hosts_on_resolve => 1,
+ }
+
+Configuring socks
+-----------------
+
+To configure tor socks support, you can do the following:
+
+ tor::daemon::socks { "listen_locally": listen_addresses => [ '127.0.0.1' ]; }
+
+this will setup the SocksListenAddress to be 127.0.0.1. You also can pass the
+following options to tor::daemon::socks:
+
+$port = 0 - SocksPort
+$listen_address - can pass multiple values to configure SocksListenAddress lines
+$policies - can pass multiple values to configure SocksPolicy lines
+
+Installing torsocks
+-------------------
+
+To install torsocks, simply include the 'torsocks' class in your manifests:
+
+ class { 'torsocks': }
+
+You can specify the $ensure_version class parameter to get a specific
+version installed.
+
+Configuring relays
+==================
+
+An example relay configuration:
+
+ tor::daemon::relay { "foobar":
+ port => 9001, listen_addresses => '192.168.0.1', address => '192.168.0.1',
+ bandwidth_rate => '256', bandwidth_burst => '256', contact_info => "Foo <collective at example dot com>",
+ my_family => '<long family string here>'
+ }
+
+You have the following options that can be passed to a relay, with the defaults shown:
+
+$port = 0,
+$listen_addresses = [],
+$portforwarding = 0, # PortForwarding 0|1, set for opening ports at the router via UPnP.
+ # Requires 'tor-fw-helper' binary present.
+$bandwidth_rate = '', # KB/s, defaulting to using tor's default: 5120KB/s
+$bandwidth_burst = '', # KB/s, defaulting to using tor's default: 10240KB/s
+$relay_bandwidth_rate = 0, # KB/s, 0 for no limit.
+$relay_bandwidth_burst = 0, # KB/s, 0 for no limit.
+$accounting_max = 0, # GB, 0 for no limit.
+$accounting_start = [],
+$contact_info = '',
+$my_family = '', # TODO: autofill with other relays
+$address = "tor.${domain}",
+$bridge_relay = 0,
+$ensure = present
+$nickname = $name
+
+Configuring the control
+-----------------------
+
+To pass parameters to configure the ControlPort and the HashedControlPassword,
+you would do something like this:
+
+ tor::daemon::control { "foo-control":
+ port => '80', hashed_control_password => '<somehash>',
+ ensure => present
+}
+
+Note: you must pass a hashed password to the control port, if you are going to
+use it.
+
+
+Configuring hidden services
+---------------------------
+
+To configure a tor hidden service you can do something like the following:
+
+ tor::daemon::hidden_service { "hidden_ssh": ports => 22 }
+
+The HiddenServiceDir is set to the ${data_dir}/${name}.
+
+Configuring directories
+-----------------------
+
+An example directory configuration:
+
+ tor::daemon::directory { 'ssh_directory':
+ port => 80, listen_address => '192.168.0.1',
+ port_front_page => '/etc/tor/tor.html'
+ }
+
+Configuring exit policies
+--------------------------
+
+To configure exit policies, you can do the following:
+
+tor::daemon::exit_policy { "ssh_exit_policy":
+ accept => "192.168.0.1:22",
+ reject => "*:*";
+ }
+ }
+
+
+Polipo
+======
+
+Polipo support can be enabled by doing:
+
+ include tor::polipo
+
+this will inherit the tor class by default, remove privoxy if its installed, and
+install polipo, making sure it is running.
+
+
+Munin
+=====
+
+If you are using munin, and have the puppet munin module installed, you can set
+the use_munin parameter to true when defining the tor::daemon class to have
+graphs setup for you.
+
diff --git a/puppet/modules/tor/files/munin/tor_connections b/puppet/modules/tor/files/munin/tor_connections
new file mode 100755
index 00000000..c1d0a928
--- /dev/null
+++ b/puppet/modules/tor/files/munin/tor_connections
@@ -0,0 +1,162 @@
+#!/usr/bin/perl -w
+#
+# Munin plugin to monitor Tor
+#
+# Author: Ge van Geldorp <ge@gse.nl>
+#
+# Parameters understood:
+#
+# host - Change which host to graph (default localhost)
+# port - Change which port to connect to (default 9051)
+# password - Plain-text control channel password (see torrc
+# HashedControlPassword parameter)
+# cookiefile - Name of the file containing the control channel cookie
+# (see torrc CookieAuthentication parameter)
+#
+# Using HashedControlPassword authentication has the problem that you must
+# include the plain-text password in the munin config file. To have any
+# effect, that file shouldn't be world-readable.
+# If you're using CookieAuthentication, you should run this plugin as a user
+# which has read access to the tor datafiles. Also note that bugs in versions
+# upto and including 0.1.1.20 prevent CookieAuthentication from working.
+#
+# Usage: place in /etc/munin/node.d/ (or link it there using ln -s)
+#
+# Parameters understood:
+# config (required)
+# autoconf (optional - used by munin-config)
+#
+#
+# Magic markers - optional - used by installation scripts and
+# munin-config:
+#
+#%# family=contrib
+#%# capabilities=autoconf
+
+use strict;
+use IO::Socket::INET;
+
+# Config
+our $address = $ENV{host} || "localhost"; # Default: localhost
+our $port = $ENV{port} || 9051; # Default: 9051
+
+# Don't edit below this line
+
+sub Authenticate
+{
+ my ($socket) = @_;
+ my $authline = "AUTHENTICATE";
+ if (defined($ENV{cookiefile})) {
+ if (open(COOKIE, "<$ENV{cookiefile}")) {
+ binmode COOKIE;
+ my $cookie;
+ $authline .= " ";
+ while (read(COOKIE, $cookie, 32)) {
+ foreach my $byte (unpack "C*", $cookie) {
+ $authline .= sprintf "%02x", $byte;
+ }
+ }
+ close COOKIE;
+ }
+ } elsif (defined($ENV{password})) {
+ $authline .= ' "' . $ENV{password} . '"';
+ }
+ print $socket "$authline\r\n";
+ my $replyline = <$socket>;
+ if (substr($replyline, 0, 1) != '2') {
+ $replyline =~ s/\s*$//;
+ return "Failed to authenticate: $replyline";
+ }
+
+ return;
+}
+
+if ($ARGV[0] and $ARGV[0] eq "autoconf") {
+ # Try to connect to the daemon
+ my $socket = IO::Socket::INET->new("$address:$port")
+ or my $failed = 1;
+
+ if ($failed) {
+ print "no (failed to connect to $address port $port)\n";
+ exit 1;
+ }
+
+ my $msg = Authenticate($socket);
+ if (defined($msg)) {
+ print $socket "QUIT\r\n";
+ close($socket);
+ print "no ($msg)\n";
+ exit 1;
+ }
+
+ print $socket "QUIT\r\n";
+ close($socket);
+ print "yes\n";
+ exit 0;
+}
+
+my %connections = ("new", 0,
+ "launched", 0,
+ "connected", 0,
+ "failed", 0,
+ "closed", 0);
+
+if ($ARGV[0] and $ARGV[0] eq "config") {
+ print "graph_title Connections\n";
+ print "graph_args -l 0 --base 1000\n";
+ print "graph_vlabel connections\n";
+ print "graph_category Tor\n";
+ print "graph_period second\n";
+ print "graph_info This graph shows the number of Tor OR connections.\n";
+
+ foreach my $status (keys %connections) {
+ print "$status.label $status\n";
+ print "$status.type GAUGE\n";
+ print "$status.max 50000\n";
+ print "$status.min 0\n";
+ }
+
+ exit 0;
+}
+
+my $socket = IO::Socket::INET->new("$address:$port")
+ or die("Couldn't connect to $address port $port: $!");
+
+my $msg = Authenticate($socket);
+if (defined($msg)) {
+ print $socket "QUIT\r\n";
+ close($socket);
+ die "$msg\n";
+}
+
+print $socket "GETINFO orconn-status\r\n";
+my $replyline = <$socket>;
+if (substr($replyline, 0, 1) != '2') {
+ print $socket "QUIT\r\n";
+ close($socket);
+ $replyline =~ s/\s*$//;
+ die "Failed to get orconn-status info: $replyline\n";
+}
+
+while (! (($replyline = <$socket>) =~ /^\.\s*$/)) {
+ my @reply = split(/\s+/, $replyline);
+ $connections{lc($reply[1])}++;
+}
+$replyline = <$socket>;
+if (substr($replyline, 0, 1) != '2') {
+ print $socket "QUIT\r\n";
+ close($socket);
+ $replyline =~ s/\s*$//;
+ die "Failed to authenticate: $replyline\n";
+}
+
+print $socket "QUIT\r\n";
+close($socket);
+
+while (my ($status, $count) = each(%connections)) {
+ print "$status.value $count\n";
+}
+
+exit 0;
+
+# vim:syntax=perl
diff --git a/puppet/modules/tor/files/munin/tor_routers b/puppet/modules/tor/files/munin/tor_routers
new file mode 100755
index 00000000..b977f9aa
--- /dev/null
+++ b/puppet/modules/tor/files/munin/tor_routers
@@ -0,0 +1,151 @@
+#!/usr/bin/perl -w
+#
+# Munin plugin to monitor Tor routers
+#
+# Author: Ævar Arnfjörð Bjarmason <avarab@gmail.com>, based on a plugin by Ge van Geldorp <ge@gse.nl>
+#
+# Parameters understood:
+#
+# host - Change which host to graph (default localhost)
+# port - Change which port to connect to (default 9051)
+# password - Plain-text control channel password (see torrc
+# HashedControlPassword parameter)
+# cookiefile - Name of the file containing the control channel cookie
+# (see torrc CookieAuthentication parameter)
+#
+# Using HashedControlPassword authentication has the problem that you must
+# include the plain-text password in the munin config file. To have any
+# effect, that file shouldn't be world-readable.
+# If you're using CookieAuthentication, you should run this plugin as a user
+# which has read access to the tor datafiles. Also note that bugs in versions
+# upto and including 0.1.1.20 prevent CookieAuthentication from working.
+#
+# Usage: place in /etc/munin/node.d/ (or link it there using ln -s)
+#
+# Parameters understood:
+# config (required)
+# autoconf (optional - used by munin-config)
+#
+#
+# Magic markers - optional - used by installation scripts and
+# munin-config:
+#
+#%# family=contrib
+#%# capabilities=autoconf
+
+use strict;
+use IO::Socket::INET;
+
+# Config
+our $address = $ENV{host} || "localhost"; # Default: localhost
+our $port = $ENV{port} || 9051; # Default: 9051
+
+# Don't edit below this line
+
+sub Authenticate
+{
+ my ($socket) = @_;
+ my $authline = "AUTHENTICATE";
+ if (defined($ENV{cookiefile})) {
+ if (open(COOKIE, "<$ENV{cookiefile}")) {
+ binmode COOKIE;
+ my $cookie;
+ $authline .= " ";
+ while (read(COOKIE, $cookie, 32)) {
+ foreach my $byte (unpack "C*", $cookie) {
+ $authline .= sprintf "%02x", $byte;
+ }
+ }
+ close COOKIE;
+ }
+ } elsif (defined($ENV{password})) {
+ $authline .= ' "' . $ENV{password} . '"';
+ }
+ print $socket "$authline\r\n";
+ my $replyline = <$socket>;
+ if (substr($replyline, 0, 1) != '2') {
+ $replyline =~ s/\s*$//;
+ return "Failed to authenticate: $replyline";
+ }
+
+ return;
+}
+
+if ($ARGV[0] and $ARGV[0] eq "autoconf") {
+ # Try to connect to the daemon
+ my $socket = IO::Socket::INET->new("$address:$port")
+ or my $failed = 1;
+
+ if ($failed) {
+ print "no (failed to connect to $address port $port)\n";
+ exit 1;
+ }
+
+ my $msg = Authenticate($socket);
+ if (defined($msg)) {
+ print $socket "QUIT\r\n";
+ close($socket);
+ print "no ($msg)\n";
+ exit 1;
+ }
+
+ print $socket "QUIT\r\n";
+ close($socket);
+ print "yes\n";
+ exit 0;
+}
+
+if ($ARGV[0] and $ARGV[0] eq "config") {
+ print "graph_title Routers\n";
+ print "graph_args -l 0\n";
+ print "graph_vlabel routers\n";
+ print "graph_category Tor\n";
+ print "graph_info This graph shows the number of known Tor ORs.\n";
+
+ print "ors.label routers\n";
+ print "ors.type GAUGE\n";
+ print "ors.info The number of known Tor ORs (onion routers)\n";
+
+ exit 0;
+}
+
+my $socket = IO::Socket::INET->new("$address:$port")
+ or die("Couldn't connect to $address port $port: $!");
+
+my $msg = Authenticate($socket);
+if (defined($msg)) {
+ print $socket "QUIT\r\n";
+ close($socket);
+ die "$msg\n";
+}
+
+print $socket "GETINFO ns/all\r\n";
+my $replyline = <$socket>;
+if (substr($replyline, 0, 1) != '2') {
+ print $socket "QUIT\r\n";
+ close($socket);
+ $replyline =~ s/\s*$//;
+ die "Failed to get orconn-status info: $replyline\n";
+}
+
+my $count;
+while (! (($replyline = <$socket>) =~ /^\.\s*$/)) {
+ my @reply = split(/\s+/, $replyline);
+ $count++ if $reply[0] eq 'r';
+}
+$replyline = <$socket>;
+if (substr($replyline, 0, 1) != '2') {
+ print $socket "QUIT\r\n";
+ close($socket);
+ $replyline =~ s/\s*$//;
+ die "Failed to authenticate: $replyline\n";
+}
+
+print $socket "QUIT\r\n";
+close($socket);
+
+print "ors.value $count\n";
+
+exit 0;
+
+# vim:syntax=perl
diff --git a/puppet/modules/tor/files/munin/tor_traffic b/puppet/modules/tor/files/munin/tor_traffic
new file mode 100755
index 00000000..a72e7d7f
--- /dev/null
+++ b/puppet/modules/tor/files/munin/tor_traffic
@@ -0,0 +1,154 @@
+#!/usr/bin/perl -w
+#
+# Munin plugin to monitor Tor traffic
+#
+# Author: Ge van Geldorp <ge@gse.nl>
+#
+# Parameters understood:
+#
+# host - Change which host to graph (default localhost)
+# port - Change which port to connect to (default 9051)
+# password - Plain-text control channel password (see torrc
+# HashedControlPassword parameter)
+# cookiefile - Name of the file containing the control channel cookie
+# (see torrc CookieAuthentication parameter)
+#
+# Using HashedControlPassword authentication has the problem that you must
+# include the plain-text password in the munin config file. To have any
+# effect, that file shouldn't be world-readable.
+# If you're using CookieAuthentication, you should run this plugin as a user
+# which has read access to the tor datafiles. Also note that bugs in versions
+# upto and including 0.1.1.20 prevent CookieAuthentication from working.
+#
+# Usage: place in /etc/munin/node.d/ (or link it there using ln -s)
+#
+# Parameters understood:
+# config (required)
+# autoconf (optional - used by munin-config)
+#
+#
+# Magic markers - optional - used by installation scripts and
+# munin-config:
+#
+#%# family=contrib
+#%# capabilities=autoconf
+
+use strict;
+use IO::Socket::INET;
+
+# Config
+our $address = $ENV{host} || "localhost"; # Default: localhost
+our $port = $ENV{port} || 9051; # Default: 9051
+
+# Don't edit below this line
+
+sub Authenticate
+{
+ my ($socket) = @_;
+ my $authline = "AUTHENTICATE";
+ if (defined($ENV{cookiefile})) {
+ if (open(COOKIE, "<$ENV{cookiefile}")) {
+ binmode COOKIE;
+ my $cookie;
+ $authline .= " ";
+ while (read(COOKIE, $cookie, 32)) {
+ foreach my $byte (unpack "C*", $cookie) {
+ $authline .= sprintf "%02x", $byte;
+ }
+ }
+ close COOKIE;
+ }
+ } elsif (defined($ENV{password})) {
+ $authline .= ' "' . $ENV{password} . '"';
+ }
+ print $socket "$authline\r\n";
+ my $replyline = <$socket>;
+ if (substr($replyline, 0, 1) != '2') {
+ $replyline =~ s/\s*$//;
+ return "Failed to authenticate: $replyline";
+ }
+
+ return;
+}
+
+if ($ARGV[0] and $ARGV[0] eq "autoconf") {
+ # Try to connect to the daemon
+ my $socket = IO::Socket::INET->new("$address:$port")
+ or my $failed = 1;
+
+ if ($failed) {
+ print "no (failed to connect to $address port $port)\n";
+ exit 1;
+ }
+
+ my $msg = Authenticate($socket);
+ if (defined($msg)) {
+ print $socket "QUIT\r\n";
+ close($socket);
+ print "no ($msg)\n";
+ exit 1;
+ }
+
+ print $socket "QUIT\r\n";
+ close($socket);
+ print "yes\n";
+ exit 0;
+}
+
+if ($ARGV[0] and $ARGV[0] eq "config") {
+ print "graph_title Traffic\n";
+ print "graph_vlabel bytes per \${graph_period} read (-) / written (+)\n";
+ print "graph_category Tor\n";
+ print "graph_info This graph shows the bandwidth used by Tor.\n";
+
+ print "read.label byte/s\n";
+ print "read.type GAUGE\n";
+ print "read.graph no\n";
+ print "read.max 10000000\n";
+ print "write.label byte/s\n";
+ print "write.type GAUGE\n";
+ print "write.negative read\n";
+ print "write.max 10000000\n";
+
+ exit 0;
+}
+
+my $socket = IO::Socket::INET->new("$address:$port")
+ or die("Couldn't connect to $address port $port: $!");
+
+my $msg = Authenticate($socket);
+if (defined($msg)) {
+ print $socket "QUIT\r\n";
+ close($socket);
+ die "$msg\n";
+}
+
+print $socket "SETEVENTS bw\r\n";
+my $replyline = <$socket>;
+if (substr($replyline, 0, 1) != '2') {
+ print $socket "QUIT\r\n";
+ close($socket);
+ $replyline =~ s/\s*$//;
+ die "Failed to get orconn-status info: $replyline\n";
+}
+
+$replyline = <$socket>;
+if (substr($replyline, 0, 1) != '6') {
+ print $socket "QUIT\r\n";
+ close($socket);
+ $replyline =~ s/\s*$//;
+ die "Failed to get bw: $replyline\n";
+}
+my @reply = split(/\s+/, $replyline);
+
+print $socket "SETEVENTS\r\n";
+$replyline = <$socket>;
+print $socket "QUIT\r\n";
+close($socket);
+
+print "read.value $reply[2]\n";
+print "write.value $reply[3]\n";
+
+exit 0;
+
+# vim:syntax=perl
diff --git a/puppet/modules/tor/files/polipo/polipo.conf b/puppet/modules/tor/files/polipo/polipo.conf
new file mode 100644
index 00000000..12b10c41
--- /dev/null
+++ b/puppet/modules/tor/files/polipo/polipo.conf
@@ -0,0 +1,164 @@
+# Polipo Configuration from https://svn.torproject.org/svn/torbrowser/trunk/build-scripts/config/polipo.conf
+# Managed by puppet.
+
+### Basic configuration
+### *******************
+
+# Uncomment one of these if you want to allow remote clients to
+# connect:
+
+# proxyAddress = "::0" # both IPv4 and IPv6
+# proxyAddress = "0.0.0.0" # IPv4 only
+
+proxyAddress = "127.0.0.1"
+proxyPort = 8118
+
+# If you do that, you'll want to restrict the set of hosts allowed to
+# connect:
+
+# allowedClients = "127.0.0.1, 134.157.168.57"
+# allowedClients = "127.0.0.1, 134.157.168.0/24"
+
+allowedClients = 127.0.0.1
+allowedPorts = 1-65535
+
+# Uncomment this if you want your Polipo to identify itself by
+# something else than the host name:
+
+proxyName = "localhost"
+
+# Uncomment this if there's only one user using this instance of Polipo:
+
+cacheIsShared = false
+
+# Uncomment this if you want to use a parent proxy:
+
+# parentProxy = "squid.example.org:3128"
+
+# Uncomment this if you want to use a parent SOCKS proxy:
+
+socksParentProxy = "localhost:9050"
+socksProxyType = socks5
+
+
+### Memory
+### ******
+
+# Uncomment this if you want Polipo to use a ridiculously small amount
+# of memory (a hundred C-64 worth or so):
+
+# chunkHighMark = 819200
+# objectHighMark = 128
+
+# Uncomment this if you've got plenty of memory:
+
+# chunkHighMark = 50331648
+# objectHighMark = 16384
+
+chunkHighMark = 67108864
+
+### On-disk data
+### ************
+
+# Uncomment this if you want to disable the on-disk cache:
+
+diskCacheRoot = ""
+
+# Uncomment this if you want to put the on-disk cache in a
+# non-standard location:
+
+# diskCacheRoot = "~/.polipo-cache/"
+
+# Uncomment this if you want to disable the local web server:
+
+localDocumentRoot = ""
+
+# Uncomment this if you want to enable the pages under /polipo/index?
+# and /polipo/servers?. This is a serious privacy leak if your proxy
+# is shared.
+
+# disableIndexing = false
+# disableServersList = false
+
+disableLocalInterface = true
+disableConfiguration = true
+
+### Domain Name System
+### ******************
+
+# Uncomment this if you want to contact IPv4 hosts only (and make DNS
+# queries somewhat faster):
+#
+# dnsQueryIPv6 = no
+
+# Uncomment this if you want Polipo to prefer IPv4 to IPv6 for
+# double-stack hosts:
+#
+# dnsQueryIPv6 = reluctantly
+
+# Uncomment this to disable Polipo's DNS resolver and use the system's
+# default resolver instead. If you do that, Polipo will freeze during
+# every DNS query:
+
+dnsUseGethostbyname = yes
+
+
+### HTTP
+### ****
+
+# Uncomment this if you want to enable detection of proxy loops.
+# This will cause your hostname (or whatever you put into proxyName
+# above) to be included in every request:
+
+disableVia = true
+
+# Uncomment this if you want to slightly reduce the amount of
+# information that you leak about yourself:
+
+# censoredHeaders = from, accept-language
+# censorReferer = maybe
+
+censoredHeaders = from,accept-language,x-pad,link
+censorReferer = maybe
+
+# Uncomment this if you're paranoid. This will break a lot of sites,
+# though:
+
+# censoredHeaders = set-cookie, cookie, cookie2, from, accept-language
+# censorReferer = true
+
+# Uncomment this if you want to use Poor Man's Multiplexing; increase
+# the sizes if you're on a fast line. They should each amount to a few
+# seconds' worth of transfer; if pmmSize is small, you'll want
+# pmmFirstSize to be larger.
+
+# Note that PMM is somewhat unreliable.
+
+# pmmFirstSize = 16384
+# pmmSize = 8192
+
+# Uncomment this if your user-agent does something reasonable with
+# Warning headers (most don't):
+
+# relaxTransparency = maybe
+
+# Uncomment this if you never want to revalidate instances for which
+# data is available (this is not a good idea):
+
+# relaxTransparency = yes
+
+# Uncomment this if you have no network:
+
+# proxyOffline = yes
+
+# Uncomment this if you want to avoid revalidating instances with a
+# Vary header (this is not a good idea):
+
+# mindlesslyCacheVary = true
+
+# Suggestions from Incognito configuration
+maxConnectionAge = 5m
+maxConnectionRequests = 120
+serverMaxSlots = 8
+serverSlots = 2
+tunnelAllowedPorts = 1-65535
diff --git a/puppet/modules/tor/files/tor-exit-notice.html b/puppet/modules/tor/files/tor-exit-notice.html
new file mode 100644
index 00000000..de3be174
--- /dev/null
+++ b/puppet/modules/tor/files/tor-exit-notice.html
@@ -0,0 +1,144 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+<title>This is a Tor Exit Router</title>
+
+<!--
+
+This notice is intended to be placed on a virtual host for a domain that
+your Tor exit node IP reverse resolves to so that people who may be about
+to file an abuse complaint would check it first before bothering you or
+your ISP. Ex:
+http://tor-exit.yourdomain.org or http://tor-readme.yourdomain.org.
+
+This type of setup has proven very effective at reducing abuse complaints
+for exit node operators.
+
+There are a few places in this document that you may want to customize.
+They are marked with FIXME.
+
+-->
+
+</head>
+<body>
+
+<p style="text-align:center; font-size:xx-large; font-weight:bold">This is a
+Tor Exit Router</p>
+
+<p>
+Most likely you are accessing this website because you had some issue with
+the traffic coming from this IP. This router is part of the <a
+href="https://www.torproject.org/">Tor Anonymity Network</a>, which is
+dedicated to <a href="https://www.torproject.org/about/overview">providing
+privacy</a> to people who need it most: average computer users. This
+router IP should be generating no other traffic, unless it has been
+compromised.</p>
+
+
+<!-- FIXME: you should probably grab your own copy of how_tor_works_thumb.png
+ and serve it locally -->
+
+<p style="text-align:center">
+<a href="https://www.torproject.org/about/overview">
+<img src="https://www.torproject.org/images/how_tor_works_thumb.png" alt="How Tor works" style="border-style:none"/>
+</a></p>
+
+<p>
+Tor sees use by <a href="https://www.torproject.org/about/torusers">many
+important segments of the population</a>, including whistle blowers,
+journalists, Chinese dissidents skirting the Great Firewall and oppressive
+censorship, abuse victims, stalker targets, the US military, and law
+enforcement, just to name a few. While Tor is not designed for malicious
+computer users, it is true that they can use the network for malicious ends.
+In reality however, the actual amount of <a
+href="https://www.torproject.org/docs/faq-abuse">abuse</a> is quite low. This
+is largely because criminals and hackers have significantly better access to
+privacy and anonymity than do the regular users whom they prey upon. Criminals
+can and do <a
+href="http://voices.washingtonpost.com/securityfix/2008/08/web_fraud_20_tools.html">build,
+sell, and trade</a> far larger and <a
+href="http://voices.washingtonpost.com/securityfix/2008/08/web_fraud_20_distributing_your.html">more
+powerful networks</a> than Tor on a daily basis. Thus, in the mind of this
+operator, the social need for easily accessible censorship-resistant private,
+anonymous communication trumps the risk of unskilled bad actors, who are
+almost always more easily uncovered by traditional police work than by
+extensive monitoring and surveillance anyway.</p>
+
+<p>
+In terms of applicable law, the best way to understand Tor is to consider it a
+network of routers operating as common carriers, much like the Internet
+backbone. However, unlike the Internet backbone routers, Tor routers
+explicitly do not contain identifiable routing information about the source of
+a packet, and no single Tor node can determine both the origin and destination
+of a given transmission.</p>
+
+<p>
+As such, there is little the operator of this router can do to help you track
+the connection further. This router maintains no logs of any of the Tor
+traffic, so there is little that can be done to trace either legitimate or
+illegitimate traffic (or to filter one from the other). Attempts to
+seize this router will accomplish nothing.</p>
+
+<!-- FIXME: US-Only section. Remove if you are a non-US operator -->
+
+<p>
+Furthermore, this machine also serves as a carrier of email, which means that
+its contents are further protected under the ECPA. <a
+href="http://www4.law.cornell.edu/uscode/html/uscode18/usc_sec_18_00002707----000-.html">18
+USC 2707</a> explicitly allows for civil remedies ($1000/account
+<i><b>plus</b></i> legal fees)
+in the event of a seizure executed without good faith or probable cause (it
+should be clear at this point that traffic with an originating IP address of
+FIXME_DNS_NAME should not constitute probable cause to seize the
+machine). Similar considerations exist for 1st amendment content on this
+machine.</p>
+
+<!-- FIXME: May or may not be US-only. Some non-US tor nodes have in
+ fact reported DMCA harassment... -->
+
+<p>
+If you are a representative of a company who feels that this router is being
+used to violate the DMCA, please be aware that this machine does not host or
+contain any illegal content. Also be aware that network infrastructure
+maintainers are not liable for the type of content that passes over their
+equipment, in accordance with <a
+href="http://www4.law.cornell.edu/uscode/html/uscode17/usc_sec_17_00000512----000-.html">DMCA
+"safe harbor" provisions</a>. In other words, you will have just as much luck
+sending a takedown notice to the Internet backbone providers. Please consult
+<a href="https://www.torproject.org/eff/tor-dmca-response">EFF's prepared
+response</a> for more information on this matter.</p>
+
+<p>For more information, please consult the following documentation:</p>
+
+<ol>
+<li><a href="https://www.torproject.org/about/overview">Tor Overview</a></li>
+<li><a href="https://www.torproject.org/docs/faq-abuse">Tor Abuse FAQ</a></li>
+<li><a href="https://www.torproject.org/eff/tor-legal-faq">Tor Legal FAQ</a></li>
+</ol>
+
+<p>
+That being said, if you still have a complaint about the router, you may
+email the <a href="mailto:FIXME_YOUR_EMAIL_ADDRESS">maintainer</a>. If
+complaints are related to a particular service that is being abused, I will
+consider removing that service from my exit policy, which would prevent my
+router from allowing that traffic to exit through it. I can only do this on an
+IP+destination port basis, however. Common P2P ports are
+already blocked.</p>
+
+<p>
+You also have the option of blocking this IP address and others on
+the Tor network if you so desire. The Tor project provides a <a
+href="https://check.torproject.org/cgi-bin/TorBulkExitList.py">web service</a>
+to fetch a list of all IP addresses of Tor exit nodes that allow exiting to a
+specified IP:port combination, and an official <a
+href="https://www.torproject.org/tordnsel/dist/">DNSRBL</a> is also available to
+determine if a given IP address is actually a Tor exit server. Please
+be considerate
+when using these options. It would be unfortunate to deny all Tor users access
+to your site indefinitely simply because of a few bad apples.</p>
+
+</body>
+</html>
diff --git a/puppet/modules/tor/files/tor.html b/puppet/modules/tor/files/tor.html
new file mode 100644
index 00000000..484545b8
--- /dev/null
+++ b/puppet/modules/tor/files/tor.html
@@ -0,0 +1,3157 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<meta name="generator" content="AsciiDoc 8.4.5" />
+<title>TOR(1)</title>
+<style type="text/css">
+/* Debug borders */
+p, li, dt, dd, div, pre, h1, h2, h3, h4, h5, h6 {
+/*
+ border: 1px solid red;
+*/
+}
+
+body {
+ margin: 1em 5% 1em 5%;
+}
+
+a {
+ color: blue;
+ text-decoration: underline;
+}
+a:visited {
+ color: fuchsia;
+}
+
+em {
+ font-style: italic;
+ color: navy;
+}
+
+strong {
+ font-weight: bold;
+ color: #083194;
+}
+
+tt {
+ color: navy;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ color: #527bbd;
+ font-family: sans-serif;
+ margin-top: 1.2em;
+ margin-bottom: 0.5em;
+ line-height: 1.3;
+}
+
+h1, h2, h3 {
+ border-bottom: 2px solid silver;
+}
+h2 {
+ padding-top: 0.5em;
+}
+h3 {
+ float: left;
+}
+h3 + * {
+ clear: left;
+}
+
+div.sectionbody {
+ font-family: serif;
+ margin-left: 0;
+}
+
+hr {
+ border: 1px solid silver;
+}
+
+p {
+ margin-top: 0.5em;
+ margin-bottom: 0.5em;
+}
+
+ul, ol, li > p {
+ margin-top: 0;
+}
+
+pre {
+ padding: 0;
+ margin: 0;
+}
+
+span#author {
+ color: #527bbd;
+ font-family: sans-serif;
+ font-weight: bold;
+ font-size: 1.1em;
+}
+span#email {
+}
+span#revnumber, span#revdate, span#revremark {
+ font-family: sans-serif;
+}
+
+div#footer {
+ font-family: sans-serif;
+ font-size: small;
+ border-top: 2px solid silver;
+ padding-top: 0.5em;
+ margin-top: 4.0em;
+}
+div#footer-text {
+ float: left;
+ padding-bottom: 0.5em;
+}
+div#footer-badges {
+ float: right;
+ padding-bottom: 0.5em;
+}
+
+div#preamble {
+ margin-top: 1.5em;
+ margin-bottom: 1.5em;
+}
+div.tableblock, div.imageblock, div.exampleblock, div.verseblock,
+div.quoteblock, div.literalblock, div.listingblock, div.sidebarblock,
+div.admonitionblock {
+ margin-top: 1.5em;
+ margin-bottom: 1.5em;
+}
+div.admonitionblock {
+ margin-top: 2.5em;
+ margin-bottom: 2.5em;
+}
+
+div.content { /* Block element content. */
+ padding: 0;
+}
+
+/* Block element titles. */
+div.title, caption.title {
+ color: #527bbd;
+ font-family: sans-serif;
+ font-weight: bold;
+ text-align: left;
+ margin-top: 1.0em;
+ margin-bottom: 0.5em;
+}
+div.title + * {
+ margin-top: 0;
+}
+
+td div.title:first-child {
+ margin-top: 0.0em;
+}
+div.content div.title:first-child {
+ margin-top: 0.0em;
+}
+div.content + div.title {
+ margin-top: 0.0em;
+}
+
+div.sidebarblock > div.content {
+ background: #ffffee;
+ border: 1px solid silver;
+ padding: 0.5em;
+}
+
+div.listingblock > div.content {
+ border: 1px solid silver;
+ background: #f4f4f4;
+ padding: 0.5em;
+}
+
+div.quoteblock {
+ padding-left: 2.0em;
+ margin-right: 10%;
+}
+div.quoteblock > div.attribution {
+ padding-top: 0.5em;
+ text-align: right;
+}
+
+div.verseblock {
+ padding-left: 2.0em;
+ margin-right: 10%;
+}
+div.verseblock > div.content {
+ white-space: pre;
+}
+div.verseblock > div.attribution {
+ padding-top: 0.75em;
+ text-align: left;
+}
+/* DEPRECATED: Pre version 8.2.7 verse style literal block. */
+div.verseblock + div.attribution {
+ text-align: left;
+}
+
+div.admonitionblock .icon {
+ vertical-align: top;
+ font-size: 1.1em;
+ font-weight: bold;
+ text-decoration: underline;
+ color: #527bbd;
+ padding-right: 0.5em;
+}
+div.admonitionblock td.content {
+ padding-left: 0.5em;
+ border-left: 2px solid silver;
+}
+
+div.exampleblock > div.content {
+ border-left: 2px solid silver;
+ padding: 0.5em;
+}
+
+div.imageblock div.content { padding-left: 0; }
+span.image img { border-style: none; }
+a.image:visited { color: white; }
+
+dl {
+ margin-top: 0.8em;
+ margin-bottom: 0.8em;
+}
+dt {
+ margin-top: 0.5em;
+ margin-bottom: 0;
+ font-style: normal;
+ color: navy;
+}
+dd > *:first-child {
+ margin-top: 0.1em;
+}
+
+ul, ol {
+ list-style-position: outside;
+}
+ol.arabic {
+ list-style-type: decimal;
+}
+ol.loweralpha {
+ list-style-type: lower-alpha;
+}
+ol.upperalpha {
+ list-style-type: upper-alpha;
+}
+ol.lowerroman {
+ list-style-type: lower-roman;
+}
+ol.upperroman {
+ list-style-type: upper-roman;
+}
+
+div.compact ul, div.compact ol,
+div.compact p, div.compact p,
+div.compact div, div.compact div {
+ margin-top: 0.1em;
+ margin-bottom: 0.1em;
+}
+
+div.tableblock > table {
+ border: 3px solid #527bbd;
+}
+thead {
+ font-family: sans-serif;
+ font-weight: bold;
+}
+tfoot {
+ font-weight: bold;
+}
+td > div.verse {
+ white-space: pre;
+}
+p.table {
+ margin-top: 0;
+}
+/* Because the table frame attribute is overriden by CSS in most browsers. */
+div.tableblock > table[frame="void"] {
+ border-style: none;
+}
+div.tableblock > table[frame="hsides"] {
+ border-left-style: none;
+ border-right-style: none;
+}
+div.tableblock > table[frame="vsides"] {
+ border-top-style: none;
+ border-bottom-style: none;
+}
+
+
+div.hdlist {
+ margin-top: 0.8em;
+ margin-bottom: 0.8em;
+}
+div.hdlist tr {
+ padding-bottom: 15px;
+}
+dt.hdlist1.strong, td.hdlist1.strong {
+ font-weight: bold;
+}
+td.hdlist1 {
+ vertical-align: top;
+ font-style: normal;
+ padding-right: 0.8em;
+ color: navy;
+}
+td.hdlist2 {
+ vertical-align: top;
+}
+div.hdlist.compact tr {
+ margin: 0;
+ padding-bottom: 0;
+}
+
+.comment {
+ background: yellow;
+}
+
+@media print {
+ div#footer-badges { display: none; }
+}
+
+div#toctitle {
+ color: #527bbd;
+ font-family: sans-serif;
+ font-size: 1.1em;
+ font-weight: bold;
+ margin-top: 1.0em;
+ margin-bottom: 0.1em;
+}
+
+div.toclevel1, div.toclevel2, div.toclevel3, div.toclevel4 {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+div.toclevel2 {
+ margin-left: 2em;
+ font-size: 0.9em;
+}
+div.toclevel3 {
+ margin-left: 4em;
+ font-size: 0.9em;
+}
+div.toclevel4 {
+ margin-left: 6em;
+ font-size: 0.9em;
+}
+/* Overrides for manpage documents */
+h1 {
+ padding-top: 0.5em;
+ padding-bottom: 0.5em;
+ border-top: 2px solid silver;
+ border-bottom: 2px solid silver;
+}
+h2 {
+ border-style: none;
+}
+div.sectionbody {
+ margin-left: 5%;
+}
+
+@media print {
+ div#toc { display: none; }
+}
+
+/* Workarounds for IE6's broken and incomplete CSS2. */
+
+div.sidebar-content {
+ background: #ffffee;
+ border: 1px solid silver;
+ padding: 0.5em;
+}
+div.sidebar-title, div.image-title {
+ color: #527bbd;
+ font-family: sans-serif;
+ font-weight: bold;
+ margin-top: 0.0em;
+ margin-bottom: 0.5em;
+}
+
+div.listingblock div.content {
+ border: 1px solid silver;
+ background: #f4f4f4;
+ padding: 0.5em;
+}
+
+div.quoteblock-attribution {
+ padding-top: 0.5em;
+ text-align: right;
+}
+
+div.verseblock-content {
+ white-space: pre;
+}
+div.verseblock-attribution {
+ padding-top: 0.75em;
+ text-align: left;
+}
+
+div.exampleblock-content {
+ border-left: 2px solid silver;
+ padding-left: 0.5em;
+}
+
+/* IE6 sets dynamically generated links as visited. */
+div#toc a:visited { color: blue; }
+</style>
+</head>
+<body>
+<div id="header">
+<h1>
+TOR(1) Manual Page
+</h1>
+<h2>NAME</h2>
+<div class="sectionbody">
+<p>tor -
+ The second-generation onion router
+</p>
+</div>
+</div>
+<h2 id="_synopsis">SYNOPSIS</h2>
+<div class="sectionbody">
+<div class="paragraph"><p><strong>tor</strong> [<em>OPTION</em> <em>value</em>]&#8230;</p></div>
+</div>
+<h2 id="_description">DESCRIPTION</h2>
+<div class="sectionbody">
+<div class="paragraph"><p><em>tor</em> is a connection-oriented anonymizing communication
+service. Users choose a source-routed path through a set of nodes, and
+negotiate a "virtual circuit" through the network, in which each node
+knows its predecessor and successor, but no others. Traffic flowing down
+the circuit is unwrapped by a symmetric key at each node, which reveals
+the downstream node.<br /></p></div>
+<div class="paragraph"><p>Basically <em>tor</em> provides a distributed network of servers ("onion routers").
+Users bounce their TCP streams&#8201;&#8212;&#8201;web traffic, ftp, ssh, etc&#8201;&#8212;&#8201;around the
+routers, and recipients, observers, and even the routers themselves have
+difficulty tracking the source of the stream.</p></div>
+</div>
+<h2 id="_options">OPTIONS</h2>
+<div class="sectionbody">
+<div class="dlist"><dl>
+<dt class="hdlist1">
+<strong>-h</strong>, <strong>-help</strong>
+</dt>
+<dd>
+<p>
+ Display a short help message and exit.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>-f</strong> <em>FILE</em>
+</dt>
+<dd>
+<p>
+ FILE contains further "option value" pairs. (Default: /etc/tor/torrc)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--hash-password</strong>
+</dt>
+<dd>
+<p>
+ Generates a hashed password for control port access.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--list-fingerprint</strong>
+</dt>
+<dd>
+<p>
+ Generate your keys and output your nickname and fingerprint.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--verify-config</strong>
+</dt>
+<dd>
+<p>
+ Verify the configuration file is valid.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--nt-service</strong>
+</dt>
+<dd>
+<p>
+ <strong>--service [install|remove|start|stop]</strong> Manage the Tor Windows
+ NT/2000/XP service. Current instructions can be found at
+ <a href="https://wiki.torproject.org/noreply/TheOnionRouter/TorFAQ#WinNTService">https://wiki.torproject.org/noreply/TheOnionRouter/TorFAQ#WinNTService</a>
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--list-torrc-options</strong>
+</dt>
+<dd>
+<p>
+ List all valid options.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--version</strong>
+</dt>
+<dd>
+<p>
+ Display Tor version and exit.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>--quiet</strong>
+</dt>
+<dd>
+<p>
+ Do not start Tor with a console log unless explicitly requested to do so.
+ (By default, Tor starts out logging messages at level "notice" or higher to
+ the console, until it has parsed its configuration.)
+</p>
+</dd>
+</dl></div>
+<div class="paragraph"><p>Other options can be specified either on the command-line (--option
+ value), or in the configuration file (option value or option "value").
+ Options are case-insensitive. C-style escaped characters are allowed inside
+ quoted values. Options on the command line take precedence over
+ options found in the configuration file, except indicated otherwise. To
+ split one configuration entry into multiple lines, use a single \ before
+ the end of the line. Comments can be used in such multiline entries, but
+ they must start at the beginning of a line.</p></div>
+<div class="dlist"><dl>
+<dt class="hdlist1">
+<strong>BandwidthRate</strong> <em>N</em> <strong>bytes</strong>|<strong>KB</strong>|<strong>MB</strong>|<strong>GB</strong>
+</dt>
+<dd>
+<p>
+ A token bucket limits the average incoming bandwidth usage on this node to
+ the specified number of bytes per second, and the average outgoing
+ bandwidth usage to that same value. If you want to run a relay in the
+ public network, this needs to be <em>at the very least</em> 20 KB (that is,
+ 20480 bytes). (Default: 5 MB)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>BandwidthBurst</strong> <em>N</em> <strong>bytes</strong>|<strong>KB</strong>|<strong>MB</strong>|<strong>GB</strong>
+</dt>
+<dd>
+<p>
+ Limit the maximum token bucket size (also known as the burst) to the given
+ number of bytes in each direction. (Default: 10 MB)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>MaxAdvertisedBandwidth</strong> <em>N</em> <strong>bytes</strong>|<strong>KB</strong>|<strong>MB</strong>|<strong>GB</strong>
+</dt>
+<dd>
+<p>
+ If set, we will not advertise more than this amount of bandwidth for our
+ BandwidthRate. Server operators who want to reduce the number of clients
+ who ask to build circuits through them (since this is proportional to
+ advertised bandwidth rate) can thus reduce the CPU demands on their server
+ without impacting network performance.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>RelayBandwidthRate</strong> <em>N</em> <strong>bytes</strong>|<strong>KB</strong>|<strong>MB</strong>|<strong>GB</strong>
+</dt>
+<dd>
+<p>
+ If not 0, a separate token bucket limits the average incoming bandwidth
+ usage for _relayed traffic_ on this node to the specified number of bytes
+ per second, and the average outgoing bandwidth usage to that same value.
+ Relayed traffic currently is calculated to include answers to directory
+ requests, but that may change in future versions. (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>RelayBandwidthBurst</strong> <em>N</em> <strong>bytes</strong>|<strong>KB</strong>|<strong>MB</strong>|<strong>GB</strong>
+</dt>
+<dd>
+<p>
+ If not 0, limit the maximum token bucket size (also known as the burst) for
+ _relayed traffic_ to the given number of bytes in each direction.
+ (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>PerConnBWRate</strong> <em>N</em> <strong>bytes</strong>|<strong>KB</strong>|<strong>MB</strong>|<strong>GB</strong>
+</dt>
+<dd>
+<p>
+ If set, do separate rate limiting for each connection from a non-relay.
+ You should never need to change this value, since a network-wide value is
+ published in the consensus and your relay will use that value. (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>PerConnBWBurst</strong> <em>N</em> <strong>bytes</strong>|<strong>KB</strong>|<strong>MB</strong>|<strong>GB</strong>
+</dt>
+<dd>
+<p>
+ If set, do separate rate limiting for each connection from a non-relay.
+ You should never need to change this value, since a network-wide value is
+ published in the consensus and your relay will use that value. (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ConnLimit</strong> <em>NUM</em>
+</dt>
+<dd>
+<p>
+ The minimum number of file descriptors that must be available to the Tor
+ process before it will start. Tor will ask the OS for as many file
+ descriptors as the OS will allow (you can find this by "ulimit -H -n").
+ If this number is less than ConnLimit, then Tor will refuse to start.<br />
+<br />
+ You probably don&#8217;t need to adjust this. It has no effect on Windows
+ since that platform lacks getrlimit(). (Default: 1000)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ConstrainedSockets</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If set, Tor will tell the kernel to attempt to shrink the buffers for all
+ sockets to the size specified in <strong>ConstrainedSockSize</strong>. This is useful for
+ virtual servers and other environments where system level TCP buffers may
+ be limited. If you&#8217;re on a virtual server, and you encounter the "Error
+ creating network socket: No buffer space available" message, you are
+ likely experiencing this problem.<br />
+<br />
+ The preferred solution is to have the admin increase the buffer pool for
+ the host itself via /proc/sys/net/ipv4/tcp_mem or equivalent facility;
+ this configuration option is a second-resort.<br />
+<br />
+ The DirPort option should also not be used if TCP buffers are scarce. The
+ cached directory requests consume additional sockets which exacerbates
+ the problem.<br />
+<br />
+ You should <strong>not</strong> enable this feature unless you encounter the "no buffer
+ space available" issue. Reducing the TCP buffers affects window size for
+ the TCP stream and will reduce throughput in proportion to round trip
+ time on long paths. (Default: 0.)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ConstrainedSockSize</strong> <em>N</em> <strong>bytes</strong>|<strong>KB</strong>
+</dt>
+<dd>
+<p>
+ When <strong>ConstrainedSockets</strong> is enabled the receive and transmit buffers for
+ all sockets will be set to this limit. Must be a value between 2048 and
+ 262144, in 1024 byte increments. Default of 8192 is recommended.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ControlPort</strong> <em>PORT</em>|<strong>auto</strong>
+</dt>
+<dd>
+<p>
+ If set, Tor will accept connections on this port and allow those
+ connections to control the Tor process using the Tor Control Protocol
+ (described in control-spec.txt). Note: unless you also specify one or
+ more of <strong>HashedControlPassword</strong> or <strong>CookieAuthentication</strong>,
+ setting this option will cause Tor to allow any process on the local
+ host to control it. (Setting both authentication methods means either
+ method is sufficient to authenticate to Tor.) This
+ option is required for many Tor controllers; most use the value of 9051.
+ Set it to "auto" to have Tor pick a port for you. (Default: 0).
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ControlListenAddress</strong> <em>IP</em>[:<em>PORT</em>]
+</dt>
+<dd>
+<p>
+ Bind the controller listener to this address. If you specify a port, bind
+ to this port rather than the one specified in ControlPort. We strongly
+ recommend that you leave this alone unless you know what you&#8217;re doing,
+ since giving attackers access to your control listener is really
+ dangerous. (Default: 127.0.0.1) This directive can be specified multiple
+ times to bind to multiple addresses/ports.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ControlSocket</strong> <em>Path</em>
+</dt>
+<dd>
+<p>
+ Like ControlPort, but listens on a Unix domain socket, rather than a TCP
+ socket. (Unix and Unix-like systems only.)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ControlSocketsGroupWritable</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If this option is set to 0, don&#8217;t allow the filesystem group to read and
+ write unix sockets (e.g. ControlSocket). If the option is set to 1, make
+ the control socket readable and writable by the default GID. (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>HashedControlPassword</strong> <em>hashed_password</em>
+</dt>
+<dd>
+<p>
+ Allow connections on the control port if they present
+ the password whose one-way hash is <em>hashed_password</em>. You
+ can compute the hash of a password by running "tor --hash-password
+ <em>password</em>". You can provide several acceptable passwords by using more
+ than one HashedControlPassword line.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>CookieAuthentication</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If this option is set to 1, allow connections on the control port
+ when the connecting process knows the contents of a file named
+ "control_auth_cookie", which Tor will create in its data directory. This
+ authentication method should only be used on systems with good filesystem
+ security. (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>CookieAuthFile</strong> <em>Path</em>
+</dt>
+<dd>
+<p>
+ If set, this option overrides the default location and file name
+ for Tor&#8217;s cookie file. (See CookieAuthentication above.)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>CookieAuthFileGroupReadable</strong> <strong>0</strong>|<strong>1</strong>|<em>Groupname</em>
+</dt>
+<dd>
+<p>
+ If this option is set to 0, don&#8217;t allow the filesystem group to read the
+ cookie file. If the option is set to 1, make the cookie file readable by
+ the default GID. [Making the file readable by other groups is not yet
+ implemented; let us know if you need this for some reason.] (Default: 0).
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ControlPortWriteToFile</strong> <em>Path</em>
+</dt>
+<dd>
+<p>
+ If set, Tor writes the address and port of any control port it opens to
+ this address. Usable by controllers to learn the actual control port
+ when ControlPort is set to "auto".
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ControlPortFileGroupReadable</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If this option is set to 0, don&#8217;t allow the filesystem group to read the
+ control port file. If the option is set to 1, make the control port
+ file readable by the default GID. (Default: 0).
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>DataDirectory</strong> <em>DIR</em>
+</dt>
+<dd>
+<p>
+ Store working data in DIR (Default: /var/lib/tor)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>DirServer</strong> [<em>nickname</em>] [<strong>flags</strong>] <em>address</em>:<em>port</em> <em>fingerprint</em>
+</dt>
+<dd>
+<p>
+ Use a nonstandard authoritative directory server at the provided address
+ and port, with the specified key fingerprint. This option can be repeated
+ many times, for multiple authoritative directory servers. Flags are
+ separated by spaces, and determine what kind of an authority this directory
+ is. By default, every authority is authoritative for current ("v2")-style
+ directories, unless the "no-v2" flag is given. If the "v1" flags is
+ provided, Tor will use this server as an authority for old-style (v1)
+ directories as well. (Only directory mirrors care about this.) Tor will
+ use this server as an authority for hidden service information if the "hs"
+ flag is set, or if the "v1" flag is set and the "no-hs" flag is <strong>not</strong> set.
+ Tor will use this authority as a bridge authoritative directory if the
+ "bridge" flag is set. If a flag "orport=<strong>port</strong>" is given, Tor will use the
+ given port when opening encrypted tunnels to the dirserver. Lastly, if a
+ flag "v3ident=<strong>fp</strong>" is given, the dirserver is a v3 directory authority
+ whose v3 long-term signing key has the fingerprint <strong>fp</strong>.<br />
+<br />
+ If no <strong>dirserver</strong> line is given, Tor will use the default directory
+ servers. NOTE: this option is intended for setting up a private Tor
+ network with its own directory authorities. If you use it, you will be
+ distinguishable from other users, because you won&#8217;t believe the same
+ authorities they do.
+</p>
+</dd>
+</dl></div>
+<div class="paragraph"><p><strong>AlternateDirAuthority</strong> [<em>nickname</em>] [<strong>flags</strong>] <em>address</em>:<em>port</em> <em>fingerprint</em><br /></p></div>
+<div class="paragraph"><p><strong>AlternateHSAuthority</strong> [<em>nickname</em>] [<strong>flags</strong>] <em>address</em>:<em>port</em> <em>fingerprint</em><br /></p></div>
+<div class="dlist"><dl>
+<dt class="hdlist1">
+<strong>AlternateBridgeAuthority</strong> [<em>nickname</em>] [<strong>flags</strong>] <em>address</em>:<em>port</em> <em> fingerprint</em>
+</dt>
+<dd>
+<p>
+ As DirServer, but replaces less of the default directory authorities. Using
+ AlternateDirAuthority replaces the default Tor directory authorities, but
+ leaves the hidden service authorities and bridge authorities in place.
+ Similarly, Using AlternateHSAuthority replaces the default hidden service
+ authorities, but not the directory or bridge authorities.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>DisableAllSwap</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If set to 1, Tor will attempt to lock all current and future memory pages,
+ so that memory cannot be paged out. Windows, OS X and Solaris are currently
+ not supported. We believe that this feature works on modern Gnu/Linux
+ distributions, and that it should work on *BSD systems (untested). This
+ option requires that you start your Tor as root, and you should use the
+ <strong>User</strong> option to properly reduce Tor&#8217;s privileges. (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>FetchDirInfoEarly</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If set to 1, Tor will always fetch directory information like other
+ directory caches, even if you don&#8217;t meet the normal criteria for fetching
+ early. Normal users should leave it off. (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>FetchDirInfoExtraEarly</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If set to 1, Tor will fetch directory information before other directory
+ caches. It will attempt to download directory information closer to the
+ start of the consensus period. Normal users should leave it off.
+ (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>FetchHidServDescriptors</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If set to 0, Tor will never fetch any hidden service descriptors from the
+ rendezvous directories. This option is only useful if you&#8217;re using a Tor
+ controller that handles hidden service fetches for you. (Default: 1)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>FetchServerDescriptors</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If set to 0, Tor will never fetch any network status summaries or server
+ descriptors from the directory servers. This option is only useful if
+ you&#8217;re using a Tor controller that handles directory fetches for you.
+ (Default: 1)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>FetchUselessDescriptors</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If set to 1, Tor will fetch every non-obsolete descriptor from the
+ authorities that it hears about. Otherwise, it will avoid fetching useless
+ descriptors, for example for routers that are not running. This option is
+ useful if you&#8217;re using the contributed "exitlist" script to enumerate Tor
+ nodes that exit to certain addresses. (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>HTTPProxy</strong> <em>host</em>[:<em>port</em>]
+</dt>
+<dd>
+<p>
+ Tor will make all its directory requests through this host:port (or host:80
+ if port is not specified), rather than connecting directly to any directory
+ servers.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>HTTPProxyAuthenticator</strong> <em>username:password</em>
+</dt>
+<dd>
+<p>
+ If defined, Tor will use this username:password for Basic HTTP proxy
+ authentication, as in RFC 2617. This is currently the only form of HTTP
+ proxy authentication that Tor supports; feel free to submit a patch if you
+ want it to support others.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>HTTPSProxy</strong> <em>host</em>[:<em>port</em>]
+</dt>
+<dd>
+<p>
+ Tor will make all its OR (SSL) connections through this host:port (or
+ host:443 if port is not specified), via HTTP CONNECT rather than connecting
+ directly to servers. You may want to set <strong>FascistFirewall</strong> to restrict
+ the set of ports you might try to connect to, if your HTTPS proxy only
+ allows connecting to certain ports.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>HTTPSProxyAuthenticator</strong> <em>username:password</em>
+</dt>
+<dd>
+<p>
+ If defined, Tor will use this username:password for Basic HTTPS proxy
+ authentication, as in RFC 2617. This is currently the only form of HTTPS
+ proxy authentication that Tor supports; feel free to submit a patch if you
+ want it to support others.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>Socks4Proxy</strong> <em>host</em>[:<em>port</em>]
+</dt>
+<dd>
+<p>
+ Tor will make all OR connections through the SOCKS 4 proxy at host:port
+ (or host:1080 if port is not specified).
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>Socks5Proxy</strong> <em>host</em>[:<em>port</em>]
+</dt>
+<dd>
+<p>
+ Tor will make all OR connections through the SOCKS 5 proxy at host:port
+ (or host:1080 if port is not specified).
+</p>
+</dd>
+</dl></div>
+<div class="paragraph"><p><strong>Socks5ProxyUsername</strong> <em>username</em><br /></p></div>
+<div class="dlist"><dl>
+<dt class="hdlist1">
+<strong>Socks5ProxyPassword</strong> <em>password</em>
+</dt>
+<dd>
+<p>
+ If defined, authenticate to the SOCKS 5 server using username and password
+ in accordance to RFC 1929. Both username and password must be between 1 and
+ 255 characters.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>KeepalivePeriod</strong> <em>NUM</em>
+</dt>
+<dd>
+<p>
+ To keep firewalls from expiring connections, send a padding keepalive cell
+ every NUM seconds on open connections that are in use. If the connection
+ has no open circuits, it will instead be closed after NUM seconds of
+ idleness. (Default: 5 minutes)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>Log</strong> <em>minSeverity</em>[-<em>maxSeverity</em>] <strong>stderr</strong>|<strong>stdout</strong>|<strong>syslog</strong>
+</dt>
+<dd>
+<p>
+ Send all messages between <em>minSeverity</em> and <em>maxSeverity</em> to the standard
+ output stream, the standard error stream, or to the system log. (The
+ "syslog" value is only supported on Unix.) Recognized severity levels are
+ debug, info, notice, warn, and err. We advise using "notice" in most cases,
+ since anything more verbose may provide sensitive information to an
+ attacker who obtains the logs. If only one severity level is given, all
+ messages of that level or higher will be sent to the listed destination.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>Log</strong> <em>minSeverity</em>[-<em>maxSeverity</em>] <strong>file</strong> <em>FILENAME</em>
+</dt>
+<dd>
+<p>
+ As above, but send log messages to the listed filename. The
+ "Log" option may appear more than once in a configuration file.
+ Messages are sent to all the logs that match their severity
+ level.
+</p>
+</dd>
+</dl></div>
+<div class="paragraph"><p><strong>Log</strong> <strong>[</strong><em>domain</em>,&#8230;<strong>]</strong><em>minSeverity</em>[-<em>maxSeverity</em>] &#8230; <strong>file</strong> <em>FILENAME</em><br /></p></div>
+<div class="dlist"><dl>
+<dt class="hdlist1">
+<strong>Log</strong> <strong>[</strong><em>domain</em>,&#8230;<strong>]</strong><em>minSeverity</em>[-<em>maxSeverity</em>] &#8230; <strong>stderr</strong>|<strong>stdout</strong>|<strong>syslog</strong>
+</dt>
+<dd>
+<p>
+ As above, but select messages by range of log severity <em>and</em> by a
+ set of "logging domains". Each logging domain corresponds to an area of
+ functionality inside Tor. You can specify any number of severity ranges
+ for a single log statement, each of them prefixed by a comma-separated
+ list of logging domains. You can prefix a domain with ~ to indicate
+ negation, and use * to indicate "all domains". If you specify a severity
+ range without a list of domains, it matches all domains.<br />
+<br />
+ This is an advanced feature which is most useful for debugging one or two
+ of Tor&#8217;s subsystems at a time.<br />
+<br />
+ The currently recognized domains are: general, crypto, net, config, fs,
+ protocol, mm, http, app, control, circ, rend, bug, dir, dirserv, or, edge,
+ acct, hist, and handshake. Domain names are case-insensitive.<br />
+<br />
+ For example, "<tt>Log [handshake]debug [~net,~mm]info notice stdout</tt>" sends
+ to stdout: all handshake messages of any severity, all info-and-higher
+ messages from domains other than networking and memory management, and all
+ messages of severity notice or higher.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>LogMessageDomains</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If 1, Tor includes message domains with each log message. Every log
+ message currently has at least one domain; most currently have exactly
+ one. This doesn&#8217;t affect controller log messages. (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>OutboundBindAddress</strong> <em>IP</em>
+</dt>
+<dd>
+<p>
+ Make all outbound connections originate from the IP address specified. This
+ is only useful when you have multiple network interfaces, and you want all
+ of Tor&#8217;s outgoing connections to use a single one. This setting will be
+ ignored for connections to the loopback addresses (127.0.0.0/8 and ::1).
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>PidFile</strong> <em>FILE</em>
+</dt>
+<dd>
+<p>
+ On startup, write our PID to FILE. On clean shutdown, remove
+ FILE.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ProtocolWarnings</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If 1, Tor will log with severity 'warn' various cases of other parties not
+ following the Tor specification. Otherwise, they are logged with severity
+ 'info'. (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>RunAsDaemon</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If 1, Tor forks and daemonizes to the background. This option has no effect
+ on Windows; instead you should use the --service command-line option.
+ (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>SafeLogging</strong> <strong>0</strong>|<strong>1</strong>|<strong>relay</strong>
+</dt>
+<dd>
+<p>
+ Tor can scrub potentially sensitive strings from log messages (e.g.
+ addresses) by replacing them with the string [scrubbed]. This way logs can
+ still be useful, but they don&#8217;t leave behind personally identifying
+ information about what sites a user might have visited.<br />
+<br />
+ If this option is set to 0, Tor will not perform any scrubbing, if it is
+ set to 1, all potentially sensitive strings are replaced. If it is set to
+ relay, all log messages generated when acting as a relay are sanitized, but
+ all messages generated when acting as a client are not. (Default: 1)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>User</strong> <em>UID</em>
+</dt>
+<dd>
+<p>
+ On startup, setuid to this user and setgid to their primary group.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>HardwareAccel</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If non-zero, try to use built-in (static) crypto hardware acceleration when
+ available. (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>AccelName</strong> <em>NAME</em>
+</dt>
+<dd>
+<p>
+ When using OpenSSL hardware crypto acceleration attempt to load the dynamic
+ engine of this name. This must be used for any dynamic hardware engine.
+ Names can be verified with the openssl engine command.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>AccelDir</strong> <em>DIR</em>
+</dt>
+<dd>
+<p>
+ Specify this option if using dynamic hardware acceleration and the engine
+ implementation library resides somewhere other than the OpenSSL default.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>AvoidDiskWrites</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If non-zero, try to write to disk less frequently than we would otherwise.
+ This is useful when running on flash memory or other media that support
+ only a limited number of writes. (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>TunnelDirConns</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If non-zero, when a directory server we contact supports it, we will build
+ a one-hop circuit and make an encrypted connection via its ORPort.
+ (Default: 1)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>PreferTunneledDirConns</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If non-zero, we will avoid directory servers that don&#8217;t support tunneled
+ directory connections, when possible. (Default: 1)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>CircuitPriorityHalflife</strong> <em>NUM1</em>
+</dt>
+<dd>
+<p>
+ If this value is set, we override the default algorithm for choosing which
+ circuit&#8217;s cell to deliver or relay next. When the value is 0, we
+ round-robin between the active circuits on a connection, delivering one
+ cell from each in turn. When the value is positive, we prefer delivering
+ cells from whichever connection has the lowest weighted cell count, where
+ cells are weighted exponentially according to the supplied
+ CircuitPriorityHalflife value (in seconds). If this option is not set at
+ all, we use the behavior recommended in the current consensus
+ networkstatus. This is an advanced option; you generally shouldn&#8217;t have
+ to mess with it. (Default: not set.)
+</p>
+</dd>
+</dl></div>
+</div>
+<h2 id="_client_options">CLIENT OPTIONS</h2>
+<div class="sectionbody">
+<div class="paragraph"><p>The following options are useful only for clients (that is, if
+<strong>SocksPort</strong> is non-zero):</p></div>
+<div class="dlist"><dl>
+<dt class="hdlist1">
+<strong>AllowInvalidNodes</strong> <strong>entry</strong>|<strong>exit</strong>|<strong>middle</strong>|<strong>introduction</strong>|<strong>rendezvous</strong>|<strong>&#8230;</strong>
+</dt>
+<dd>
+<p>
+ If some Tor servers are obviously not working right, the directory
+ authorities can manually mark them as invalid, meaning that it&#8217;s not
+ recommended you use them for entry or exit positions in your circuits. You
+ can opt to use them in some circuit positions, though. The default is
+ "middle,rendezvous", and other choices are not advised.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ExcludeSingleHopRelays</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ This option controls whether circuits built by Tor will include relays with
+ the AllowSingleHopExits flag set to true. If ExcludeSingleHopRelays is set
+ to 0, these relays will be included. Note that these relays might be at
+ higher risk of being seized or observed, so they are not normally
+ included. Also note that relatively few clients turn off this option,
+ so using these relays might make your client stand out.
+ (Default: 1)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>Bridge</strong> <em>IP</em>:<em>ORPort</em> [fingerprint]
+</dt>
+<dd>
+<p>
+ When set along with UseBridges, instructs Tor to use the relay at
+ "IP:ORPort" as a "bridge" relaying into the Tor network. If "fingerprint"
+ is provided (using the same format as for DirServer), we will verify that
+ the relay running at that location has the right fingerprint. We also use
+ fingerprint to look up the bridge descriptor at the bridge authority, if
+ it&#8217;s provided and if UpdateBridgesFromAuthority is set too.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>LearnCircuitBuildTimeout</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If 0, CircuitBuildTimeout adaptive learning is disabled. (Default: 1)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>CircuitBuildTimeout</strong> <em>NUM</em>
+</dt>
+<dd>
+<p>
+ Try for at most NUM seconds when building circuits. If the circuit isn&#8217;t
+ open in that time, give up on it. If LearnCircuitBuildTimeout is 1, this
+ value serves as the initial value to use before a timeout is learned. If
+ LearnCircuitBuildTimeout is 0, this value is the only value used.
+ (Default: 60 seconds.)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>CircuitIdleTimeout</strong> <em>NUM</em>
+</dt>
+<dd>
+<p>
+ If we have kept a clean (never used) circuit around for NUM seconds, then
+ close it. This way when the Tor client is entirely idle, it can expire all
+ of its circuits, and then expire its TLS connections. Also, if we end up
+ making a circuit that is not useful for exiting any of the requests we&#8217;re
+ receiving, it won&#8217;t forever take up a slot in the circuit list. (Default: 1
+ hour.)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>CircuitStreamTimeout</strong> <em>NUM</em>
+</dt>
+<dd>
+<p>
+ If non-zero, this option overrides our internal timeout schedule for how
+ many seconds until we detach a stream from a circuit and try a new circuit.
+ If your network is particularly slow, you might want to set this to a
+ number like 60. (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ClientOnly</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If set to 1, Tor will under no circumstances run as a server or serve
+ directory requests. The default is to run as a client unless ORPort is
+ configured. (Usually, you don&#8217;t need to set this; Tor is pretty smart at
+ figuring out whether you are reliable and high-bandwidth enough to be a
+ useful server.) (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ExcludeNodes</strong> <em>node</em>,<em>node</em>,<em>&#8230;</em>
+</dt>
+<dd>
+<p>
+ A list of identity fingerprints, nicknames, country codes and address
+ patterns of nodes to avoid when building a circuit.
+ (Example:
+ ExcludeNodes SlowServer, ABCD1234CDEF5678ABCD1234CDEF5678ABCD1234, {cc}, 255.254.0.0/8)<br />
+<br />
+ By default, this option is treated as a preference that Tor is allowed
+ to override in order to keep working.
+ For example, if you try to connect to a hidden service,
+ but you have excluded all of the hidden service&#8217;s introduction points,
+ Tor will connect to one of them anyway. If you do not want this
+ behavior, set the StrictNodes option (documented below). <br />
+<br />
+ Note also that if you are a relay, this (and the other node selection
+ options below) only affects your own circuits that Tor builds for you.
+ Clients can still build circuits through you to any node. Controllers
+ can tell Tor to build circuits through any node.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ExcludeExitNodes</strong> <em>node</em>,<em>node</em>,<em>&#8230;</em>
+</dt>
+<dd>
+<p>
+ A list of identity fingerprints, nicknames, country codes and address
+ patterns of nodes to never use when picking an exit node---that is, a
+ node that delivers traffic for you outside the Tor network. Note that any
+ node listed in ExcludeNodes is automatically considered to be part of this
+ list too. See also the caveats on the "ExitNodes" option below.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ExitNodes</strong> <em>node</em>,<em>node</em>,<em>&#8230;</em>
+</dt>
+<dd>
+<p>
+ A list of identity fingerprints, nicknames, country codes and address
+ patterns of nodes to use as exit node---that is, a
+ node that delivers traffic for you outside the Tor network.<br />
+<br />
+ Note that if you list too few nodes here, or if you exclude too many exit
+ nodes with ExcludeExitNodes, you can degrade functionality. For example,
+ if none of the exits you list allows traffic on port 80 or 443, you won&#8217;t
+ be able to browse the web.<br />
+<br />
+ Note also that not every circuit is used to deliver traffic outside of
+ the Tor network. It is normal to see non-exit circuits (such as those
+ used to connect to hidden services, those that do directory fetches,
+ those used for relay reachability self-tests, and so on) that end
+ at a non-exit node. To
+ keep a node from being used entirely, see ExcludeNodes and StrictNodes.<br />
+<br />
+ The ExcludeNodes option overrides this option: any node listed in both
+ ExitNodes and ExcludeNodes is treated as excluded.<br />
+<br />
+ The .exit address notation, if enabled via AllowDotExit, overrides
+ this option.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>EntryNodes</strong> <em>node</em>,<em>node</em>,<em>&#8230;</em>
+</dt>
+<dd>
+<p>
+ A list of identity fingerprints and nicknames of nodes
+ to use for the first hop in your normal circuits. (Country codes and
+ address patterns are not yet supported.) Normal circuits include all
+ circuits except for direct connections to directory servers. The Bridge
+ option overrides this option; if you have configured bridges and
+ UseBridges is 1, the Bridges are used as your entry nodes.<br />
+<br />
+ The ExcludeNodes option overrides this option: any node listed in both
+ EntryNodes and ExcludeNodes is treated as excluded.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>StrictNodes</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If StrictNodes is set to 1, Tor will treat the ExcludeNodes option as a
+ requirement to follow for all the circuits you generate, even if doing so
+ will break functionality for you. If StrictNodes is set to 0, Tor will
+ still try to avoid nodes in the ExcludeNodes list, but it will err on the
+ side of avoiding unexpected errors. Specifically, StrictNodes 0 tells
+ Tor that it is okay to use an excluded node when it is <strong>necessary</strong> to
+ perform relay reachability self-tests, connect to
+ a hidden service, provide a hidden service to a client, fulfill a .exit
+ request, upload directory information, or download directory information.
+ (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>FascistFirewall</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If 1, Tor will only create outgoing connections to ORs running on ports
+ that your firewall allows (defaults to 80 and 443; see <strong>FirewallPorts</strong>).
+ This will allow you to run Tor as a client behind a firewall with
+ restrictive policies, but will not allow you to run as a server behind such
+ a firewall. If you prefer more fine-grained control, use
+ ReachableAddresses instead.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>FirewallPorts</strong> <em>PORTS</em>
+</dt>
+<dd>
+<p>
+ A list of ports that your firewall allows you to connect to. Only used when
+ <strong>FascistFirewall</strong> is set. This option is deprecated; use ReachableAddresses
+ instead. (Default: 80, 443)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>HidServAuth</strong> <em>onion-address</em> <em>auth-cookie</em> [<em>service-name</em>]
+</dt>
+<dd>
+<p>
+ Client authorization for a hidden service. Valid onion addresses contain 16
+ characters in a-z2-7 plus ".onion", and valid auth cookies contain 22
+ characters in A-Za-z0-9+/. The service name is only used for internal
+ purposes, e.g., for Tor controllers. This option may be used multiple times
+ for different hidden services. If a hidden service uses authorization and
+ this option is not set, the hidden service is not accessible. Hidden
+ services can be configured to require authorization using the
+ <strong>HiddenServiceAuthorizeClient</strong> option.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ReachableAddresses</strong> <em>ADDR</em>[/<em>MASK</em>][:<em>PORT</em>]&#8230;
+</dt>
+<dd>
+<p>
+ A comma-separated list of IP addresses and ports that your firewall allows
+ you to connect to. The format is as for the addresses in ExitPolicy, except
+ that "accept" is understood unless "reject" is explicitly provided. For
+ example, 'ReachableAddresses 99.0.0.0/8, reject 18.0.0.0/8:80, accept
+ *:80' means that your firewall allows connections to everything inside net
+ 99, rejects port 80 connections to net 18, and accepts connections to port
+ 80 otherwise. (Default: 'accept *:*'.)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ReachableDirAddresses</strong> <em>ADDR</em>[/<em>MASK</em>][:<em>PORT</em>]&#8230;
+</dt>
+<dd>
+<p>
+ Like <strong>ReachableAddresses</strong>, a list of addresses and ports. Tor will obey
+ these restrictions when fetching directory information, using standard HTTP
+ GET requests. If not set explicitly then the value of
+ <strong>ReachableAddresses</strong> is used. If <strong>HTTPProxy</strong> is set then these
+ connections will go through that proxy.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ReachableORAddresses</strong> <em>ADDR</em>[/<em>MASK</em>][:<em>PORT</em>]&#8230;
+</dt>
+<dd>
+<p>
+ Like <strong>ReachableAddresses</strong>, a list of addresses and ports. Tor will obey
+ these restrictions when connecting to Onion Routers, using TLS/SSL. If not
+ set explicitly then the value of <strong>ReachableAddresses</strong> is used. If
+ <strong>HTTPSProxy</strong> is set then these connections will go through that proxy.<br />
+<br />
+ The separation between <strong>ReachableORAddresses</strong> and
+ <strong>ReachableDirAddresses</strong> is only interesting when you are connecting
+ through proxies (see <strong>HTTPProxy</strong> and <strong>HTTPSProxy</strong>). Most proxies limit
+ TLS connections (which Tor uses to connect to Onion Routers) to port 443,
+ and some limit HTTP GET requests (which Tor uses for fetching directory
+ information) to port 80.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>LongLivedPorts</strong> <em>PORTS</em>
+</dt>
+<dd>
+<p>
+ A list of ports for services that tend to have long-running connections
+ (e.g. chat and interactive shells). Circuits for streams that use these
+ ports will contain only high-uptime nodes, to reduce the chance that a node
+ will go down before the stream is finished. (Default: 21, 22, 706, 1863,
+ 5050, 5190, 5222, 5223, 6667, 6697, 8300)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>MapAddress</strong> <em>address</em> <em>newaddress</em>
+</dt>
+<dd>
+<p>
+ When a request for address arrives to Tor, it will rewrite it to newaddress
+ before processing it. For example, if you always want connections to
+ www.indymedia.org to exit via <em>torserver</em> (where <em>torserver</em> is the
+ nickname of the server), use "MapAddress www.indymedia.org
+ www.indymedia.org.torserver.exit".
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>NewCircuitPeriod</strong> <em>NUM</em>
+</dt>
+<dd>
+<p>
+ Every NUM seconds consider whether to build a new circuit. (Default: 30
+ seconds)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>MaxCircuitDirtiness</strong> <em>NUM</em>
+</dt>
+<dd>
+<p>
+ Feel free to reuse a circuit that was first used at most NUM seconds ago,
+ but never attach a new stream to a circuit that is too old. (Default: 10
+ minutes)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>NodeFamily</strong> <em>node</em>,<em>node</em>,<em>&#8230;</em>
+</dt>
+<dd>
+<p>
+ The Tor servers, defined by their identity fingerprints or nicknames,
+ constitute a "family" of similar or co-administered servers, so never use
+ any two of them in the same circuit. Defining a NodeFamily is only needed
+ when a server doesn&#8217;t list the family itself (with MyFamily). This option
+ can be used multiple times.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>EnforceDistinctSubnets</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If 1, Tor will not put two servers whose IP addresses are "too close" on
+ the same circuit. Currently, two addresses are "too close" if they lie in
+ the same /16 range. (Default: 1)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>SocksPort</strong> <em>PORT</em>|<strong>auto</strong>
+</dt>
+<dd>
+<p>
+ Advertise this port to listen for connections from Socks-speaking
+ applications. Set this to 0 if you don&#8217;t want to allow application
+ connections via SOCKS. Set it to "auto" to have Tor pick a port for
+ you. (Default: 9050)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>SocksListenAddress</strong> <em>IP</em>[:<em>PORT</em>]
+</dt>
+<dd>
+<p>
+ Bind to this address to listen for connections from Socks-speaking
+ applications. (Default: 127.0.0.1) You can also specify a port (e.g.
+ 192.168.0.1:9100). This directive can be specified multiple times to bind
+ to multiple addresses/ports.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>SocksPolicy</strong> <em>policy</em>,<em>policy</em>,<em>&#8230;</em>
+</dt>
+<dd>
+<p>
+ Set an entrance policy for this server, to limit who can connect to the
+ SocksPort and DNSPort ports. The policies have the same form as exit
+ policies below.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>SocksTimeout</strong> <em>NUM</em>
+</dt>
+<dd>
+<p>
+ Let a socks connection wait NUM seconds handshaking, and NUM seconds
+ unattached waiting for an appropriate circuit, before we fail it. (Default:
+ 2 minutes.)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>TrackHostExits</strong> <em>host</em>,<em>.domain</em>,<em>&#8230;</em>
+</dt>
+<dd>
+<p>
+ For each value in the comma separated list, Tor will track recent
+ connections to hosts that match this value and attempt to reuse the same
+ exit node for each. If the value is prepended with a '.', it is treated as
+ matching an entire domain. If one of the values is just a '.', it means
+ match everything. This option is useful if you frequently connect to sites
+ that will expire all your authentication cookies (i.e. log you out) if
+ your IP address changes. Note that this option does have the disadvantage
+ of making it more clear that a given history is associated with a single
+ user. However, most people who would wish to observe this will observe it
+ through cookies or other protocol-specific means anyhow.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>TrackHostExitsExpire</strong> <em>NUM</em>
+</dt>
+<dd>
+<p>
+ Since exit servers go up and down, it is desirable to expire the
+ association between host and exit server after NUM seconds. The default is
+ 1800 seconds (30 minutes).
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>UpdateBridgesFromAuthority</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ When set (along with UseBridges), Tor will try to fetch bridge descriptors
+ from the configured bridge authorities when feasible. It will fall back to
+ a direct request if the authority responds with a 404. (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>UseBridges</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ When set, Tor will fetch descriptors for each bridge listed in the "Bridge"
+ config lines, and use these relays as both entry guards and directory
+ guards. (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>UseEntryGuards</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If this option is set to 1, we pick a few long-term entry servers, and try
+ to stick with them. This is desirable because constantly changing servers
+ increases the odds that an adversary who owns some servers will observe a
+ fraction of your paths. (Defaults to 1.)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>NumEntryGuards</strong> <em>NUM</em>
+</dt>
+<dd>
+<p>
+ If UseEntryGuards is set to 1, we will try to pick a total of NUM routers
+ as long-term entries for our circuits. (Defaults to 3.)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>SafeSocks</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ When this option is enabled, Tor will reject application connections that
+ use unsafe variants of the socks protocol&#8201;&#8212;&#8201;ones that only provide an IP
+ address, meaning the application is doing a DNS resolve first.
+ Specifically, these are socks4 and socks5 when not doing remote DNS.
+ (Defaults to 0.)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>TestSocks</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ When this option is enabled, Tor will make a notice-level log entry for
+ each connection to the Socks port indicating whether the request used a
+ safe socks protocol or an unsafe one (see above entry on SafeSocks). This
+ helps to determine whether an application using Tor is possibly leaking
+ DNS requests. (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>WarnUnsafeSocks</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ When this option is enabled, Tor will warn whenever a request is
+ received that only contains an IP address instead of a hostname. Allowing
+ applications to do DNS resolves themselves is usually a bad idea and
+ can leak your location to attackers. (Default: 1)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>VirtualAddrNetwork</strong> <em>Address</em>/<em>bits</em>
+</dt>
+<dd>
+<p>
+ When Tor needs to assign a virtual (unused) address because of a MAPADDRESS
+ command from the controller or the AutomapHostsOnResolve feature, Tor
+ picks an unassigned address from this range. (Default:
+ 127.192.0.0/10)<br />
+<br />
+ When providing proxy server service to a network of computers using a tool
+ like dns-proxy-tor, change this address to "10.192.0.0/10" or
+ "172.16.0.0/12". The default <strong>VirtualAddrNetwork</strong> address range on a
+ properly configured machine will route to the loopback interface. For
+ local use, no change to the default VirtualAddrNetwork setting is needed.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>AllowNonRFC953Hostnames</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ When this option is disabled, Tor blocks hostnames containing illegal
+ characters (like @ and :) rather than sending them to an exit node to be
+ resolved. This helps trap accidental attempts to resolve URLs and so on.
+ (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>AllowDotExit</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If enabled, we convert "www.google.com.foo.exit" addresses on the
+ SocksPort/TransPort/NATDPort into "www.google.com" addresses that exit from
+ the node "foo". Disabled by default since attacking websites and exit
+ relays can use it to manipulate your path selection. (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>FastFirstHopPK</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ When this option is disabled, Tor uses the public key step for the first
+ hop of creating circuits. Skipping it is generally safe since we have
+ already used TLS to authenticate the relay and to establish forward-secure
+ keys. Turning this option off makes circuit building slower.<br />
+<br />
+ Note that Tor will always use the public key step for the first hop if it&#8217;s
+ operating as a relay, and it will never use the public key step if it
+ doesn&#8217;t yet know the onion key of the first hop. (Default: 1)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>TransPort</strong> <em>PORT</em>|<strong>auto</strong>
+</dt>
+<dd>
+<p>
+ If non-zero, enables transparent proxy support on <em>PORT</em> (by convention,
+ 9040). Requires OS support for transparent proxies, such as BSDs' pf or
+ Linux&#8217;s IPTables. If you&#8217;re planning to use Tor as a transparent proxy for
+ a network, you&#8217;ll want to examine and change VirtualAddrNetwork from the
+ default setting. You&#8217;ll also want to set the TransListenAddress option for
+ the network you&#8217;d like to proxy. Set it to "auto" to have Tor pick a
+ port for you. (Default: 0).
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>TransListenAddress</strong> <em>IP</em>[:<em>PORT</em>]
+</dt>
+<dd>
+<p>
+ Bind to this address to listen for transparent proxy connections. (Default:
+ 127.0.0.1). This is useful for exporting a transparent proxy server to an
+ entire network.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>NATDPort</strong> <em>PORT</em>|<strong>auto</strong>
+</dt>
+<dd>
+<p>
+ Allow old versions of ipfw (as included in old versions of FreeBSD, etc.)
+ to send connections through Tor using the NATD protocol. This option is
+ only for people who cannot use TransPort. Set it to "auto" to have Tor
+ pick a port for you. (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>NATDListenAddress</strong> <em>IP</em>[:<em>PORT</em>]
+</dt>
+<dd>
+<p>
+ Bind to this address to listen for NATD connections. (Default: 127.0.0.1).
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>AutomapHostsOnResolve</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ When this option is enabled, and we get a request to resolve an address
+ that ends with one of the suffixes in <strong>AutomapHostsSuffixes</strong>, we map an
+ unused virtual address to that address, and return the new virtual address.
+ This is handy for making ".onion" addresses work with applications that
+ resolve an address and then connect to it. (Default: 0).
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>AutomapHostsSuffixes</strong> <em>SUFFIX</em>,<em>SUFFIX</em>,<em>&#8230;</em>
+</dt>
+<dd>
+<p>
+ A comma-separated list of suffixes to use with <strong>AutomapHostsOnResolve</strong>.
+ The "." suffix is equivalent to "all addresses." (Default: .exit,.onion).
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>DNSPort</strong> <em>PORT</em>|<strong>auto</strong>
+</dt>
+<dd>
+<p>
+ If non-zero, Tor listens for UDP DNS requests on this port and resolves
+ them anonymously. Set it to "auto" to have Tor pick a port for
+ you. (Default: 0).
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>DNSListenAddress</strong> <em>IP</em>[:<em>PORT</em>]
+</dt>
+<dd>
+<p>
+ Bind to this address to listen for DNS connections. (Default: 127.0.0.1).
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ClientDNSRejectInternalAddresses</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If true, Tor does not believe any anonymously retrieved DNS answer that
+ tells it that an address resolves to an internal address (like 127.0.0.1 or
+ 192.168.0.1). This option prevents certain browser-based attacks; don&#8217;t
+ turn it off unless you know what you&#8217;re doing. (Default: 1).
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ClientRejectInternalAddresses</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If true, Tor does not try to fulfill requests to connect to an internal
+ address (like 127.0.0.1 or 192.168.0.1) <em>unless a exit node is
+ specifically requested</em> (for example, via a .exit hostname, or a
+ controller request). (Default: 1).
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>DownloadExtraInfo</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If true, Tor downloads and caches "extra-info" documents. These documents
+ contain information about servers other than the information in their
+ regular router descriptors. Tor does not use this information for anything
+ itself; to save bandwidth, leave this option turned off. (Default: 0).
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>FallbackNetworkstatusFile</strong> <em>FILENAME</em>
+</dt>
+<dd>
+<p>
+ If Tor doesn&#8217;t have a cached networkstatus file, it starts out using this
+ one instead. Even if this file is out of date, Tor can still use it to
+ learn about directory mirrors, so it doesn&#8217;t need to put load on the
+ authorities. (Default: None).
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>WarnPlaintextPorts</strong> <em>port</em>,<em>port</em>,<em>&#8230;</em>
+</dt>
+<dd>
+<p>
+ Tells Tor to issue a warnings whenever the user tries to make an anonymous
+ connection to one of these ports. This option is designed to alert users
+ to services that risk sending passwords in the clear. (Default:
+ 23,109,110,143).
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>RejectPlaintextPorts</strong> <em>port</em>,<em>port</em>,<em>&#8230;</em>
+</dt>
+<dd>
+<p>
+ Like WarnPlaintextPorts, but instead of warning about risky port uses, Tor
+ will instead refuse to make the connection. (Default: None).
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>AllowSingleHopCircuits</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ When this option is set, the attached Tor controller can use relays
+ that have the <strong>AllowSingleHopExits</strong> option turned on to build
+ one-hop Tor connections. (Default: 0)
+</p>
+</dd>
+</dl></div>
+</div>
+<h2 id="_server_options">SERVER OPTIONS</h2>
+<div class="sectionbody">
+<div class="paragraph"><p>The following options are useful only for servers (that is, if ORPort
+is non-zero):</p></div>
+<div class="dlist"><dl>
+<dt class="hdlist1">
+<strong>Address</strong> <em>address</em>
+</dt>
+<dd>
+<p>
+ The IP address or fully qualified domain name of this server (e.g.
+ moria.mit.edu). You can leave this unset, and Tor will guess your IP
+ address. This IP address is the one used to tell clients and other
+ servers where to find your Tor server; it doesn&#8217;t affect the IP that your
+ Tor client binds to. To bind to a different address, use the
+ *ListenAddress and OutboundBindAddress options.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>AllowSingleHopExits</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ This option controls whether clients can use this server as a single hop
+ proxy. If set to 1, clients can use this server as an exit even if it is
+ the only hop in the circuit. Note that most clients will refuse to use
+ servers that set this option, since most clients have
+ ExcludeSingleHopRelays set. (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>AssumeReachable</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ This option is used when bootstrapping a new Tor network. If set to 1,
+ don&#8217;t do self-reachability testing; just upload your server descriptor
+ immediately. If <strong>AuthoritativeDirectory</strong> is also set, this option
+ instructs the dirserver to bypass remote reachability testing too and list
+ all connected servers as running.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>BridgeRelay</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ Sets the relay to act as a "bridge" with respect to relaying connections
+ from bridge users to the Tor network. It mainly causes Tor to publish a
+ server descriptor to the bridge database, rather than publishing a relay
+ descriptor to the public directory authorities.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ContactInfo</strong> <em>email_address</em>
+</dt>
+<dd>
+<p>
+ Administrative contact information for server. This line might get picked
+ up by spam harvesters, so you may want to obscure the fact that it&#8217;s an
+ email address.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ExitPolicy</strong> <em>policy</em>,<em>policy</em>,<em>&#8230;</em>
+</dt>
+<dd>
+<p>
+ Set an exit policy for this server. Each policy is of the form
+ "<strong>accept</strong>|<strong>reject</strong> <em>ADDR</em>[/<em>MASK</em>][:<em>PORT</em>]". If /<em>MASK</em> is
+ omitted then this policy just applies to the host given. Instead of giving
+ a host or network you can also use "*" to denote the universe (0.0.0.0/0).
+ <em>PORT</em> can be a single port number, an interval of ports
+ "<em>FROM_PORT</em>-<em>TO_PORT</em>", or "*". If <em>PORT</em> is omitted, that means
+ "*".<br />
+<br />
+ For example, "accept 18.7.22.69:*,reject 18.0.0.0/8:*,accept *:*" would
+ reject any traffic destined for MIT except for web.mit.edu, and accept
+ anything else.<br />
+<br />
+ To specify all internal and link-local networks (including 0.0.0.0/8,
+ 169.254.0.0/16, 127.0.0.0/8, 192.168.0.0/16, 10.0.0.0/8, and
+ 172.16.0.0/12), you can use the "private" alias instead of an address.
+ These addresses are rejected by default (at the beginning of your exit
+ policy), along with your public IP address, unless you set the
+ ExitPolicyRejectPrivate config option to 0. For example, once you&#8217;ve done
+ that, you could allow HTTP to 127.0.0.1 and block all other connections to
+ internal networks with "accept 127.0.0.1:80,reject private:*", though that
+ may also allow connections to your own computer that are addressed to its
+ public (external) IP address. See RFC 1918 and RFC 3330 for more details
+ about internal and reserved IP address space.<br />
+<br />
+ This directive can be specified multiple times so you don&#8217;t have to put it
+ all on one line.<br />
+<br />
+ Policies are considered first to last, and the first match wins. If you
+ want to _replace_ the default exit policy, end your exit policy with
+ either a reject *:* or an accept *:*. Otherwise, you&#8217;re _augmenting_
+ (prepending to) the default exit policy. The default exit policy is:<br />
+</p>
+<div class="literalblock">
+<div class="content">
+<pre><tt>reject *:25
+reject *:119
+reject *:135-139
+reject *:445
+reject *:563
+reject *:1214
+reject *:4661-4666
+reject *:6346-6429
+reject *:6699
+reject *:6881-6999
+accept *:*</tt></pre>
+</div></div>
+</dd>
+<dt class="hdlist1">
+<strong>ExitPolicyRejectPrivate</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ Reject all private (local) networks, along with your own public IP address,
+ at the beginning of your exit policy. See above entry on ExitPolicy.
+ (Default: 1)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>MaxOnionsPending</strong> <em>NUM</em>
+</dt>
+<dd>
+<p>
+ If you have more than this number of onionskins queued for decrypt, reject
+ new ones. (Default: 100)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>MyFamily</strong> <em>node</em>,<em>node</em>,<em>&#8230;</em>
+</dt>
+<dd>
+<p>
+ Declare that this Tor server is controlled or administered by a group or
+ organization identical or similar to that of the other servers, defined by
+ their identity fingerprints or nicknames. When two servers both declare
+ that they are in the same 'family', Tor clients will not use them in the
+ same circuit. (Each server only needs to list the other servers in its
+ family; it doesn&#8217;t need to list itself, but it won&#8217;t hurt.)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>Nickname</strong> <em>name</em>
+</dt>
+<dd>
+<p>
+ Set the server&#8217;s nickname to 'name'. Nicknames must be between 1 and 19
+ characters inclusive, and must contain only the characters [a-zA-Z0-9].
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>NumCPUs</strong> <em>num</em>
+</dt>
+<dd>
+<p>
+ How many processes to use at once for decrypting onionskins. (Default: 1)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ORPort</strong> <em>PORT</em>|<strong>auto</strong>
+</dt>
+<dd>
+<p>
+ Advertise this port to listen for connections from Tor clients and
+ servers. This option is required to be a Tor server.
+ Set it to "auto" to have Tor pick a port for you. (Default: 0).
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ORListenAddress</strong> <em>IP</em>[:<em>PORT</em>]
+</dt>
+<dd>
+<p>
+ Bind to this IP address to listen for connections from Tor clients and
+ servers. If you specify a port, bind to this port rather than the one
+ specified in ORPort. (Default: 0.0.0.0) This directive can be specified
+ multiple times to bind to multiple addresses/ports.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>PublishServerDescriptor</strong> <strong>0</strong>|<strong>1</strong>|<strong>v1</strong>|<strong>v2</strong>|<strong>v3</strong>|<strong>bridge</strong>,<strong>&#8230;</strong>
+</dt>
+<dd>
+<p>
+ This option specifies which descriptors Tor will publish when acting as
+ a relay. You can
+ choose multiple arguments, separated by commas.
+<br />
+ If this option is set to 0, Tor will not publish its
+ descriptors to any directories. (This is useful if you&#8217;re testing
+ out your server, or if you&#8217;re using a Tor controller that handles directory
+ publishing for you.) Otherwise, Tor will publish its descriptors of all
+ type(s) specified. The default is "1",
+ which means "if running as a server, publish the
+ appropriate descriptors to the authorities".
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ShutdownWaitLength</strong> <em>NUM</em>
+</dt>
+<dd>
+<p>
+ When we get a SIGINT and we&#8217;re a server, we begin shutting down:
+ we close listeners and start refusing new circuits. After <strong>NUM</strong>
+ seconds, we exit. If we get a second SIGINT, we exit immedi-
+ ately. (Default: 30 seconds)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>AccountingMax</strong> <em>N</em> <strong>bytes</strong>|<strong>KB</strong>|<strong>MB</strong>|<strong>GB</strong>|<strong>TB</strong>
+</dt>
+<dd>
+<p>
+ Never send more than the specified number of bytes in a given accounting
+ period, or receive more than that number in the period. For example, with
+ AccountingMax set to 1 GB, a server could send 900 MB and receive 800 MB
+ and continue running. It will only hibernate once one of the two reaches 1
+ GB. When the number of bytes gets low, Tor will stop accepting new
+ connections and circuits. When the number of bytes
+ is exhausted, Tor will hibernate until some
+ time in the next accounting period. To prevent all servers from waking at
+ the same time, Tor will also wait until a random point in each period
+ before waking up. If you have bandwidth cost issues, enabling hibernation
+ is preferable to setting a low bandwidth, since it provides users with a
+ collection of fast servers that are up some of the time, which is more
+ useful than a set of slow servers that are always "available".
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>AccountingStart</strong> <strong>day</strong>|<strong>week</strong>|<strong>month</strong> [<em>day</em>] <em>HH:MM</em>
+</dt>
+<dd>
+<p>
+ Specify how long accounting periods last. If <strong>month</strong> is given, each
+ accounting period runs from the time <em>HH:MM</em> on the <em>dayth</em> day of one
+ month to the same day and time of the next. (The day must be between 1 and
+ 28.) If <strong>week</strong> is given, each accounting period runs from the time <em>HH:MM</em>
+ of the <em>dayth</em> day of one week to the same day and time of the next week,
+ with Monday as day 1 and Sunday as day 7. If <strong>day</strong> is given, each
+ accounting period runs from the time <em>HH:MM</em> each day to the same time on
+ the next day. All times are local, and given in 24-hour time. (Defaults to
+ "month 1 0:00".)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>RefuseUnknownExits</strong> <strong>0</strong>|<strong>1</strong>|<strong>auto</strong>
+</dt>
+<dd>
+<p>
+ Prevent nodes that don&#8217;t appear in the consensus from exiting using this
+ relay. If the option is 1, we always block exit attempts from such
+ nodes; if it&#8217;s 0, we never do, and if the option is "auto", then we do
+ whatever the authorities suggest in the consensus. (Defaults to auto.)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ServerDNSResolvConfFile</strong> <em>filename</em>
+</dt>
+<dd>
+<p>
+ Overrides the default DNS configuration with the configuration in
+ <em>filename</em>. The file format is the same as the standard Unix
+ "<strong>resolv.conf</strong>" file (7). This option, like all other ServerDNS options,
+ only affects name lookups that your server does on behalf of clients.
+ (Defaults to use the system DNS configuration.)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ServerDNSAllowBrokenConfig</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If this option is false, Tor exits immediately if there are problems
+ parsing the system DNS configuration or connecting to nameservers.
+ Otherwise, Tor continues to periodically retry the system nameservers until
+ it eventually succeeds. (Defaults to "1".)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ServerDNSSearchDomains</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If set to 1, then we will search for addresses in the local search domain.
+ For example, if this system is configured to believe it is in
+ "example.com", and a client tries to connect to "www", the client will be
+ connected to "www.example.com". This option only affects name lookups that
+ your server does on behalf of clients. (Defaults to "0".)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ServerDNSDetectHijacking</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ When this option is set to 1, we will test periodically to determine
+ whether our local nameservers have been configured to hijack failing DNS
+ requests (usually to an advertising site). If they are, we will attempt to
+ correct this. This option only affects name lookups that your server does
+ on behalf of clients. (Defaults to "1".)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ServerDNSTestAddresses</strong> <em>address</em>,<em>address</em>,<em>&#8230;</em>
+</dt>
+<dd>
+<p>
+ When we&#8217;re detecting DNS hijacking, make sure that these <em>valid</em> addresses
+ aren&#8217;t getting redirected. If they are, then our DNS is completely useless,
+ and we&#8217;ll reset our exit policy to "reject <strong>:</strong>". This option only affects
+ name lookups that your server does on behalf of clients. (Defaults to
+ "www.google.com, www.mit.edu, www.yahoo.com, www.slashdot.org".)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ServerDNSAllowNonRFC953Hostnames</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ When this option is disabled, Tor does not try to resolve hostnames
+ containing illegal characters (like @ and :) rather than sending them to an
+ exit node to be resolved. This helps trap accidental attempts to resolve
+ URLs and so on. This option only affects name lookups that your server does
+ on behalf of clients. (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>BridgeRecordUsageByCountry</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ When this option is enabled and BridgeRelay is also enabled, and we have
+ GeoIP data, Tor keeps a keep a per-country count of how many client
+ addresses have contacted it so that it can help the bridge authority guess
+ which countries have blocked access to it. (Default: 1)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ServerDNSRandomizeCase</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ When this option is set, Tor sets the case of each character randomly in
+ outgoing DNS requests, and makes sure that the case matches in DNS replies.
+ This so-called "0x20 hack" helps resist some types of DNS poisoning attack.
+ For more information, see "Increased DNS Forgery Resistance through
+ 0x20-Bit Encoding". This option only affects name lookups that your server
+ does on behalf of clients. (Default: 1)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>GeoIPFile</strong> <em>filename</em>
+</dt>
+<dd>
+<p>
+ A filename containing GeoIP data, for use with BridgeRecordUsageByCountry.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>CellStatistics</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ When this option is enabled, Tor writes statistics on the mean time that
+ cells spend in circuit queues to disk every 24 hours. (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>DirReqStatistics</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ When this option is enabled, Tor writes statistics on the number and
+ response time of network status requests to disk every 24 hours.
+ (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>EntryStatistics</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ When this option is enabled, Tor writes statistics on the number of
+ directly connecting clients to disk every 24 hours. (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ExitPortStatistics</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ When this option is enabled, Tor writes statistics on the number of relayed
+ bytes and opened stream per exit port to disk every 24 hours. (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ExtraInfoStatistics</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ When this option is enabled, Tor includes previously gathered statistics in
+ its extra-info documents that it uploads to the directory authorities.
+ (Default: 0)
+</p>
+</dd>
+</dl></div>
+</div>
+<h2 id="_directory_server_options">DIRECTORY SERVER OPTIONS</h2>
+<div class="sectionbody">
+<div class="paragraph"><p>The following options are useful only for directory servers (that is,
+if DirPort is non-zero):</p></div>
+<div class="dlist"><dl>
+<dt class="hdlist1">
+<strong>AuthoritativeDirectory</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ When this option is set to 1, Tor operates as an authoritative directory
+ server. Instead of caching the directory, it generates its own list of
+ good servers, signs it, and sends that to the clients. Unless the clients
+ already have you listed as a trusted directory, you probably do not want
+ to set this option. Please coordinate with the other admins at
+ <a href="mailto:tor-ops@torproject.org">tor-ops@torproject.org</a> if you think you should be a directory.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>DirPortFrontPage</strong> <em>FILENAME</em>
+</dt>
+<dd>
+<p>
+ When this option is set, it takes an HTML file and publishes it as "/" on
+ the DirPort. Now relay operators can provide a disclaimer without needing
+ to set up a separate webserver. There&#8217;s a sample disclaimer in
+ contrib/tor-exit-notice.html.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>V1AuthoritativeDirectory</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ When this option is set in addition to <strong>AuthoritativeDirectory</strong>, Tor
+ generates version 1 directory and running-routers documents (for legacy
+ Tor clients up to 0.1.0.x).
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>V2AuthoritativeDirectory</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ When this option is set in addition to <strong>AuthoritativeDirectory</strong>, Tor
+ generates version 2 network statuses and serves descriptors, etc as
+ described in doc/spec/dir-spec-v2.txt (for Tor clients and servers running
+ 0.1.1.x and 0.1.2.x).
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>V3AuthoritativeDirectory</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ When this option is set in addition to <strong>AuthoritativeDirectory</strong>, Tor
+ generates version 3 network statuses and serves descriptors, etc as
+ described in doc/spec/dir-spec.txt (for Tor clients and servers running at
+ least 0.2.0.x).
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>VersioningAuthoritativeDirectory</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ When this option is set to 1, Tor adds information on which versions of
+ Tor are still believed safe for use to the published directory. Each
+ version 1 authority is automatically a versioning authority; version 2
+ authorities provide this service optionally. See <strong>RecommendedVersions</strong>,
+ <strong>RecommendedClientVersions</strong>, and <strong>RecommendedServerVersions</strong>.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>NamingAuthoritativeDirectory</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ When this option is set to 1, then the server advertises that it has
+ opinions about nickname-to-fingerprint bindings. It will include these
+ opinions in its published network-status pages, by listing servers with
+ the flag "Named" if a correct binding between that nickname and fingerprint
+ has been registered with the dirserver. Naming dirservers will refuse to
+ accept or publish descriptors that contradict a registered binding. See
+ <strong>approved-routers</strong> in the <strong>FILES</strong> section below.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>HSAuthoritativeDir</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ When this option is set in addition to <strong>AuthoritativeDirectory</strong>, Tor also
+ accepts and serves v0 hidden service descriptors,
+ which are produced and used by Tor 0.2.1.x and older. (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>HidServDirectoryV2</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ When this option is set, Tor accepts and serves v2 hidden service
+ descriptors. Setting DirPort is not required for this, because clients
+ connect via the ORPort by default. (Default: 1)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>BridgeAuthoritativeDir</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ When this option is set in addition to <strong>AuthoritativeDirectory</strong>, Tor
+ accepts and serves router descriptors, but it caches and serves the main
+ networkstatus documents rather than generating its own. (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>MinUptimeHidServDirectoryV2</strong> <em>N</em> <strong>seconds</strong>|<strong>minutes</strong>|<strong>hours</strong>|<strong>days</strong>|<strong>weeks</strong>
+</dt>
+<dd>
+<p>
+ Minimum uptime of a v2 hidden service directory to be accepted as such by
+ authoritative directories. (Default: 25 hours)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>DirPort</strong> <em>PORT</em>|<strong>auto</strong>
+</dt>
+<dd>
+<p>
+ If this option is nonzero, advertise the directory service on this port.
+ Set it to "auto" to have Tor pick a port for you. (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>DirListenAddress</strong> <em>IP</em>[:<em>PORT</em>]
+</dt>
+<dd>
+<p>
+ Bind the directory service to this address. If you specify a port, bind to
+ this port rather than the one specified in DirPort. (Default: 0.0.0.0)
+ This directive can be specified multiple times to bind to multiple
+ addresses/ports.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>DirPolicy</strong> <em>policy</em>,<em>policy</em>,<em>&#8230;</em>
+</dt>
+<dd>
+<p>
+ Set an entrance policy for this server, to limit who can connect to the
+ directory ports. The policies have the same form as exit policies above.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>FetchV2Networkstatus</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If set, we try to fetch the (obsolete, unused) version 2 network status
+ consensus documents from the directory authorities. No currently
+ supported Tor version uses them. (Default: 0.)
+</p>
+</dd>
+</dl></div>
+</div>
+<h2 id="_directory_authority_server_options">DIRECTORY AUTHORITY SERVER OPTIONS</h2>
+<div class="sectionbody">
+<div class="dlist"><dl>
+<dt class="hdlist1">
+<strong>RecommendedVersions</strong> <em>STRING</em>
+</dt>
+<dd>
+<p>
+ STRING is a comma-separated list of Tor versions currently believed to be
+ safe. The list is included in each directory, and nodes which pull down the
+ directory learn whether they need to upgrade. This option can appear
+ multiple times: the values from multiple lines are spliced together. When
+ this is set then <strong>VersioningAuthoritativeDirectory</strong> should be set too.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>RecommendedClientVersions</strong> <em>STRING</em>
+</dt>
+<dd>
+<p>
+ STRING is a comma-separated list of Tor versions currently believed to be
+ safe for clients to use. This information is included in version 2
+ directories. If this is not set then the value of <strong>RecommendedVersions</strong>
+ is used. When this is set then <strong>VersioningAuthoritativeDirectory</strong> should
+ be set too.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>RecommendedServerVersions</strong> <em>STRING</em>
+</dt>
+<dd>
+<p>
+ STRING is a comma-separated list of Tor versions currently believed to be
+ safe for servers to use. This information is included in version 2
+ directories. If this is not set then the value of <strong>RecommendedVersions</strong>
+ is used. When this is set then <strong>VersioningAuthoritativeDirectory</strong> should
+ be set too.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>ConsensusParams</strong> <em>STRING</em>
+</dt>
+<dd>
+<p>
+ STRING is a space-separated list of key=value pairs that Tor will include
+ in the "params" line of its networkstatus vote.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>DirAllowPrivateAddresses</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If set to 1, Tor will accept router descriptors with arbitrary "Address"
+ elements. Otherwise, if the address is not an IP address or is a private IP
+ address, it will reject the router descriptor. Defaults to 0.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>AuthDirBadDir</strong> <em>AddressPattern&#8230;</em>
+</dt>
+<dd>
+<p>
+ Authoritative directories only. A set of address patterns for servers that
+ will be listed as bad directories in any network status document this
+ authority publishes, if <strong>AuthDirListBadDirs</strong> is set.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>AuthDirBadExit</strong> <em>AddressPattern&#8230;</em>
+</dt>
+<dd>
+<p>
+ Authoritative directories only. A set of address patterns for servers that
+ will be listed as bad exits in any network status document this authority
+ publishes, if <strong>AuthDirListBadExits</strong> is set.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>AuthDirInvalid</strong> <em>AddressPattern&#8230;</em>
+</dt>
+<dd>
+<p>
+ Authoritative directories only. A set of address patterns for servers that
+ will never be listed as "valid" in any network status document that this
+ authority publishes.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>AuthDirReject</strong> <em>AddressPattern</em>&#8230;
+</dt>
+<dd>
+<p>
+ Authoritative directories only. A set of address patterns for servers that
+ will never be listed at all in any network status document that this
+ authority publishes, or accepted as an OR address in any descriptor
+ submitted for publication by this authority.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>AuthDirListBadDirs</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ Authoritative directories only. If set to 1, this directory has some
+ opinion about which nodes are unsuitable as directory caches. (Do not set
+ this to 1 unless you plan to list non-functioning directories as bad;
+ otherwise, you are effectively voting in favor of every declared
+ directory.)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>AuthDirListBadExits</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ Authoritative directories only. If set to 1, this directory has some
+ opinion about which nodes are unsuitable as exit nodes. (Do not set this to
+ 1 unless you plan to list non-functioning exits as bad; otherwise, you are
+ effectively voting in favor of every declared exit as an exit.)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>AuthDirRejectUnlisted</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ Authoritative directories only. If set to 1, the directory server rejects
+ all uploaded server descriptors that aren&#8217;t explicitly listed in the
+ fingerprints file. This acts as a "panic button" if we get hit with a Sybil
+ attack. (Default: 0)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>AuthDirMaxServersPerAddr</strong> <em>NUM</em>
+</dt>
+<dd>
+<p>
+ Authoritative directories only. The maximum number of servers that we will
+ list as acceptable on a single IP address. Set this to "0" for "no limit".
+ (Default: 2)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>AuthDirMaxServersPerAuthAddr</strong> <em>NUM</em>
+</dt>
+<dd>
+<p>
+ Authoritative directories only. Like AuthDirMaxServersPerAddr, but applies
+ to addresses shared with directory authorities. (Default: 5)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>AuthDirFastGuarantee</strong> <em>N</em> <strong>bytes</strong>|<strong>KB</strong>|<strong>MB</strong>|<strong>GB</strong>
+</dt>
+<dd>
+<p>
+ Authoritative directories only. If non-zero, always vote the
+ Fast flag for any relay advertising this amount of capacity or
+ more. (Default: 20 KB)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>AuthDirGuardBWGuarantee</strong> <em>N</em> <strong>bytes</strong>|<strong>KB</strong>|<strong>MB</strong>|<strong>GB</strong>
+</dt>
+<dd>
+<p>
+ Authoritative directories only. If non-zero, this advertised capacity
+ or more is always sufficient to satisfy the bandwidth requirement
+ for the Guard flag. (Default: 250 KB)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>BridgePassword</strong> <em>Password</em>
+</dt>
+<dd>
+<p>
+ If set, contains an HTTP authenticator that tells a bridge authority to
+ serve all requested bridge information. Used for debugging. (Default:
+ not set.)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>V3AuthVotingInterval</strong> <em>N</em> <strong>minutes</strong>|<strong>hours</strong>
+</dt>
+<dd>
+<p>
+ V3 authoritative directories only. Configures the server&#8217;s preferred voting
+ interval. Note that voting will <em>actually</em> happen at an interval chosen
+ by consensus from all the authorities' preferred intervals. This time
+ SHOULD divide evenly into a day. (Default: 1 hour)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>V3AuthVoteDelay</strong> <em>N</em> <strong>minutes</strong>|<strong>hours</strong>
+</dt>
+<dd>
+<p>
+ V3 authoritative directories only. Configures the server&#8217;s preferred delay
+ between publishing its vote and assuming it has all the votes from all the
+ other authorities. Note that the actual time used is not the server&#8217;s
+ preferred time, but the consensus of all preferences. (Default: 5 minutes.)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>V3AuthDistDelay</strong> <em>N</em> <strong>minutes</strong>|<strong>hours</strong>
+</dt>
+<dd>
+<p>
+ V3 authoritative directories only. Configures the server&#8217;s preferred delay
+ between publishing its consensus and signature and assuming it has all the
+ signatures from all the other authorities. Note that the actual time used
+ is not the server&#8217;s preferred time, but the consensus of all preferences.
+ (Default: 5 minutes.)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>V3AuthNIntervalsValid</strong> <em>NUM</em>
+</dt>
+<dd>
+<p>
+ V3 authoritative directories only. Configures the number of VotingIntervals
+ for which each consensus should be valid for. Choosing high numbers
+ increases network partitioning risks; choosing low numbers increases
+ directory traffic. Note that the actual number of intervals used is not the
+ server&#8217;s preferred number, but the consensus of all preferences. Must be at
+ least 2. (Default: 3.)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>V3BandwidthsFile</strong> <em>FILENAME</em>
+</dt>
+<dd>
+<p>
+ V3 authoritative directories only. Configures the location of the
+ bandiwdth-authority generated file storing information on relays' measured
+ bandwidth capacities. (Default: unset.)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>V3AuthUseLegacyKey</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If set, the directory authority will sign consensuses not only with its
+ own signing key, but also with a "legacy" key and certificate with a
+ different identity. This feature is used to migrate directory authority
+ keys in the event of a compromise. (Default: 0.)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>RephistTrackTime</strong> <em>N</em> <strong>seconds</strong>|<strong>minutes</strong>|<strong>hours</strong>|<strong>days</strong>|<strong>weeks</strong>
+</dt>
+<dd>
+<p>
+ Tells an authority, or other node tracking node reliability and history,
+ that fine-grained information about nodes can be discarded when it hasn&#8217;t
+ changed for a given amount of time. (Default: 24 hours)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>VoteOnHidServDirectoriesV2</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ When this option is set in addition to <strong>AuthoritativeDirectory</strong>, Tor
+ votes on whether to accept relays as hidden service directories.
+ (Default: 1)
+</p>
+</dd>
+</dl></div>
+</div>
+<h2 id="_hidden_service_options">HIDDEN SERVICE OPTIONS</h2>
+<div class="sectionbody">
+<div class="paragraph"><p>The following options are used to configure a hidden service.</p></div>
+<div class="dlist"><dl>
+<dt class="hdlist1">
+<strong>HiddenServiceDir</strong> <em>DIRECTORY</em>
+</dt>
+<dd>
+<p>
+ Store data files for a hidden service in DIRECTORY. Every hidden service
+ must have a separate directory. You may use this option multiple times to
+ specify multiple services. DIRECTORY must be an existing directory.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>HiddenServicePort</strong> <em>VIRTPORT</em> [<em>TARGET</em>]
+</dt>
+<dd>
+<p>
+ Configure a virtual port VIRTPORT for a hidden service. You may use this
+ option multiple times; each time applies to the service using the most
+ recent hiddenservicedir. By default, this option maps the virtual port to
+ the same port on 127.0.0.1. You may override the target port, address, or
+ both by specifying a target of addr, port, or addr:port. You may also have
+ multiple lines with the same VIRTPORT: when a user connects to that
+ VIRTPORT, one of the TARGETs from those lines will be chosen at random.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>PublishHidServDescriptors</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If set to 0, Tor will run any hidden services you configure, but it won&#8217;t
+ advertise them to the rendezvous directory. This option is only useful if
+ you&#8217;re using a Tor controller that handles hidserv publishing for you.
+ (Default: 1)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>HiddenServiceVersion</strong> <em>version</em>,<em>version</em>,<em>&#8230;</em>
+</dt>
+<dd>
+<p>
+ A list of rendezvous service descriptor versions to publish for the hidden
+ service. Currently, only version 2 is supported. (Default: 2)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>HiddenServiceAuthorizeClient</strong> <em>auth-type</em> <em>client-name</em>,<em>client-name</em>,<em>&#8230;</em>
+</dt>
+<dd>
+<p>
+ If configured, the hidden service is accessible for authorized clients
+ only. The auth-type can either be 'basic' for a general-purpose
+ authorization protocol or 'stealth' for a less scalable protocol that also
+ hides service activity from unauthorized clients. Only clients that are
+ listed here are authorized to access the hidden service. Valid client names
+ are 1 to 19 characters long and only use characters in A-Za-z0-9+-_ (no
+ spaces). If this option is set, the hidden service is not accessible for
+ clients without authorization any more. Generated authorization data can be
+ found in the hostname file. Clients need to put this authorization data in
+ their configuration file using <strong>HidServAuth</strong>.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>RendPostPeriod</strong> <em>N</em> <strong>seconds</strong>|<strong>minutes</strong>|<strong>hours</strong>|<strong>days</strong>|<strong>weeks</strong>
+</dt>
+<dd>
+<p>
+ Every time the specified period elapses, Tor uploads any rendezvous
+ service descriptors to the directory servers. This information is also
+ uploaded whenever it changes. (Default: 1 hour)
+</p>
+</dd>
+</dl></div>
+</div>
+<h2 id="_testing_network_options">TESTING NETWORK OPTIONS</h2>
+<div class="sectionbody">
+<div class="paragraph"><p>The following options are used for running a testing Tor network.</p></div>
+<div class="dlist"><dl>
+<dt class="hdlist1">
+<strong>TestingTorNetwork</strong> <strong>0</strong>|<strong>1</strong>
+</dt>
+<dd>
+<p>
+ If set to 1, Tor adjusts default values of the configuration options below,
+ so that it is easier to set up a testing Tor network. May only be set if
+ non-default set of DirServers is set. Cannot be unset while Tor is running.
+ (Default: 0)<br />
+</p>
+<div class="literalblock">
+<div class="content">
+<pre><tt>ServerDNSAllowBrokenConfig 1
+DirAllowPrivateAddresses 1
+EnforceDistinctSubnets 0
+AssumeReachable 1
+AuthDirMaxServersPerAddr 0
+AuthDirMaxServersPerAuthAddr 0
+ClientDNSRejectInternalAddresses 0
+ClientRejectInternalAddresses 0
+ExitPolicyRejectPrivate 0
+V3AuthVotingInterval 5 minutes
+V3AuthVoteDelay 20 seconds
+V3AuthDistDelay 20 seconds
+MinUptimeHidServDirectoryV2 0 seconds
+TestingV3AuthInitialVotingInterval 5 minutes
+TestingV3AuthInitialVoteDelay 20 seconds
+TestingV3AuthInitialDistDelay 20 seconds
+TestingAuthDirTimeToLearnReachability 0 minutes
+TestingEstimatedDescriptorPropagationTime 0 minutes</tt></pre>
+</div></div>
+</dd>
+<dt class="hdlist1">
+<strong>TestingV3AuthInitialVotingInterval</strong> <em>N</em> <strong>minutes</strong>|<strong>hours</strong>
+</dt>
+<dd>
+<p>
+ Like V3AuthVotingInterval, but for initial voting interval before the first
+ consensus has been created. Changing this requires that
+ <strong>TestingTorNetwork</strong> is set. (Default: 30 minutes)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>TestingV3AuthInitialVoteDelay</strong> <em>N</em> <strong>minutes</strong>|<strong>hours</strong>
+</dt>
+<dd>
+<p>
+ Like TestingV3AuthInitialVoteDelay, but for initial voting interval before
+ the first consensus has been created. Changing this requires that
+ <strong>TestingTorNetwork</strong> is set. (Default: 5 minutes)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>TestingV3AuthInitialDistDelay</strong> <em>N</em> <strong>minutes</strong>|<strong>hours</strong>
+</dt>
+<dd>
+<p>
+ Like TestingV3AuthInitialDistDelay, but for initial voting interval before
+ the first consensus has been created. Changing this requires that
+ <strong>TestingTorNetwork</strong> is set. (Default: 5 minutes)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>TestingAuthDirTimeToLearnReachability</strong> <em>N</em> <strong>minutes</strong>|<strong>hours</strong>
+</dt>
+<dd>
+<p>
+ After starting as an authority, do not make claims about whether routers
+ are Running until this much time has passed. Changing this requires
+ that <strong>TestingTorNetwork</strong> is set. (Default: 30 minutes)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>TestingEstimatedDescriptorPropagationTime</strong> <em>N</em> <strong>minutes</strong>|<strong>hours</strong>
+</dt>
+<dd>
+<p>
+ Clients try downloading router descriptors from directory caches after this
+ time. Changing this requires that <strong>TestingTorNetwork</strong> is set. (Default:
+ 10 minutes)
+</p>
+</dd>
+</dl></div>
+</div>
+<h2 id="_signals">SIGNALS</h2>
+<div class="sectionbody">
+<div class="paragraph"><p>Tor catches the following signals:</p></div>
+<div class="dlist"><dl>
+<dt class="hdlist1">
+<strong>SIGTERM</strong>
+</dt>
+<dd>
+<p>
+ Tor will catch this, clean up and sync to disk if necessary, and exit.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>SIGINT</strong>
+</dt>
+<dd>
+<p>
+ Tor clients behave as with SIGTERM; but Tor servers will do a controlled
+ slow shutdown, closing listeners and waiting 30 seconds before exiting.
+ (The delay can be configured with the ShutdownWaitLength config option.)
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>SIGHUP</strong>
+</dt>
+<dd>
+<p>
+ The signal instructs Tor to reload its configuration (including closing and
+ reopening logs), and kill and restart its helper processes if applicable.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>SIGUSR1</strong>
+</dt>
+<dd>
+<p>
+ Log statistics about current connections, past connections, and throughput.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>SIGUSR2</strong>
+</dt>
+<dd>
+<p>
+ Switch all logs to loglevel debug. You can go back to the old loglevels by
+ sending a SIGHUP.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>SIGCHLD</strong>
+</dt>
+<dd>
+<p>
+ Tor receives this signal when one of its helper processes has exited, so it
+ can clean up.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>SIGPIPE</strong>
+</dt>
+<dd>
+<p>
+ Tor catches this signal and ignores it.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>SIGXFSZ</strong>
+</dt>
+<dd>
+<p>
+ If this signal exists on your platform, Tor catches and ignores it.
+</p>
+</dd>
+</dl></div>
+</div>
+<h2 id="_files">FILES</h2>
+<div class="sectionbody">
+<div class="dlist"><dl>
+<dt class="hdlist1">
+<strong>/etc/tor/torrc</strong>
+</dt>
+<dd>
+<p>
+ The configuration file, which contains "option value" pairs.
+</p>
+</dd>
+<dt class="hdlist1">
+<strong>/var/lib/tor/</strong>
+</dt>
+<dd>
+<p>
+ The tor process stores keys and other data here.
+</p>
+</dd>
+<dt class="hdlist1">
+<em>DataDirectory</em><strong>/cached-status/</strong>
+</dt>
+<dd>
+<p>
+ The most recently downloaded network status document for each authority.
+ Each file holds one such document; the filenames are the hexadecimal
+ identity key fingerprints of the directory authorities.
+</p>
+</dd>
+<dt class="hdlist1">
+<em>DataDirectory</em><strong>/cached-descriptors</strong> and <strong>cached-descriptors.new</strong>
+</dt>
+<dd>
+<p>
+ These files hold downloaded router statuses. Some routers may appear more
+ than once; if so, the most recently published descriptor is used. Lines
+ beginning with @-signs are annotations that contain more information about
+ a given router. The ".new" file is an append-only journal; when it gets
+ too large, all entries are merged into a new cached-descriptors file.
+</p>
+</dd>
+<dt class="hdlist1">
+<em>DataDirectory</em><strong>/cached-routers</strong> and <strong>cached-routers.new</strong>
+</dt>
+<dd>
+<p>
+ Obsolete versions of cached-descriptors and cached-descriptors.new. When
+ Tor can&#8217;t find the newer files, it looks here instead.
+</p>
+</dd>
+<dt class="hdlist1">
+<em>DataDirectory</em><strong>/state</strong>
+</dt>
+<dd>
+<p>
+ A set of persistent key-value mappings. These are documented in
+ the file. These include:
+</p>
+<div class="ulist"><ul>
+<li>
+<p>
+The current entry guards and their status.
+</p>
+</li>
+<li>
+<p>
+The current bandwidth accounting values (unused so far; see
+ below).
+</p>
+</li>
+<li>
+<p>
+When the file was last written
+</p>
+</li>
+<li>
+<p>
+What version of Tor generated the state file
+</p>
+</li>
+<li>
+<p>
+A short history of bandwidth usage, as produced in the router
+ descriptors.
+</p>
+</li>
+</ul></div>
+</dd>
+<dt class="hdlist1">
+<em>DataDirectory</em><strong>/bw_accounting</strong>
+</dt>
+<dd>
+<p>
+ Used to track bandwidth accounting values (when the current period starts
+ and ends; how much has been read and written so far this period). This file
+ is obsolete, and the data is now stored in the 'state' file as well. Only
+ used when bandwidth accounting is enabled.
+</p>
+</dd>
+<dt class="hdlist1">
+<em>DataDirectory</em><strong>/control_auth_cookie</strong>
+</dt>
+<dd>
+<p>
+ Used for cookie authentication with the controller. Location can be
+ overridden by the CookieAuthFile config option. Regenerated on startup. See
+ control-spec.txt for details. Only used when cookie authentication is
+ enabled.
+</p>
+</dd>
+<dt class="hdlist1">
+<em>DataDirectory</em><strong>/keys/</strong>*
+</dt>
+<dd>
+<p>
+ Only used by servers. Holds identity keys and onion keys.
+</p>
+</dd>
+<dt class="hdlist1">
+<em>DataDirectory</em><strong>/fingerprint</strong>
+</dt>
+<dd>
+<p>
+ Only used by servers. Holds the fingerprint of the server&#8217;s identity key.
+</p>
+</dd>
+<dt class="hdlist1">
+<em>DataDirectory</em><strong>/approved-routers</strong>
+</dt>
+<dd>
+<p>
+ Only for naming authoritative directory servers (see
+ <strong>NamingAuthoritativeDirectory</strong>). This file lists nickname to identity
+ bindings. Each line lists a nickname and a fingerprint separated by
+ whitespace. See your <strong>fingerprint</strong> file in the <em>DataDirectory</em> for an
+ example line. If the nickname is <strong>!reject</strong> then descriptors from the
+ given identity (fingerprint) are rejected by this server. If it is
+ <strong>!invalid</strong> then descriptors are accepted but marked in the directory as
+ not valid, that is, not recommended.
+</p>
+</dd>
+<dt class="hdlist1">
+<em>DataDirectory</em><strong>/router-stability</strong>
+</dt>
+<dd>
+<p>
+ Only used by authoritative directory servers. Tracks measurements for
+ router mean-time-between-failures so that authorities have a good idea of
+ how to set their Stable flags.
+</p>
+</dd>
+<dt class="hdlist1">
+<em>HiddenServiceDirectory</em><strong>/hostname</strong>
+</dt>
+<dd>
+<p>
+ The &lt;base32-encoded-fingerprint&gt;.onion domain name for this hidden service.
+ If the hidden service is restricted to authorized clients only, this file
+ also contains authorization data for all clients.
+</p>
+</dd>
+<dt class="hdlist1">
+<em>HiddenServiceDirectory</em><strong>/private_key</strong>
+</dt>
+<dd>
+<p>
+ The private key for this hidden service.
+</p>
+</dd>
+<dt class="hdlist1">
+<em>HiddenServiceDirectory</em><strong>/client_keys</strong>
+</dt>
+<dd>
+<p>
+ Authorization data for a hidden service that is only accessible by
+ authorized clients.
+</p>
+</dd>
+</dl></div>
+</div>
+<h2 id="_see_also">SEE ALSO</h2>
+<div class="sectionbody">
+<div class="paragraph"><p><strong>privoxy</strong>(1), <strong>tsocks</strong>(1), <strong>torify</strong>(1)<br /></p></div>
+<div class="paragraph"><p><strong>https://www.torproject.org/</strong></p></div>
+</div>
+<h2 id="_bugs">BUGS</h2>
+<div class="sectionbody">
+<div class="paragraph"><p>Plenty, probably. Tor is still in development. Please report them.</p></div>
+</div>
+<h2 id="_authors">AUTHORS</h2>
+<div class="sectionbody">
+<div class="paragraph"><p>Roger Dingledine [arma at mit.edu], Nick Mathewson [nickm at alum.mit.edu].</p></div>
+</div>
+<div id="footer">
+<div id="footer-text">
+Last updated 2011-12-15 11:28:37 EDT
+</div>
+</div>
+</body>
+</html>
diff --git a/puppet/modules/tor/manifests/arm.pp b/puppet/modules/tor/manifests/arm.pp
new file mode 100644
index 00000000..44ddcbbf
--- /dev/null
+++ b/puppet/modules/tor/manifests/arm.pp
@@ -0,0 +1,9 @@
+# manage tor-arm
+class tor::arm (
+ $ensure_version = 'installed'
+){
+ include ::tor
+ package{'tor-arm':
+ ensure => $ensure_version,
+ }
+}
diff --git a/puppet/modules/tor/manifests/base.pp b/puppet/modules/tor/manifests/base.pp
new file mode 100644
index 00000000..b98451be
--- /dev/null
+++ b/puppet/modules/tor/manifests/base.pp
@@ -0,0 +1,14 @@
+# basic management of resources for tor
+class tor::base {
+ package { [ 'tor', 'tor-geoipdb' ]:
+ ensure => $tor::ensure_version,
+ }
+
+ service { 'tor':
+ ensure => running,
+ enable => true,
+ hasrestart => true,
+ hasstatus => true,
+ require => Package['tor'],
+ }
+}
diff --git a/puppet/modules/tor/manifests/compact.pp b/puppet/modules/tor/manifests/compact.pp
new file mode 100644
index 00000000..c0f59199
--- /dev/null
+++ b/puppet/modules/tor/manifests/compact.pp
@@ -0,0 +1,7 @@
+# manage a complete tor
+# installation with all the basics
+class tor::compact {
+ include ::tor
+ include tor::polipo
+ include tor::torsocks
+}
diff --git a/puppet/modules/tor/manifests/daemon.pp b/puppet/modules/tor/manifests/daemon.pp
new file mode 100644
index 00000000..2522b2cc
--- /dev/null
+++ b/puppet/modules/tor/manifests/daemon.pp
@@ -0,0 +1,22 @@
+# manage a snippet based tor installation
+class tor::daemon (
+ $ensure_version = 'installed',
+ $use_munin = false,
+ $data_dir = '/var/lib/tor',
+ $config_file = '/etc/tor/torrc',
+ $use_bridges = 0,
+ $automap_hosts_on_resolve = 0,
+ $log_rules = [ 'notice file /var/log/tor/notices.log' ],
+ $safe_logging = 1,
+) {
+
+ class{'tor':
+ ensure_version => $ensure_version,
+ }
+
+ include tor::daemon::base
+
+ if $use_munin {
+ include tor::munin
+ }
+}
diff --git a/puppet/modules/tor/manifests/daemon/base.pp b/puppet/modules/tor/manifests/daemon/base.pp
new file mode 100644
index 00000000..63d7bc4d
--- /dev/null
+++ b/puppet/modules/tor/manifests/daemon/base.pp
@@ -0,0 +1,77 @@
+# extend basic tor things with a snippet based daemon configuration
+class tor::daemon::base inherits tor::base {
+ # packages, user, group
+ Service['tor'] {
+ subscribe => File[$tor::daemon::config_file],
+ }
+
+ Package[ 'tor' ] {
+ require => File[$tor::daemon::data_dir],
+ }
+
+ group { 'debian-tor':
+ ensure => present,
+ allowdupe => false,
+ }
+
+ user { 'debian-tor':
+ ensure => present,
+ allowdupe => false,
+ comment => 'tor user,,,',
+ home => $tor::daemon::data_dir,
+ shell => '/bin/false',
+ gid => 'debian-tor',
+ require => Group['debian-tor'],
+ }
+
+ # directories
+ file { $tor::daemon::data_dir:
+ ensure => directory,
+ mode => '0700',
+ owner => 'debian-tor',
+ group => 'debian-tor',
+ require => User['debian-tor'],
+ }
+
+ file { '/etc/tor':
+ ensure => directory,
+ mode => '0755',
+ owner => 'debian-tor',
+ group => 'debian-tor',
+ require => User['debian-tor'],
+ }
+
+ file { '/var/lib/puppet/modules/tor':
+ ensure => absent,
+ recurse => true,
+ force => true,
+ }
+
+ # tor configuration file
+ concat { $tor::daemon::config_file:
+ mode => '0600',
+ owner => 'debian-tor',
+ group => 'debian-tor',
+ }
+
+ # config file headers
+ concat::fragment { '00.header':
+ ensure => present,
+ content => template('tor/torrc.header.erb'),
+ owner => 'debian-tor',
+ group => 'debian-tor',
+ mode => '0644',
+ order => 00,
+ target => $tor::daemon::config_file,
+ }
+
+ # global configurations
+ concat::fragment { '01.global':
+ content => template('tor/torrc.global.erb'),
+ owner => 'debian-tor',
+ group => 'debian-tor',
+ mode => '0644',
+ order => 01,
+ target => $tor::daemon::config_file,
+ }
+}
diff --git a/puppet/modules/tor/manifests/daemon/bridge.pp b/puppet/modules/tor/manifests/daemon/bridge.pp
new file mode 100644
index 00000000..063f5656
--- /dev/null
+++ b/puppet/modules/tor/manifests/daemon/bridge.pp
@@ -0,0 +1,18 @@
+# Bridge definition
+define tor::daemon::bridge(
+ $ip,
+ $port,
+ $fingerprint = false,
+ $ensure = present ) {
+
+ concat::fragment { "10.bridge.${name}":
+ ensure => $ensure,
+ content => template('tor/torrc.bridge.erb'),
+ owner => 'debian-tor',
+ group => 'debian-tor',
+ mode => '0644',
+ order => 10,
+ target => $tor::daemon::config_file,
+ }
+}
+
diff --git a/puppet/modules/tor/manifests/daemon/control.pp b/puppet/modules/tor/manifests/daemon/control.pp
new file mode 100644
index 00000000..01726562
--- /dev/null
+++ b/puppet/modules/tor/manifests/daemon/control.pp
@@ -0,0 +1,27 @@
+# control definition
+define tor::daemon::control(
+ $port = 0,
+ $hashed_control_password = '',
+ $cookie_authentication = 0,
+ $cookie_auth_file = '',
+ $cookie_auth_file_group_readable = '',
+ $ensure = present ) {
+
+ if $cookie_authentication == '0' and $hashed_control_password == '' and $ensure != 'absent' {
+ fail('You need to define the tor control password')
+ }
+
+ if $cookie_authentication == 0 and ($cookie_auth_file != '' or $cookie_auth_file_group_readable != '') {
+ notice('You set a tor cookie authentication option, but do not have cookie_authentication on')
+ }
+
+ concat::fragment { '04.control':
+ ensure => $ensure,
+ content => template('tor/torrc.control.erb'),
+ owner => 'debian-tor',
+ group => 'debian-tor',
+ mode => '0600',
+ order => 04,
+ target => $tor::daemon::config_file,
+ }
+}
diff --git a/puppet/modules/tor/manifests/daemon/directory.pp b/puppet/modules/tor/manifests/daemon/directory.pp
new file mode 100644
index 00000000..d877a861
--- /dev/null
+++ b/puppet/modules/tor/manifests/daemon/directory.pp
@@ -0,0 +1,27 @@
+# directory advertising
+define tor::daemon::directory (
+ $port = 0,
+ $listen_addresses = [],
+ $port_front_page = '/etc/tor/tor-exit-notice.html',
+ $ensure = present ) {
+
+ concat::fragment { '06.directory':
+ ensure => $ensure,
+ content => template('tor/torrc.directory.erb'),
+ owner => 'debian-tor',
+ group => 'debian-tor',
+ mode => '0644',
+ order => 06,
+ target => $tor::daemon::config_file,
+ }
+
+ file { '/etc/tor/tor-exit-notice.html':
+ ensure => $ensure,
+ source => 'puppet:///modules/tor/tor-exit-notice.html',
+ require => File['/etc/tor'],
+ owner => 'debian-tor',
+ group => 'debian-tor',
+ mode => '0644',
+ }
+}
+
diff --git a/puppet/modules/tor/manifests/daemon/dns.pp b/puppet/modules/tor/manifests/daemon/dns.pp
new file mode 100644
index 00000000..4677f24d
--- /dev/null
+++ b/puppet/modules/tor/manifests/daemon/dns.pp
@@ -0,0 +1,17 @@
+# DNS definition
+define tor::daemon::dns(
+ $port = 0,
+ $listen_addresses = [],
+ $ensure = present ) {
+
+ concat::fragment { "08.dns.${name}":
+ ensure => $ensure,
+ content => template('tor/torrc.dns.erb'),
+ owner => 'debian-tor',
+ group => 'debian-tor',
+ mode => '0644',
+ order => '08',
+ target => $tor::daemon::config_file,
+ }
+}
+
diff --git a/puppet/modules/tor/manifests/daemon/exit_policy.pp b/puppet/modules/tor/manifests/daemon/exit_policy.pp
new file mode 100644
index 00000000..f459ece7
--- /dev/null
+++ b/puppet/modules/tor/manifests/daemon/exit_policy.pp
@@ -0,0 +1,18 @@
+# exit policies
+define tor::daemon::exit_policy(
+ $accept = [],
+ $reject = [],
+ $reject_private = 1,
+ $ensure = present ) {
+
+ concat::fragment { "07.exit_policy.${name}":
+ ensure => $ensure,
+ content => template('tor/torrc.exit_policy.erb'),
+ owner => 'debian-tor',
+ group => 'debian-tor',
+ mode => '0644',
+ order => 07,
+ target => $tor::daemon::config_file,
+ }
+}
+
diff --git a/puppet/modules/tor/manifests/daemon/hidden_service.pp b/puppet/modules/tor/manifests/daemon/hidden_service.pp
new file mode 100644
index 00000000..c8272116
--- /dev/null
+++ b/puppet/modules/tor/manifests/daemon/hidden_service.pp
@@ -0,0 +1,17 @@
+# hidden services definition
+define tor::daemon::hidden_service(
+ $ports = [],
+ $data_dir = $tor::daemon::data_dir,
+ $ensure = present ) {
+
+ concat::fragment { "05.hidden_service.${name}":
+ ensure => $ensure,
+ content => template('tor/torrc.hidden_service.erb'),
+ owner => 'debian-tor',
+ group => 'debian-tor',
+ mode => '0644',
+ order => 05,
+ target => $tor::daemon::config_file,
+ }
+}
+
diff --git a/puppet/modules/tor/manifests/daemon/map_address.pp b/puppet/modules/tor/manifests/daemon/map_address.pp
new file mode 100644
index 00000000..270eac21
--- /dev/null
+++ b/puppet/modules/tor/manifests/daemon/map_address.pp
@@ -0,0 +1,17 @@
+# map address definition
+define tor::daemon::map_address(
+ $address = '',
+ $newaddress = '',
+ $ensure = 'present') {
+
+ concat::fragment { "08.map_address.${name}":
+ ensure => $ensure,
+ content => template('tor/torrc.map_address.erb'),
+ owner => 'debian-tor',
+ group => 'debian-tor',
+ mode => '0644',
+ order => '08',
+ target => $tor::daemon::config_file,
+ }
+}
+
diff --git a/puppet/modules/tor/manifests/daemon/relay.pp b/puppet/modules/tor/manifests/daemon/relay.pp
new file mode 100644
index 00000000..ff528937
--- /dev/null
+++ b/puppet/modules/tor/manifests/daemon/relay.pp
@@ -0,0 +1,42 @@
+# relay definition
+define tor::daemon::relay(
+ $port = 0,
+ $listen_addresses = [],
+ $outbound_bindaddresses = [],
+ $portforwarding = 0,
+ # KB/s, defaulting to using tor's default: 5120KB/s
+ $bandwidth_rate = '',
+ # KB/s, defaulting to using tor's default: 10240KB/s
+ $bandwidth_burst = '',
+ # KB/s, 0 for no limit
+ $relay_bandwidth_rate = 0,
+ # KB/s, 0 for no limit
+ $relay_bandwidth_burst = 0,
+ # GB, 0 for no limit
+ $accounting_max = 0,
+ $accounting_start = [],
+ $contact_info = '',
+ # TODO: autofill with other relays
+ $my_family = '',
+ $address = "tor.${::domain}",
+ $bridge_relay = 0,
+ $ensure = present ) {
+
+ $nickname = $name
+
+ if $outbound_bindaddresses == [] {
+ $real_outbound_bindaddresses = []
+ } else {
+ $real_outbound_bindaddresses = $outbound_bindaddresses
+ }
+
+ concat::fragment { '03.relay':
+ ensure => $ensure,
+ content => template('tor/torrc.relay.erb'),
+ owner => 'debian-tor',
+ group => 'debian-tor',
+ mode => '0644',
+ order => 03,
+ target => $tor::daemon::config_file,
+ }
+}
diff --git a/puppet/modules/tor/manifests/daemon/snippet.pp b/puppet/modules/tor/manifests/daemon/snippet.pp
new file mode 100644
index 00000000..b9089b40
--- /dev/null
+++ b/puppet/modules/tor/manifests/daemon/snippet.pp
@@ -0,0 +1,16 @@
+# Arbitrary torrc snippet definition
+define tor::daemon::snippet(
+ $content = '',
+ $ensure = present ) {
+
+ concat::fragment { "99.snippet.${name}":
+ ensure => $ensure,
+ content => $content,
+ owner => 'debian-tor',
+ group => 'debian-tor',
+ mode => '0644',
+ order => 99,
+ target => $tor::daemon::config_file,
+ }
+}
+
diff --git a/puppet/modules/tor/manifests/daemon/socks.pp b/puppet/modules/tor/manifests/daemon/socks.pp
new file mode 100644
index 00000000..910461c9
--- /dev/null
+++ b/puppet/modules/tor/manifests/daemon/socks.pp
@@ -0,0 +1,15 @@
+# socks definition
+define tor::daemon::socks(
+ $port = 0,
+ $listen_addresses = [],
+ $policies = [] ) {
+
+ concat::fragment { '02.socks':
+ content => template('tor/torrc.socks.erb'),
+ owner => 'debian-tor',
+ group => 'debian-tor',
+ mode => '0644',
+ order => 02,
+ target => $tor::daemon::config_file,
+ }
+}
diff --git a/puppet/modules/tor/manifests/daemon/transparent.pp b/puppet/modules/tor/manifests/daemon/transparent.pp
new file mode 100644
index 00000000..65d744f4
--- /dev/null
+++ b/puppet/modules/tor/manifests/daemon/transparent.pp
@@ -0,0 +1,17 @@
+# Transparent proxy definition
+define tor::daemon::transparent(
+ $port = 0,
+ $listen_addresses = [],
+ $ensure = present ) {
+
+ concat::fragment { "09.transparent.${name}":
+ ensure => $ensure,
+ content => template('tor/torrc.transparent.erb'),
+ owner => 'debian-tor',
+ group => 'debian-tor',
+ mode => '0644',
+ order => '09',
+ target => $tor::daemon::config_file,
+ }
+}
+
diff --git a/puppet/modules/tor/manifests/init.pp b/puppet/modules/tor/manifests/init.pp
new file mode 100644
index 00000000..9c19c648
--- /dev/null
+++ b/puppet/modules/tor/manifests/init.pp
@@ -0,0 +1,6 @@
+# manage a basic tor installation
+class tor (
+ $ensure_version = 'installed'
+){
+ include tor::base
+}
diff --git a/puppet/modules/tor/manifests/munin.pp b/puppet/modules/tor/manifests/munin.pp
new file mode 100644
index 00000000..4412337a
--- /dev/null
+++ b/puppet/modules/tor/manifests/munin.pp
@@ -0,0 +1,21 @@
+# munin plugins for puppet
+class tor::munin {
+ tor::daemon::control{
+ 'control_port_for_munin':
+ port => 19051,
+ cookie_authentication => 1,
+ cookie_auth_file => '/var/run/tor/control.authcookie',
+ }
+
+ Munin::Plugin::Deploy {
+ config => "user debian-tor\n env.cookiefile /var/run/tor/control.authcookie\n env.port 19051"
+ }
+ munin::plugin::deploy {
+ 'tor_connections':
+ source => 'tor/munin/tor_connections';
+ 'tor_routers':
+ source => 'tor/munin/tor_routers';
+ 'tor_traffic':
+ source => 'tor/munin/tor_traffic';
+ }
+}
diff --git a/puppet/modules/tor/manifests/polipo.pp b/puppet/modules/tor/manifests/polipo.pp
new file mode 100644
index 00000000..73dc2262
--- /dev/null
+++ b/puppet/modules/tor/manifests/polipo.pp
@@ -0,0 +1,9 @@
+# manage the polipo proxy service
+class tor::polipo {
+ include ::tor
+
+ case $::operatingsystem {
+ 'debian': { include tor::polipo::debian }
+ default: { include tor::polipo::base }
+ }
+}
diff --git a/puppet/modules/tor/manifests/polipo/base.pp b/puppet/modules/tor/manifests/polipo/base.pp
new file mode 100644
index 00000000..df2d6ea6
--- /dev/null
+++ b/puppet/modules/tor/manifests/polipo/base.pp
@@ -0,0 +1,22 @@
+# manage polipo resources
+class tor::polipo::base {
+ package{'polipo':
+ ensure => present,
+ }
+
+ file { '/etc/polipo/config':
+ ensure => present,
+ owner => root,
+ group => root,
+ mode => '0644',
+ source => 'puppet:///modules/tor/polipo/polipo.conf',
+ require => Package['polipo'],
+ notify => Service['polipo'],
+ }
+
+ service { 'polipo':
+ ensure => running,
+ enable => true,
+ require => [ Package['polipo'], Service['tor'] ],
+ }
+}
diff --git a/puppet/modules/tor/manifests/polipo/debian.pp b/puppet/modules/tor/manifests/polipo/debian.pp
new file mode 100644
index 00000000..607b3617
--- /dev/null
+++ b/puppet/modules/tor/manifests/polipo/debian.pp
@@ -0,0 +1,7 @@
+# manage polipo on debian
+class tor::polipo::debian inherits tor::polipo::base {
+ Service['polipo'] {
+ hasstatus => false,
+ pattern => '/usr/bin/polipo',
+ }
+}
diff --git a/puppet/modules/tor/manifests/repo.pp b/puppet/modules/tor/manifests/repo.pp
new file mode 100644
index 00000000..f6255995
--- /dev/null
+++ b/puppet/modules/tor/manifests/repo.pp
@@ -0,0 +1,16 @@
+class tor::repo (
+ $ensure = present,
+ $source_name = 'torproject.org',
+ $include_src = false,
+) {
+ case $::osfamily {
+ 'Debian': {
+ $key = '886DDD89'
+ $location = 'https://deb.torproject.org/torproject.org/'
+ class { 'tor::repo::debian': }
+ }
+ default: {
+ fail("Unsupported managed repository for osfamily: ${::osfamily}, operatingsystem: ${::operatingsystem}, module ${module_name} currently only supports managing repos for osfamily Debian and Ubuntu")
+ }
+ }
+}
diff --git a/puppet/modules/tor/manifests/repo/debian.pp b/puppet/modules/tor/manifests/repo/debian.pp
new file mode 100644
index 00000000..174c3310
--- /dev/null
+++ b/puppet/modules/tor/manifests/repo/debian.pp
@@ -0,0 +1,9 @@
+# PRIVATE CLASS: do not use directly
+class tor::repo::debian inherits tor::repo {
+ apt::source { $source_name:
+ ensure => $::tor::repo::ensure,
+ location => $::tor::repo::location,
+ key => $::tor::repo::key,
+ include_src => $::tor::repo::include_src,
+ }
+}
diff --git a/puppet/modules/tor/manifests/torsocks.pp b/puppet/modules/tor/manifests/torsocks.pp
new file mode 100644
index 00000000..e9fc75b2
--- /dev/null
+++ b/puppet/modules/tor/manifests/torsocks.pp
@@ -0,0 +1,9 @@
+# manage torsocks
+class tor::torsocks (
+ $ensure_version = 'installed'
+){
+ include ::tor
+ package{'torsocks':
+ ensure => $ensure_version,
+ }
+}
diff --git a/puppet/modules/tor/templates/torrc.bridge.erb b/puppet/modules/tor/templates/torrc.bridge.erb
new file mode 100644
index 00000000..559ce5df
--- /dev/null
+++ b/puppet/modules/tor/templates/torrc.bridge.erb
@@ -0,0 +1,3 @@
+# Bridge <%= @name %>
+Bridge <%= @ip %>:<%= @port %><% if @fingerprint -%> <%= @fingerprint%><% end -%>
+
diff --git a/puppet/modules/tor/templates/torrc.control.erb b/puppet/modules/tor/templates/torrc.control.erb
new file mode 100644
index 00000000..0b68faff
--- /dev/null
+++ b/puppet/modules/tor/templates/torrc.control.erb
@@ -0,0 +1,16 @@
+# tor controller
+<% if @port != '0' -%>
+ControlPort <%= @port %>
+<% if @cookie_authentication != '0' -%>
+CookieAuthentication 1
+<% if @cookie_auth_file != '' -%>
+CookieAuthFile <%= @cookie_auth_file %>
+<% end -%>
+<% if @cookie_auth_file_group_readable != '' -%>
+CookieAuthFileGroupReadable <%= @cookie_auth_file_group_readable %>
+<% end -%>
+<% else -%>
+HashedControlPassword <%= @hashed_control_password %>
+<% end -%>
+<% end -%>
+
diff --git a/puppet/modules/tor/templates/torrc.directory.erb b/puppet/modules/tor/templates/torrc.directory.erb
new file mode 100644
index 00000000..1af9f40f
--- /dev/null
+++ b/puppet/modules/tor/templates/torrc.directory.erb
@@ -0,0 +1,11 @@
+# directory listing
+<% if port != '0' -%>
+DirPort <%= @port %>
+<% end -%>
+<% listen_addresses.each do |listen_address| -%>
+DirListenAddress <%= listen_address %>
+<% end -%>
+<% if @port_front_page != '' -%>
+DirPortFrontPage <%= port_front_page %>
+<%- end -%>
+
diff --git a/puppet/modules/tor/templates/torrc.dns.erb b/puppet/modules/tor/templates/torrc.dns.erb
new file mode 100644
index 00000000..57cf46d9
--- /dev/null
+++ b/puppet/modules/tor/templates/torrc.dns.erb
@@ -0,0 +1,5 @@
+# DNS
+DNSPort <%= @port %>
+<% @listen_addresses.each do |listen_address| -%>
+DNSListenAddress <%= listen_address %>
+<% end -%>
diff --git a/puppet/modules/tor/templates/torrc.exit_policy.erb b/puppet/modules/tor/templates/torrc.exit_policy.erb
new file mode 100644
index 00000000..a30d43b8
--- /dev/null
+++ b/puppet/modules/tor/templates/torrc.exit_policy.erb
@@ -0,0 +1,11 @@
+# exit policies: <%= @name %>
+<% if @reject_private != '1' -%>
+ExitPolicyRejectPrivate <%= @reject_private %>
+<% end -%>
+<% @accept.each do |policy| -%>
+ExitPolicy accept <%= policy %>
+<% end -%>
+<% @reject.each do |policy| -%>
+ExitPolicy reject <%= policy %>
+<% end -%>
+
diff --git a/puppet/modules/tor/templates/torrc.global.erb b/puppet/modules/tor/templates/torrc.global.erb
new file mode 100644
index 00000000..f577673d
--- /dev/null
+++ b/puppet/modules/tor/templates/torrc.global.erb
@@ -0,0 +1,24 @@
+# runtime
+RunAsDaemon 1
+<% if (v=scope.lookupvar('tor::daemon::data_dir')) != '/var/lib/tor' -%>
+DataDirectory <%= v %>
+<% end -%>
+
+# log
+<% if (rules=scope.lookupvar('tor::daemon::log_rules')).empty? -%>
+Log notice syslog
+<% else -%>
+<% rules.each do |log_rule| -%>
+Log <%= log_rule %>
+<% end -%>
+<% end -%>
+<%- if @safe_logging != 1 then -%>
+SafeLogging <%= @safe_logging %>
+<%- end -%>
+
+<% if (v=scope.lookupvar('tor::daemon::automap_hosts_on_resolve')) != '0' -%>
+AutomapHostsOnResolve <%= v %>
+<% end -%>
+<% if (v=scope.lookupvar('tor::daemon::use_bridges')) != '0' -%>
+UseBridges <%= v %>
+<%- end -%>
diff --git a/puppet/modules/tor/templates/torrc.header.erb b/puppet/modules/tor/templates/torrc.header.erb
new file mode 100644
index 00000000..79d6da9d
--- /dev/null
+++ b/puppet/modules/tor/templates/torrc.header.erb
@@ -0,0 +1,2 @@
+# This file is managed by puppet.
+
diff --git a/puppet/modules/tor/templates/torrc.hidden_service.erb b/puppet/modules/tor/templates/torrc.hidden_service.erb
new file mode 100644
index 00000000..4dec0b25
--- /dev/null
+++ b/puppet/modules/tor/templates/torrc.hidden_service.erb
@@ -0,0 +1,6 @@
+# hidden service <%= @name %>
+HiddenServiceDir <%= @data_dir %>/<%= @name %>
+<% @ports.each do |port| -%>
+HiddenServicePort <%= port %>
+<% end -%>
+
diff --git a/puppet/modules/tor/templates/torrc.map_address.erb b/puppet/modules/tor/templates/torrc.map_address.erb
new file mode 100644
index 00000000..ef4f2683
--- /dev/null
+++ b/puppet/modules/tor/templates/torrc.map_address.erb
@@ -0,0 +1,3 @@
+# map address <%= @name %>
+MapAddress <%= @address %> <%= @newaddress %>
+
diff --git a/puppet/modules/tor/templates/torrc.relay.erb b/puppet/modules/tor/templates/torrc.relay.erb
new file mode 100644
index 00000000..a286459f
--- /dev/null
+++ b/puppet/modules/tor/templates/torrc.relay.erb
@@ -0,0 +1,46 @@
+# relay
+<% if @port != 0 -%>
+ORPort <%= @port %>
+<% @listen_addresses.each do |listen_address| -%>
+ORListenAddress <%= @listen_address %>
+<% end -%>
+<% @real_outbound_bindaddresses.each do |outbound_bindaddress| -%>
+OutboundBindAddress <%= @outbound_bindaddress %>
+<% end -%>
+<% if @nickname != '' -%>
+Nickname <%= @nickname %>
+<% end -%>
+<% if @address != '' -%>
+Address <%= @address %>
+<% end -%>
+<% if @portforwarding != '0' -%>
+PortForwarding <%= @portforwarding %>
+<% end -%>
+<% if @bandwidth_rate != '' -%>
+BandwidthRate <%= @bandwidth_rate %> KB
+<% end -%>
+<% if @bandwidth_burst != '' -%>
+BandwidthBurst <%= @bandwidth_burst %> KB
+<% end -%>
+<% if @relay_bandwidth_rate != '0' -%>
+RelayBandwidthRate <%= @relay_bandwidth_rate %> KB
+<% end -%>
+<% if @relay_bandwidth_burst != '0' -%>
+RelayBandwidthBurst <%= @relay_bandwidth_burst %> KB
+<% end -%>
+<% if @accounting_max != '0' -%>
+AccountingMax <%= @accounting_max %> GB
+<% if @accounting_start -%>
+AccountingStart <%= @accounting_start %>
+<% end -%>
+<% end -%>
+<% if @contact_info != '' -%>
+ContactInfo <%= @contact_info %>
+<% end -%>
+<% end -%>
+<% if @my_family != '' -%>
+MyFamily <%= @my_family %>
+<% end -%>
+<% if @bridge_relay != '0' -%>
+BridgeRelay <%= @bridge_relay %>
+<% end -%>
diff --git a/puppet/modules/tor/templates/torrc.socks.erb b/puppet/modules/tor/templates/torrc.socks.erb
new file mode 100644
index 00000000..4bc3ddc1
--- /dev/null
+++ b/puppet/modules/tor/templates/torrc.socks.erb
@@ -0,0 +1,9 @@
+# socks
+SocksPort <%= @port %>
+<% @listen_addresses.each do |listen_address| -%>
+SocksListenAddress <%= listen_address %>
+<% end -%>
+<% @policies.each do |policy| -%>
+SocksPolicy <%= policy %>
+<% end -%>
+
diff --git a/puppet/modules/tor/templates/torrc.transparent.erb b/puppet/modules/tor/templates/torrc.transparent.erb
new file mode 100644
index 00000000..c683150f
--- /dev/null
+++ b/puppet/modules/tor/templates/torrc.transparent.erb
@@ -0,0 +1,5 @@
+# Transparent proxy
+TransPort <%= @port %>
+<% @listen_addresses.each do |listen_address| -%>
+TransListenAddress <%= listen_address %>
+<% end -%>
diff --git a/puppet/modules/try/README.md b/puppet/modules/try/README.md
new file mode 100644
index 00000000..3888661e
--- /dev/null
+++ b/puppet/modules/try/README.md
@@ -0,0 +1,13 @@
+This module provides a "try" wrapper around common resource types.
+
+For example:
+
+ try::file {
+ '/path/to/file':
+ ensure => 'link',
+ target => $target;
+ }
+
+This will work just like `file`, but will silently fail if `$target` is undefined or the file does not exist.
+
+So far, only `file` type with symlinks works.
diff --git a/puppet/modules/try/manifests/file.pp b/puppet/modules/try/manifests/file.pp
new file mode 100644
index 00000000..2493d343
--- /dev/null
+++ b/puppet/modules/try/manifests/file.pp
@@ -0,0 +1,114 @@
+#
+# Works like the built-in type "file", but gets gracefully ignored if the target/source does not exist or is undefined.
+#
+# Also, if the source or target doesn't exist, and the destination is a git repo, then the file is restored from git.
+#
+# All executable paths are hardcoded to their paths in debian.
+#
+# known limitations:
+# * this is far too noisy
+# * $restore does not work for directories
+# * only file:// $source is supported
+# * $content is not supported, only $target or $source.
+# * does not auto-require all the parent directories like 'file' does
+#
+define try::file (
+ $ensure = undef,
+ $target = undef,
+ $source = undef,
+ $owner = undef,
+ $group = undef,
+ $recurse = undef,
+ $purge = undef,
+ $force = undef,
+ $mode = undef,
+ $restore = true) {
+
+ # dummy exec to propagate requires:
+ # metaparameter 'require' will get triggered by this dummy exec
+ # so then we just need to depend on this to capture all requires.
+ # exec { $name: command => "/bin/true" }
+
+ exec {
+ "chmod_${name}":
+ command => "/bin/chmod -R ${mode} '${name}'",
+ onlyif => "/usr/bin/test ${mode}",
+ refreshonly => true,
+ loglevel => debug;
+ "chown_${name}":
+ command => "/bin/chown -R ${owner} '${name}'",
+ onlyif => "/usr/bin/test ${owner}",
+ refreshonly => true,
+ loglevel => debug;
+ "chgrp_${name}":
+ command => "/bin/chgrp -R ${group} '${name}'",
+ onlyif => "/usr/bin/test ${group}",
+ refreshonly => true,
+ loglevel => debug;
+ }
+
+ if $target {
+ exec { "symlink_${name}":
+ command => "/bin/ln -s ${target} ${name}",
+ onlyif => "/usr/bin/test -d '${target}'",
+ }
+ } elsif $source {
+ if $ensure == 'directory' {
+ if $purge {
+ exec { "rsync_${name}":
+ command => "/usr/bin/rsync -r --delete '${source}/' '${name}'",
+ onlyif => "/usr/bin/test -d '${source}'",
+ unless => "/usr/bin/diff -rq '${source}' '${name}'",
+ notify => [Exec["chmod_${name}"], Exec["chown_${name}"], Exec["chgrp_${name}"]]
+ }
+ } else {
+ exec { "cp_r_${name}":
+ command => "/bin/cp -r '${source}' '${name}'",
+ onlyif => "/usr/bin/test -d '${source}'",
+ unless => "/usr/bin/diff -rq '${source}' '${name}'",
+ notify => [Exec["chmod_${name}"], Exec["chown_${name}"], Exec["chgrp_${name}"]]
+ }
+ }
+ } else {
+ exec { "cp_${name}":
+ command => "/bin/cp --remove-destination '${source}' '${name}'",
+ onlyif => "/usr/bin/test -e '${source}'",
+ unless => "/usr/bin/test ! -h '${name}' && /usr/bin/diff -q '${source}' '${name}'",
+ notify => [Exec["chmod_${name}"], Exec["chown_${name}"], Exec["chgrp_${name}"]]
+ }
+ }
+ }
+
+ #
+ # if the target/source does not exist (or is undef), and the file happens to be in a git repo,
+ # then restore the file to its original state.
+ #
+
+ if $target {
+ $target_or_source = $target
+ } else {
+ $target_or_source = $source
+ }
+
+ if ($target_or_source == undef) or $restore {
+ $file_basename = basename($name)
+ $file_dirname = dirname($name)
+ $command = "git rev-parse && unlink '${name}'; git checkout -- '${file_basename}' && chown --reference='${file_dirname}' '${name}'; true"
+ debug($command)
+
+ if $target_or_source == undef {
+ exec { "restore_${name}":
+ command => $command,
+ cwd => $file_dirname,
+ loglevel => info;
+ }
+ } else {
+ exec { "restore_${name}":
+ unless => "/usr/bin/test -e '${target_or_source}'",
+ command => $command,
+ cwd => $file_dirname,
+ loglevel => info;
+ }
+ }
+ }
+}
diff --git a/puppet/modules/try/manifests/init.pp b/puppet/modules/try/manifests/init.pp
new file mode 100644
index 00000000..1d2108c9
--- /dev/null
+++ b/puppet/modules/try/manifests/init.pp
@@ -0,0 +1,3 @@
+class try {
+
+}
diff --git a/puppet/modules/unbound/LICENSE b/puppet/modules/unbound/LICENSE
new file mode 100644
index 00000000..b0dfc82b
--- /dev/null
+++ b/puppet/modules/unbound/LICENSE
@@ -0,0 +1,13 @@
+Copyright (c) 2012 Martin Oppegaard <martin.oppegaard at gmail dot com>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/puppet/modules/unbound/Modulefile b/puppet/modules/unbound/Modulefile
new file mode 100644
index 00000000..93c593ad
--- /dev/null
+++ b/puppet/modules/unbound/Modulefile
@@ -0,0 +1,10 @@
+name 'oppegaard-unbound'
+version '0.1.0'
+author 'Martin Oppegaard'
+license 'ISC'
+summary 'The unbound module manages unbound'
+description 'This module manages unbound, the reqursive caching DNS resolver.
+ It manages the package, service, configuration file, control keys and support
+ files.'
+dependency 'puppetlabs/stdlib', '>= 3.2.0'
+dependency 'oppegaard/openbsd', '>= 0.1.0'
diff --git a/puppet/modules/unbound/README b/puppet/modules/unbound/README
new file mode 100644
index 00000000..529f37f0
--- /dev/null
+++ b/puppet/modules/unbound/README
@@ -0,0 +1,79 @@
+== Class: unbound
+
+The unbound class manages unbound, the reqursive caching DNS resolver.
+It manages the package, service, configuration file, control keys and
+support files.
+
+Supported operating systems are OpenBSD, Debian and Ubuntu. Tested on OpenBSD
+5.2 with Puppet 2.7.14 and Debian Sid with Puppet 2.7.18-2.
+
+The configuration file is concatenated from samples of server et. al.,
+stub-zone and forward-zone. The latter two are created independently
+from the server settings, by defines which can be used by other classes
+and modules.
+
+Control keys can be created with the unbound-control-setup program,
+and is enabled by default. These are neccessary to be able to control
+unbound (restart, reload etc) with the unbound-control program.
+
+The auto-trust-anchor-file 'root.key' can be created with the unbound-anchor
+program, and is enabled by default.
+
+The root-hints files named.cache can be managed, but have to be provided by
+the user. See the documentation in manifests/root_hints.pp for how to proceede.
+This functionality is not enabled by default.
+
+=== Parameters
+
+[*settings*]
+Hash containing the settings as key value pairs.
+
+[*ssl*]
+Mange unbound-control certificates? True or false, true by default.
+
+[*anchor*]
+Manage root.key? True or false, true by default.
+
+[*root_hints*]
+Manage named.cache? True or false, false by default.
+
+=== Examples
+
+class { 'unbound':
+ root_hints => true,
+ settings => {
+ server => {
+ verbosity => '1',
+ interface => [
+ '127.0.0.1',
+ '::1',
+ $::ipaddress,
+ ],
+ outgoing-interface => $::ipaddress,
+ access-control => [
+ '127.0.0.0/8 allow',
+ '::1 allow',
+ '10.0.0.0/8 allow',
+ ],
+ root-hints => '"/var/unbound/etc/named.cache"',
+ private-address => [
+ '10.0.0.0/8',
+ '172.16.0.0/12',
+ '192.168.0.0/16',
+ ],
+ private-domain => "\"$::domain\"",
+ auto-trust-anchor-file => '"/var/unbound/etc/root.key"',
+ },
+ python => { },
+ remote-control => {
+ control-enable => 'yes',
+ control-interface => [
+ '127.0.0.1',
+ '::1',
+ ],
+ },
+ }
+}
+
+See manifests/stub.pp and manifests/forward.pp for examples on how to create
+sub zones and forward zones repectively.
diff --git a/puppet/modules/unbound/manifests/anchor.pp b/puppet/modules/unbound/manifests/anchor.pp
new file mode 100644
index 00000000..e890722e
--- /dev/null
+++ b/puppet/modules/unbound/manifests/anchor.pp
@@ -0,0 +1,26 @@
+# == Class: unbound::anchor
+#
+# The unbound::anchor class manages the "root.key" file, and creates it with
+# the unbound-anchor program.
+#
+# === Examples
+#
+# include unbound::anchor
+#
+class unbound::anchor {
+ include unbound::params
+
+ file { $unbound::params::anchor:
+ owner => $unbound::params::user,
+ group => $unbound::params::group,
+ mode => '0644',
+ require => Exec[$unbound::params::unbound_anchor],
+ }
+
+ exec { $unbound::params::unbound_anchor:
+ command => "${unbound::params::unbound_anchor} -a ${unbound::params::anchor}",
+ creates => $unbound::params::anchor,
+ returns => 1,
+ before => Class['unbound::service'],
+ }
+}
diff --git a/puppet/modules/unbound/manifests/forward.pp b/puppet/modules/unbound/manifests/forward.pp
new file mode 100644
index 00000000..740c004d
--- /dev/null
+++ b/puppet/modules/unbound/manifests/forward.pp
@@ -0,0 +1,32 @@
+# == Define: unbound::forward
+#
+# Creates a forward-zone. $settings is a hash containing the settings.
+# The name of the resource is used as the 'name' of the zone.
+#
+# === Parameters
+#
+# [*settings*]
+# Hash containing the settings as key value pairs.
+#
+# === Examples
+#
+# unbound::forward { 'example.com':
+# settings => {
+# forward-addr => '10.0.0.1',
+# },
+# }
+#
+define unbound::forward (
+ $settings,
+) {
+ include unbound
+
+ $zone_name = { name => "\"${title}\"" }
+ $real_settings = { forward-zone => merge($zone_name, $settings) }
+
+ concat::fragment { "unbound ${title}":
+ target => $unbound::params::config,
+ content => template('unbound/unbound.conf.erb'),
+ order => 3,
+ }
+}
diff --git a/puppet/modules/unbound/manifests/init.pp b/puppet/modules/unbound/manifests/init.pp
new file mode 100644
index 00000000..ecb7970a
--- /dev/null
+++ b/puppet/modules/unbound/manifests/init.pp
@@ -0,0 +1,117 @@
+# == Class: unbound
+#
+# The unbound class manages unbound, the reqursive caching dns resolver.
+# It manages the package, service, configuration file, control keys and
+# support files.
+#
+# The configuration file is concatenated from samples of server et. al.,
+# stub-zone and forward-zone. The latter two are created independently
+# from the server settings, by defines which can be used by other classes
+# and modules.
+#
+# Control keys can be created with the unbound-control-setup program,
+# and is enabled by default. These are neccessary to be able to control
+# unbound (restart, reload etc) with the unbound-control program.
+#
+# The auto-trust-anchor-file 'root.key' can be created with the unbound-anchor
+# program, and is enabled by default.
+#
+# The root-hints files named.cache can be managed, but have to be provided by
+# the user. See the documentation in manifests/root_hints.pp for how to proceede.
+# This functionality is not enabled by default.
+#
+# === Parameters
+#
+# [*settings*]
+# Hash containing the settings as key value pairs.
+#
+# [*ssl*]
+# Mange unbound-control certificates? True or false, true by default.
+#
+# [*anchor*]
+# Manage root.key? True or false, true by default.
+#
+# [*root_hints*]
+# Manage named.cache? True or false, false by default.
+#
+# === Examples
+#
+# class { 'unbound':
+# root_hints => true,
+# settings => {
+# server => {
+# verbosity => '1',
+# interface => [
+# '127.0.0.1',
+# '::1',
+# $::ipaddress,
+# ],
+# outgoing-interface => $::ipaddress,
+# access-control => [
+# '127.0.0.0/8 allow',
+# '::1 allow',
+# '10.0.0.0/8 allow',
+# ],
+# root-hints => '"/var/unbound/etc/named.cache"',
+# private-address => [
+# '10.0.0.0/8',
+# '172.16.0.0/12',
+# '192.168.0.0/16',
+# ],
+# private-domain => "\"$::domain\"",
+# auto-trust-anchor-file => '"/var/unbound/etc/root.key"',
+# },
+# python => { },
+# remote-control => {
+# control-enable => 'yes',
+# control-interface => [
+# '127.0.0.1',
+# '::1',
+# ],
+# },
+# }
+# }
+#
+# See manifests/stub.pp and manifests/forward.pp for examples on how to create
+# sub zones and forward zones repectively.
+#
+class unbound (
+ $settings,
+ $anchor = true,
+ $root_hints = false,
+ $ssl = true,
+) inherits unbound::params {
+
+ include concat::setup
+ include unbound::package
+ include unbound::service
+
+ validate_hash($settings)
+ validate_bool($anchor)
+ validate_bool($root_hints)
+ validate_bool($ssl)
+
+ if $anchor {
+ include unbound::anchor
+ }
+
+ if $root_hints {
+ include unbound::root_hints
+ }
+
+ if $ssl {
+ include unbound::ssl
+ }
+
+ $real_settings = $settings
+
+ concat { $unbound::params::config:
+ require => Class['unbound::package'],
+ }
+
+ concat::fragment { 'unbound server':
+ target => $unbound::params::config,
+ content => template('unbound/unbound.conf.erb'),
+ order => 1,
+ }
+}
diff --git a/puppet/modules/unbound/manifests/package.pp b/puppet/modules/unbound/manifests/package.pp
new file mode 100644
index 00000000..b9b44f16
--- /dev/null
+++ b/puppet/modules/unbound/manifests/package.pp
@@ -0,0 +1,15 @@
+# == Class: unbound::package
+#
+# Manages the unbound package.
+#
+# === Examples
+#
+# include unbound::package
+#
+class unbound::package {
+ include unbound::params
+
+ package { $unbound::params::package:
+ ensure => installed,
+ }
+}
diff --git a/puppet/modules/unbound/manifests/params.pp b/puppet/modules/unbound/manifests/params.pp
new file mode 100644
index 00000000..fc043e24
--- /dev/null
+++ b/puppet/modules/unbound/manifests/params.pp
@@ -0,0 +1,42 @@
+class unbound::params {
+ case $::osfamily {
+ 'OpenBSD': {
+ $package = 'unbound'
+ $service = 'unbound'
+ $hasstatus = true
+ $dir = '/var/unbound/etc'
+ $logfile = '/var/unbound/dev/log'
+ $control_setup = '/usr/local/sbin/unbound-control-setup'
+ $unbound_anchor = '/usr/local/sbin/unbound-anchor'
+ $extended_service = 'unbound::service::openbsd'
+ $unbound_flags = ''
+ $user = '_unbound'
+ $group = '_unbound'
+ }
+ 'ubuntu', 'debian': {
+ $package = 'unbound'
+ $service = 'unbound'
+ $hasstatus = true
+ $dir = '/etc/unbound'
+ $logfile = ''
+ $control_setup = '/usr/sbin/unbound-control-setup'
+ $unbound_anchor = '/usr/sbin/unbound-anchor'
+ $unbound_flags = ''
+ $user = 'unbound'
+ $group = 'unbound'
+ }
+ default: {
+ fail("Class[unbound] is not supported by your operating system: ${::operatingsystem}")
+ }
+ }
+
+ $config = "${dir}/unbound.conf"
+ $control_certs = [
+ "${dir}/unbound_control.key",
+ "${dir}/unbound_control.pem",
+ "${dir}/unbound_server.key",
+ "${dir}/unbound_server.pem",
+ ]
+ $anchor = "${dir}/root.key"
+ $root_hints = "${dir}/named.cache"
+}
diff --git a/puppet/modules/unbound/manifests/root_hints.pp b/puppet/modules/unbound/manifests/root_hints.pp
new file mode 100644
index 00000000..12594956
--- /dev/null
+++ b/puppet/modules/unbound/manifests/root_hints.pp
@@ -0,0 +1,35 @@
+# == Class: unbound::root_hints
+#
+# The unbound::root_hints class manages the root-hints named.cache file.
+# The default mount point is /module_data, which should be installed
+# and populated with a the named.cache file before implementing this
+# class. See unbound.conf(5) or the default configuration file for
+# how to retrieve such a file.
+#
+# === Parameters
+#
+# [*_mount*]
+# Meta parameter for specifying an alternate mount path.
+#
+# === Examples
+#
+# class { 'unbound::root_hints':
+# $_mount = '/modules/unbound',
+# }
+#
+# include unbound::root_hints
+#
+class unbound::root_hints (
+ $_mount = "/module_data/unbound",
+) {
+ include unbound::params
+
+ file { $unbound::params::root_hints:
+ ensure => file,
+ owner => $unbound::params::user,
+ group => $unbound::params::group,
+ mode => '0644',
+ source => "puppet://${_mount}/named.cache",
+ before => Class['unbound::service'],
+ }
+}
diff --git a/puppet/modules/unbound/manifests/service.pp b/puppet/modules/unbound/manifests/service.pp
new file mode 100644
index 00000000..f96f453e
--- /dev/null
+++ b/puppet/modules/unbound/manifests/service.pp
@@ -0,0 +1,22 @@
+# == Class: unbound::service
+#
+# Manages the unbound service. If $unbound::params::extended_service
+# is true then OS specific service things are included.
+#
+# === Examples
+#
+# include unbound::service
+#
+class unbound::service {
+ include unbound::params
+
+ if $unbound::params::extended_service {
+ class { $unbound::params::extended_service: }
+ }
+
+ service { $unbound::params::service:
+ ensure => running,
+ hasstatus => $unbound::params::hasstatus,
+ subscribe => File[$unbound::params::config],
+ }
+}
diff --git a/puppet/modules/unbound/manifests/service/openbsd.pp b/puppet/modules/unbound/manifests/service/openbsd.pp
new file mode 100644
index 00000000..916a7ce9
--- /dev/null
+++ b/puppet/modules/unbound/manifests/service/openbsd.pp
@@ -0,0 +1,21 @@
+# == Class: unbound::service::openbsd
+#
+# Service things specific for OpenBSD. Sets the unbound_flags variable in
+# /etc/rc.conf.local, and appends the path to the log device to syslogd_flags.
+#
+# === Examples
+#
+# include unbound::service::openbsd
+#
+class unbound::service::openbsd {
+ rcconf { 'unbound_flags':
+ value => $unbound::params::unbound_flags,
+ }
+
+ # syslogd_flags needs one -a dir per chrooted service. Each can be a separate
+ # line, so don't use rcconf.
+ file_line { 'unbound syslogd_flags':
+ path => '/etc/rc.conf.local',
+ line => "syslogd_flags=\"\${syslogd_flags} -a ${unbound::params::logfile}\"";
+ }
+}
diff --git a/puppet/modules/unbound/manifests/ssl.pp b/puppet/modules/unbound/manifests/ssl.pp
new file mode 100644
index 00000000..e0cff172
--- /dev/null
+++ b/puppet/modules/unbound/manifests/ssl.pp
@@ -0,0 +1,25 @@
+# == Class: unbound::ssl
+#
+# unbound::ssl creates ssl certificates for controlling unbound with unbound-control,
+# using the unbound-control-setup program. Furthermore, the class manages the mode and user of the certificates themselves.
+#
+# === Examples
+#
+# include unbound::ssl
+#
+class unbound::ssl {
+ include unbound::params
+
+ file { $unbound::params::control_certs:
+ owner => $unbound::params::user,
+ group => $unbound::params::gruop,
+ mode => '0440',
+ require => Exec[$unbound::params::control_setup],
+ }
+
+ exec { $unbound::params::control_setup:
+ command => "${unbound::params::control_setup} -d ${unbound::params::dir}",
+ creates => $unbound::params::control_certs,
+ before => Class['unbound::service'],
+ }
+}
diff --git a/puppet/modules/unbound/manifests/stub.pp b/puppet/modules/unbound/manifests/stub.pp
new file mode 100644
index 00000000..02797fdb
--- /dev/null
+++ b/puppet/modules/unbound/manifests/stub.pp
@@ -0,0 +1,32 @@
+# == Define: unbound::stub
+#
+# Creates a stub-zone. $settings is a hash containing the settings.
+# The name of the resource is used as the 'name' of the zone.
+#
+# === Parameters
+#
+# [*settings*]
+# Hash containing the settings as key value pairs.
+#
+# === Examples
+#
+# unbound::stub { $::domain:
+# settings => {
+# stub-addr => '192.168.1.1',
+# },
+# }
+#
+define unbound::stub (
+ $settings,
+) {
+ include unbound::params
+
+ $zone_name = { name => "\"${title}\"" }
+ $real_settings = { stub-zone => merge($zone_name, $settings) }
+
+ concat::fragment { "unbound ${title}":
+ target => $unbound::params::config,
+ content => template('unbound/unbound.conf.erb'),
+ order => 2,
+ }
+}
diff --git a/puppet/modules/unbound/metadata.json b/puppet/modules/unbound/metadata.json
new file mode 100644
index 00000000..3c20ad4f
--- /dev/null
+++ b/puppet/modules/unbound/metadata.json
@@ -0,0 +1,50 @@
+{
+ "source": "UNKNOWN",
+ "types": [
+
+ ],
+ "project_page": "UNKNOWN",
+ "checksums": {
+ "manifests/service.pp": "baf263d0a562d6543c7418acf1e79cc2",
+ "tests/anchor.pp": "c6d270f2a0f448d7cebb6aed28b60998",
+ "LICENSE": "f8a2562cac0b3859771886422f703d27",
+ "spec/spec_helper.rb": "a55d1e6483344f8ec6963fcb2c220372",
+ "tests/service/openbsd.pp": "624a9db21c7bf277eec24186c490f4a5",
+ "Modulefile": "f1f67d825f8ef998667be8164f18744c",
+ "manifests/package.pp": "53a9d2e732f5690967f31e90d4eaaa99",
+ "manifests/init.pp": "92245e6671f2c3aef459c766c3df522d",
+ "tests/ssl.pp": "25439a06abaef7b5a2d778719a3d8c95",
+ "tests/service.pp": "106da53de4dd52a3e24d61af51263c00",
+ "tests/root_hints.pp": "7c9a9faa7350d023a00538812b9e3383",
+ "manifests/service/openbsd.pp": "fec14ed74ccf3a4c9a9d39a9870b992e",
+ "manifests/params.pp": "b949b2ac149f62130b0462a268c2a3ea",
+ "README": "6b5a94def03194686b83121ee218debd",
+ "tests/forward.pp": "29b51124459bc4fa265681cb0873afb7",
+ "manifests/root_hints.pp": "8c6879c961c8684dae69dbd13cea5efe",
+ "tests/params.pp": "f8662ff6d212159f10aac3186d010605",
+ "manifests/ssl.pp": "cc6e32a92c843bebbe5c05d7dc530c9b",
+ "tests/stub.pp": "34ca449ff833e3b1719d23f2875385d8",
+ "tests/init.pp": "9105221e2b9c942579d7e88f41a89b10",
+ "manifests/forward.pp": "07b209d883cf9ce1888a8427357a367f",
+ "tests/package.pp": "e1236eb6d345f5368f054f0e847a6a76",
+ "templates/unbound.conf.erb": "f5ee6f6444ad705c8494b8ec04e21d5b",
+ "manifests/stub.pp": "a0421e88c96df10cca872715c808ad5a",
+ "manifests/anchor.pp": "4ddf763591743e0505bf62a859e11a2d"
+ },
+ "license": "ISC",
+ "dependencies": [
+ {
+ "version_requirement": ">= 3.2.0",
+ "name": "puppetlabs/stdlib"
+ },
+ {
+ "version_requirement": ">= 0.1.0",
+ "name": "oppegaard/openbsd"
+ }
+ ],
+ "version": "0.1.0",
+ "summary": "The unbound module manages unbound",
+ "description": "This module manages unbound, the reqursive caching DNS resolver.\n It manages the package, service, configuration file, control keys and support\n files.",
+ "author": "Martin Oppegaard",
+ "name": "oppegaard-unbound"
+} \ No newline at end of file
diff --git a/puppet/modules/unbound/spec/spec_helper.rb b/puppet/modules/unbound/spec/spec_helper.rb
new file mode 100644
index 00000000..5fda5887
--- /dev/null
+++ b/puppet/modules/unbound/spec/spec_helper.rb
@@ -0,0 +1,17 @@
+dir = File.expand_path(File.dirname(__FILE__))
+$LOAD_PATH.unshift File.join(dir, 'lib')
+
+require 'mocha'
+require 'puppet'
+require 'rspec'
+require 'spec/autorun'
+
+Spec::Runner.configure do |config|
+ config.mock_with :mocha
+end
+
+# We need this because the RAL uses 'should' as a method. This
+# allows us the same behaviour but with a different method name.
+class Object
+ alias :must :should
+end
diff --git a/puppet/modules/unbound/templates/unbound.conf.erb b/puppet/modules/unbound/templates/unbound.conf.erb
new file mode 100644
index 00000000..ad93965c
--- /dev/null
+++ b/puppet/modules/unbound/templates/unbound.conf.erb
@@ -0,0 +1,8 @@
+<% @real_settings.sort.each do |section, settings| -%>
+<%= "#{section}:" %>
+<% settings.sort.each do |key, val| -%>
+<% [val].flatten.each do |val| -%>
+<%= " #{key}: #{val}" %>
+<% end -%>
+<% end %>
+<% end -%>
diff --git a/puppet/modules/unbound/tests/anchor.pp b/puppet/modules/unbound/tests/anchor.pp
new file mode 100644
index 00000000..ce73902a
--- /dev/null
+++ b/puppet/modules/unbound/tests/anchor.pp
@@ -0,0 +1,2 @@
+include unbound::params
+include unbound::anchor
diff --git a/puppet/modules/unbound/tests/forward.pp b/puppet/modules/unbound/tests/forward.pp
new file mode 100644
index 00000000..0d245e61
--- /dev/null
+++ b/puppet/modules/unbound/tests/forward.pp
@@ -0,0 +1,7 @@
+include concat::setup
+include unbound
+unbound::forward { 'example.com':
+ settings => {
+ forward-addr => '127.0.0.1',
+ },
+}
diff --git a/puppet/modules/unbound/tests/init.pp b/puppet/modules/unbound/tests/init.pp
new file mode 100644
index 00000000..89af1770
--- /dev/null
+++ b/puppet/modules/unbound/tests/init.pp
@@ -0,0 +1,26 @@
+# The baseline for module testing used by Puppet Labs is that each manifest
+# should have a corresponding test manifest that declares that class or defined
+# type.
+#
+# Tests are then run by using puppet apply --noop (to check for compilation errors
+# and view a log of events) or by fully applying the test in a virtual environment
+# (to compare the resulting system state to the desired state).
+#
+# Learn more about module testing here: http://docs.puppetlabs.com/guides/tests_smoke.html
+#
+include concat::setup
+include unbound::package
+include unbound::service
+include unbound::anchor
+include unbound::ssl
+
+class { 'unbound':
+ anchor => false,
+ root_hints => false,
+ ssl => false,
+ settings => {
+ server => { },
+ python => { },
+ remote-control => { },
+ },
+}
diff --git a/puppet/modules/unbound/tests/package.pp b/puppet/modules/unbound/tests/package.pp
new file mode 100644
index 00000000..0360b52f
--- /dev/null
+++ b/puppet/modules/unbound/tests/package.pp
@@ -0,0 +1,2 @@
+include unbound::params
+include unbound::package
diff --git a/puppet/modules/unbound/tests/params.pp b/puppet/modules/unbound/tests/params.pp
new file mode 100644
index 00000000..7cd72bd3
--- /dev/null
+++ b/puppet/modules/unbound/tests/params.pp
@@ -0,0 +1 @@
+include unbound::params
diff --git a/puppet/modules/unbound/tests/root_hints.pp b/puppet/modules/unbound/tests/root_hints.pp
new file mode 100644
index 00000000..3ce21ed1
--- /dev/null
+++ b/puppet/modules/unbound/tests/root_hints.pp
@@ -0,0 +1,2 @@
+include unbound::params
+include unbound::root_hints
diff --git a/puppet/modules/unbound/tests/service.pp b/puppet/modules/unbound/tests/service.pp
new file mode 100644
index 00000000..0fb73df1
--- /dev/null
+++ b/puppet/modules/unbound/tests/service.pp
@@ -0,0 +1,2 @@
+include unbound::params
+include unbound::service
diff --git a/puppet/modules/unbound/tests/service/openbsd.pp b/puppet/modules/unbound/tests/service/openbsd.pp
new file mode 100644
index 00000000..3e8696c0
--- /dev/null
+++ b/puppet/modules/unbound/tests/service/openbsd.pp
@@ -0,0 +1 @@
+include unbound::service::openbsd
diff --git a/puppet/modules/unbound/tests/ssl.pp b/puppet/modules/unbound/tests/ssl.pp
new file mode 100644
index 00000000..d7111977
--- /dev/null
+++ b/puppet/modules/unbound/tests/ssl.pp
@@ -0,0 +1,2 @@
+include unbound::params
+include unbound::ssl
diff --git a/puppet/modules/unbound/tests/stub.pp b/puppet/modules/unbound/tests/stub.pp
new file mode 100644
index 00000000..b1477f9d
--- /dev/null
+++ b/puppet/modules/unbound/tests/stub.pp
@@ -0,0 +1,7 @@
+include concat::setup
+include unbound::params
+unbound::stub { 'example.com':
+ settings => {
+ stub-addr => '127.0.0.1',
+ },
+}
diff --git a/puppet/modules/vcsrepo/.gitattributes b/puppet/modules/vcsrepo/.gitattributes
new file mode 100644
index 00000000..900ea0cb
--- /dev/null
+++ b/puppet/modules/vcsrepo/.gitattributes
@@ -0,0 +1,5 @@
+#This file is generated by ModuleSync, do not edit.
+*.rb eol=lf
+*.erb eol=lf
+*.pp eol=lf
+*.sh eol=lf
diff --git a/puppet/modules/vcsrepo/.gitignore b/puppet/modules/vcsrepo/.gitignore
new file mode 100644
index 00000000..dd126f2f
--- /dev/null
+++ b/puppet/modules/vcsrepo/.gitignore
@@ -0,0 +1,11 @@
+#This file is generated by ModuleSync, do not edit.
+pkg/
+Gemfile.lock
+vendor/
+spec/fixtures/
+.vagrant/
+.bundle/
+coverage/
+log/
+.idea/
+*.iml
diff --git a/puppet/modules/vcsrepo/.rspec b/puppet/modules/vcsrepo/.rspec
new file mode 100644
index 00000000..16f9cdb0
--- /dev/null
+++ b/puppet/modules/vcsrepo/.rspec
@@ -0,0 +1,2 @@
+--color
+--format documentation
diff --git a/puppet/modules/vcsrepo/.sync.yml b/puppet/modules/vcsrepo/.sync.yml
new file mode 100644
index 00000000..02e21731
--- /dev/null
+++ b/puppet/modules/vcsrepo/.sync.yml
@@ -0,0 +1,3 @@
+---
+LICENSE:
+ unmanaged: true
diff --git a/puppet/modules/vcsrepo/.travis.yml b/puppet/modules/vcsrepo/.travis.yml
new file mode 100644
index 00000000..588fb5b0
--- /dev/null
+++ b/puppet/modules/vcsrepo/.travis.yml
@@ -0,0 +1,20 @@
+#This file is generated by ModuleSync, do not edit.
+---
+sudo: false
+language: ruby
+cache: bundler
+bundler_args: --without system_tests
+script: "bundle exec rake validate lint spec"
+matrix:
+ fast_finish: true
+ include:
+ - rvm: 2.1.6
+ env: PUPPET_GEM_VERSION="~> 4.0" STRICT_VARIABLES="yes"
+ - rvm: 2.1.5
+ env: PUPPET_GEM_VERSION="~> 3.0" FUTURE_PARSER="yes"
+ - rvm: 2.1.5
+ env: PUPPET_GEM_VERSION="~> 3.0"
+ - rvm: 1.9.3
+ env: PUPPET_GEM_VERSION="~> 3.0"
+notifications:
+ email: false
diff --git a/puppet/modules/vcsrepo/CHANGELOG.md b/puppet/modules/vcsrepo/CHANGELOG.md
new file mode 100644
index 00000000..9aac1e52
--- /dev/null
+++ b/puppet/modules/vcsrepo/CHANGELOG.md
@@ -0,0 +1,150 @@
+# Change Log
+All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/).
+
+## [1.3.2] - Supported Release
+###Summary
+
+Small release for support of newer PE versions. This increments the version of PE in the metadata.json file.
+
+## [1.3.1] - 2015-07-28 Supported Release
+###Summary
+This release includes a number of bugfixes along with some test updates.
+
+### Fixed
+- Fix for detached HEAD on git 2.4+
+- Git provider doesn't ignore revision property when depth is used (MODULES-2131)
+- Test fixes
+- Check if submodules == true before calling update_submodules
+
+## [1.3.0] - 2015-05-19 Supported Release
+### Summary
+This release adds git provider remote handling, svn conflict resolution, and fixes the git provider when /tmp is mounted noexec.
+
+### Added
+- `source` property now takes a hash of sources for the git provider's remotes
+- Add `submodules` parameter to skip submodule initialization for git provider
+- Add `conflict` to svn provider to resolve conflicts
+- Add `branch` parameter to specify clone branch
+- Readme rewrite
+
+### Fixed
+- The git provider now works even if `/tmp` is noexec
+
+## [1.2.0] - 2014-11-04 Supported Release
+### Summary
+This release includes some improvements for git, mercurial, and cvs providers, and fixes the bug where there were warnings about multiple default providers.
+
+### Added
+- Update git and mercurial providers to set UID with `Puppet::Util::Execution.execute` instead of `su`
+- Allow git excludes to be string or array
+- Add `user` feature to cvs provider
+
+### Fixed
+- No more warnings about multiple default providers! (MODULES-428)
+
+## [1.1.0] - 2014-07-14 Supported Release
+### Summary
+This release adds a Perforce provider\* and corrects the git provider behavior
+when using `ensure => latest`.
+
+\*(Only git provider is currently supported.)
+
+### Added
+- New Perforce provider
+
+### Fixed
+- (MODULES-660) Fix behavior with `ensure => latest` and detached HEAD
+- Spec test fixes
+
+## [1.0.2] - 2014-06-30 Supported Release
+### Summary
+This supported release adds SLES 11 to the list of compatible OSs and
+documentation updates for support.
+
+## [1.0.1] - 2014-06-17 Supported Release
+### Summary
+This release is the first supported release of vcsrepo. The readme has been
+greatly improved.
+
+### Added
+- Updated and expanded readme to follow readme template
+
+### Fixed
+- Remove SLES from compatability metadata
+- Unpin rspec development dependencies
+- Update acceptance level testing
+
+## [1.0.0] - 2014-06-04
+### Summary
+
+This release focuses on a number of bugfixes, and also has some
+new features for Bzr and Git.
+
+### Added
+- Bzr:
+ - Call set_ownership
+- Git:
+ - Add ability for shallow clones
+ - Use -a and desired for HARD resets
+ - Use rev-parse to get tag canonical revision
+
+### Fixed
+- HG:
+ - Only add ssh options when it's talking to the network
+- Git:
+ - Fix for issue with detached HEAD
+ - force => true will now destroy and recreate repo
+ - Actually use the remote parameter
+ - Use origin/master instead of origin/HEAD when on master
+- SVN:
+ - Fix svnlook behavior with plain directories
+
+## 0.2.0 - 2013-11-13
+### Summary
+
+This release mainly focuses on a number of bugfixes, which should
+significantly improve the reliability of Git and SVN. Thanks to
+our many contributors for all of these fixes!
+
+### Added
+- Git:
+ - Add autorequire for Package['git']
+- HG:
+ - Allow user and identity properties.
+- Bzr:
+ - "ensure => latest" support.
+- SVN:
+ - Added configuration parameter.
+ - Add support for master svn repositories.
+- CVS:
+ - Allow for setting the CVS_RSH environment variable.
+
+### Fixed
+- Handle Puppet::Util[::Execution].withenv for 2.x and 3.x properly.
+- Change path_empty? to not do full directory listing.
+- Overhaul spec tests to work with rspec2.
+- Git:
+ - Improve Git SSH usage documentation.
+ - Add ssh session timeouts to prevent network issues from blocking runs.
+ - Fix git provider checkout of a remote ref on an existing repo.
+ - Allow unlimited submodules (thanks to --recursive).
+ - Use git checkout --force instead of short -f everywhere.
+ - Update git provider to handle checking out into an existing (empty) dir.
+- SVN:
+ - Handle force property. for svn.
+ - Adds support for changing upstream repo url.
+ - Check that the URL of the WC matches the URL from the manifest.
+ - Changed from using "update" to "switch".
+ - Handle revision update without source switch.
+ - Fix svn provider to look for '^Revision:' instead of '^Last Changed Rev:'.
+- CVS:
+ - Documented the "module" attribute.
+
+[1.3.2]: https://github.com/puppetlabs/puppetlabs-vcsrepo/compare/1.3.1...1.3.2
+[1.3.1]: https://github.com/puppetlabs/puppetlabs-vcsrepo/compare/1.3.0...1.3.1
+[1.3.0]: https://github.com/puppetlabs/puppetlabs-vcsrepo/compare/1.2.0...1.3.0
+[1.2.0]: https://github.com/puppetlabs/puppetlabs-vcsrepo/compare/1.1.0...1.2.0
+[1.1.0]: https://github.com/puppetlabs/puppetlabs-vcsrepo/compare/1.0.2...1.1.0
+[1.0.2]: https://github.com/puppetlabs/puppetlabs-vcsrepo/compare/1.0.1...1.0.2
+[1.0.1]: https://github.com/puppetlabs/puppetlabs-vcsrepo/compare/1.0.0...1.0.1
+[1.0.0]: https://github.com/puppetlabs/puppetlabs-vcsrepo/compare/0.2.0...1.0.0
diff --git a/puppet/modules/vcsrepo/CONTRIBUTING.md b/puppet/modules/vcsrepo/CONTRIBUTING.md
new file mode 100644
index 00000000..bfeaa701
--- /dev/null
+++ b/puppet/modules/vcsrepo/CONTRIBUTING.md
@@ -0,0 +1,220 @@
+Checklist (and a short version for the impatient)
+=================================================
+
+ * Commits:
+
+ - Make commits of logical units.
+
+ - Check for unnecessary whitespace with "git diff --check" before
+ committing.
+
+ - Commit using Unix line endings (check the settings around "crlf" in
+ git-config(1)).
+
+ - Do not check in commented out code or unneeded files.
+
+ - The first line of the commit message should be a short
+ description (50 characters is the soft limit, excluding ticket
+ number(s)), and should skip the full stop.
+
+ - Associate the issue in the message. The first line should include
+ the issue number in the form "(#XXXX) Rest of message".
+
+ - The body should provide a meaningful commit message, which:
+
+ - uses the imperative, present tense: "change", not "changed" or
+ "changes".
+
+ - includes motivation for the change, and contrasts its
+ implementation with the previous behavior.
+
+ - Make sure that you have tests for the bug you are fixing, or
+ feature you are adding.
+
+ - Make sure the test suites passes after your commit:
+ `bundle exec rspec spec/acceptance` More information on [testing](#Testing) below
+
+ - When introducing a new feature, make sure it is properly
+ documented in the README.md
+
+ * Submission:
+
+ * Pre-requisites:
+
+ - Make sure you have a [GitHub account](https://github.com/join)
+
+ - [Create a ticket](https://tickets.puppetlabs.com/secure/CreateIssue!default.jspa), or [watch the ticket](https://tickets.puppetlabs.com/browse/) you are patching for.
+
+ * Preferred method:
+
+ - Fork the repository on GitHub.
+
+ - Push your changes to a topic branch in your fork of the
+ repository. (the format ticket/1234-short_description_of_change is
+ usually preferred for this project).
+
+ - Submit a pull request to the repository in the puppetlabs
+ organization.
+
+The long version
+================
+
+ 1. Make separate commits for logically separate changes.
+
+ Please break your commits down into logically consistent units
+ which include new or changed tests relevant to the rest of the
+ change. The goal of doing this is to make the diff easier to
+ read for whoever is reviewing your code. In general, the easier
+ your diff is to read, the more likely someone will be happy to
+ review it and get it into the code base.
+
+ If you are going to refactor a piece of code, please do so as a
+ separate commit from your feature or bug fix changes.
+
+ We also really appreciate changes that include tests to make
+ sure the bug is not re-introduced, and that the feature is not
+ accidentally broken.
+
+ Describe the technical detail of the change(s). If your
+ description starts to get too long, that is a good sign that you
+ probably need to split up your commit into more finely grained
+ pieces.
+
+ Commits which plainly describe the things which help
+ reviewers check the patch and future developers understand the
+ code are much more likely to be merged in with a minimum of
+ bike-shedding or requested changes. Ideally, the commit message
+ would include information, and be in a form suitable for
+ inclusion in the release notes for the version of Puppet that
+ includes them.
+
+ Please also check that you are not introducing any trailing
+ whitespace or other "whitespace errors". You can do this by
+ running "git diff --check" on your changes before you commit.
+
+ 2. Sending your patches
+
+ To submit your changes via a GitHub pull request, we _highly_
+ recommend that you have them on a topic branch, instead of
+ directly on "master".
+ It makes things much easier to keep track of, especially if
+ you decide to work on another thing before your first change
+ is merged in.
+
+ GitHub has some pretty good
+ [general documentation](http://help.github.com/) on using
+ their site. They also have documentation on
+ [creating pull requests](http://help.github.com/send-pull-requests/).
+
+ In general, after pushing your topic branch up to your
+ repository on GitHub, you can switch to the branch in the
+ GitHub UI and click "Pull Request" towards the top of the page
+ in order to open a pull request.
+
+
+ 3. Update the related GitHub issue.
+
+ If there is a GitHub issue associated with the change you
+ submitted, then you should update the ticket to include the
+ location of your branch, along with any other commentary you
+ may wish to make.
+
+Testing
+=======
+
+Getting Started
+---------------
+
+Our puppet modules provide [`Gemfile`](./Gemfile)s which can tell a ruby
+package manager such as [bundler](http://bundler.io/) what Ruby packages,
+or Gems, are required to build, develop, and test this software.
+
+Please make sure you have [bundler installed](http://bundler.io/#getting-started)
+on your system, then use it to install all dependencies needed for this project,
+by running
+
+```shell
+% bundle install
+Fetching gem metadata from https://rubygems.org/........
+Fetching gem metadata from https://rubygems.org/..
+Using rake (10.1.0)
+Using builder (3.2.2)
+-- 8><-- many more --><8 --
+Using rspec-system-puppet (2.2.0)
+Using serverspec (0.6.3)
+Using rspec-system-serverspec (1.0.0)
+Using bundler (1.3.5)
+Your bundle is complete!
+Use `bundle show [gemname]` to see where a bundled gem is installed.
+```
+
+NOTE some systems may require you to run this command with sudo.
+
+If you already have those gems installed, make sure they are up-to-date:
+
+```shell
+% bundle update
+```
+
+With all dependencies in place and up-to-date we can now run the tests:
+
+```shell
+% bundle exec rake spec
+```
+
+This will execute all the [rspec tests](http://rspec-puppet.com/) tests
+under [spec/defines](./spec/defines), [spec/classes](./spec/classes),
+and so on. rspec tests may have the same kind of dependencies as the
+module they are testing. While the module defines in its [Modulefile](./Modulefile),
+rspec tests define them in [.fixtures.yml](./fixtures.yml).
+
+Some puppet modules also come with [beaker](https://github.com/puppetlabs/beaker)
+tests. These tests spin up a virtual machine under
+[VirtualBox](https://www.virtualbox.org/)) with, controlling it with
+[Vagrant](http://www.vagrantup.com/) to actually simulate scripted test
+scenarios. In order to run these, you will need both of those tools
+installed on your system.
+
+You can run them by issuing the following command
+
+```shell
+% bundle exec rake spec_clean
+% bundle exec rspec spec/acceptance
+```
+
+This will now download a pre-fabricated image configured in the [default node-set](./spec/acceptance/nodesets/default.yml),
+install puppet, copy this module and install its dependencies per [spec/spec_helper_acceptance.rb](./spec/spec_helper_acceptance.rb)
+and then run all the tests under [spec/acceptance](./spec/acceptance).
+
+Writing Tests
+-------------
+
+XXX getting started writing tests.
+
+If you have commit access to the repository
+===========================================
+
+Even if you have commit access to the repository, you will still need to
+go through the process above, and have someone else review and merge
+in your changes. The rule is that all changes must be reviewed by a
+developer on the project (that did not write the code) to ensure that
+all changes go through a code review process.
+
+Having someone other than the author of the topic branch recorded as
+performing the merge is the record that they performed the code
+review.
+
+
+Additional Resources
+====================
+
+* [Getting additional help](http://puppetlabs.com/community/get-help)
+
+* [Writing tests](http://projects.puppetlabs.com/projects/puppet/wiki/Development_Writing_Tests)
+
+* [Patchwork](https://patchwork.puppetlabs.com)
+
+* [General GitHub documentation](http://help.github.com/)
+
+* [GitHub pull request documentation](http://help.github.com/send-pull-requests/)
+
diff --git a/puppet/modules/vcsrepo/Gemfile b/puppet/modules/vcsrepo/Gemfile
new file mode 100644
index 00000000..e490bc9b
--- /dev/null
+++ b/puppet/modules/vcsrepo/Gemfile
@@ -0,0 +1,39 @@
+#This file is generated by ModuleSync, do not edit.
+
+source ENV['GEM_SOURCE'] || "https://rubygems.org"
+
+def location_for(place, version = nil)
+ if place =~ /^(git[:@][^#]*)#(.*)/
+ [version, { :git => $1, :branch => $2, :require => false}].compact
+ elsif place =~ /^file:\/\/(.*)/
+ ['>= 0', { :path => File.expand_path($1), :require => false}]
+ else
+ [place, version, { :require => false}].compact
+ end
+end
+
+group :development, :unit_tests do
+ gem 'json', :require => false
+ gem 'metadata-json-lint', :require => false
+ gem 'puppet_facts', :require => false
+ gem 'puppet-blacksmith', :require => false
+ gem 'puppetlabs_spec_helper', :require => false
+ gem 'rspec-puppet', '>= 2.3.2', :require => false
+ gem 'simplecov', :require => false
+end
+group :system_tests do
+ gem 'beaker-rspec', *location_for(ENV['BEAKER_RSPEC_VERSION'] || '>= 3.4')
+ gem 'beaker', *location_for(ENV['BEAKER_VERSION'])
+ gem 'serverspec', :require => false
+ gem 'beaker-puppet_install_helper', :require => false
+ gem 'master_manipulator', :require => false
+ gem 'beaker-hostgenerator', *location_for(ENV['BEAKER_HOSTGENERATOR_VERSION'])
+end
+
+gem 'facter', *location_for(ENV['FACTER_GEM_VERSION'])
+gem 'puppet', *location_for(ENV['PUPPET_GEM_VERSION'])
+
+
+if File.exists? "#{__FILE__}.local"
+ eval(File.read("#{__FILE__}.local"), binding)
+end
diff --git a/puppet/modules/vcsrepo/LICENSE b/puppet/modules/vcsrepo/LICENSE
new file mode 100644
index 00000000..d159169d
--- /dev/null
+++ b/puppet/modules/vcsrepo/LICENSE
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/puppet/modules/vcsrepo/NOTICE b/puppet/modules/vcsrepo/NOTICE
new file mode 100644
index 00000000..7eab165b
--- /dev/null
+++ b/puppet/modules/vcsrepo/NOTICE
@@ -0,0 +1,20 @@
+vcsrepo puppet module
+
+Copyright (C) 2010-2012 Puppet Labs Inc.
+
+Puppet Labs can be contacted at: info@puppetlabs.com
+
+
+This program and entire repository is free software; you can
+redistribute it and/or modify it under the terms of the GNU
+General Public License as published by the Free Software
+Foundation; either version 2 of the License, or any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
diff --git a/puppet/modules/vcsrepo/README.markdown b/puppet/modules/vcsrepo/README.markdown
new file mode 100644
index 00000000..ffc2d7e0
--- /dev/null
+++ b/puppet/modules/vcsrepo/README.markdown
@@ -0,0 +1,758 @@
+#vcsrepo
+
+####Table of Contents
+
+1. [Overview](#overview)
+2. [Module Description - What the module does and why it is useful](#module-description)
+3. [Setup - The basics of getting started with vcsrepo](#setup)
+ * [Setup requirements](#setup-requirements)
+ * [Beginning with vcsrepo](#beginning-with-vcsrepo)
+4. [Usage - Configuration options and additional functionality](#usage)
+ * [Git](#git)
+ * [Bazaar](#bazaar)
+ * [CVS](#cvs)
+ * [Mercurial](#mercurial)
+ * [Perforce](#perforce)
+ * [Subversion](#subversion)
+5. [Reference - An under-the-hood peek at what the module is doing and how](#reference)
+ * [Type: vcsrepo](#type-vcsrepo)
+ * [Providers](#providers)
+ * [Features](#features)
+ * [Parameters](#parameters)
+5. [Limitations - OS compatibility, etc.](#limitations)
+6. [Development - Guide for contributing to the module](#development)
+
+##Overview
+
+The vcsrepo module lets you use Puppet to easily deploy content from your version control system (VCS).
+
+##Module Description
+
+The vcsrepo module provides a single type with providers to support the following version control systems:
+
+* [Git](#git)
+* [Bazaar](#bazaar)
+* [CVS](#cvs)
+* [Mercurial](#mercurial)
+* [Perforce](#perforce)
+* [Subversion](#subversion)
+
+**Note:** `git` is the only vcs provider officially [supported by Puppet Labs](https://forge.puppetlabs.com/supported).
+
+##Setup
+
+###Setup Requirements
+
+The `vcsrepo` module does not install any VCS software for you. You must install a VCS before you can use this module.
+
+Like Puppet in general, the `vcsrepo` module does not automatically create parent directories for the files it manages. Make sure to set up any needed directory structures before you get started.
+
+###Beginning with vcsrepo
+
+To create and manage a blank repository, define the type `vcsrepo` with a path to your repository and supply the `provider` parameter based on the [VCS you're using](#usage).
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => present,
+ provider => git,
+}
+~~~
+
+##Usage
+
+**Note:** `git` is the only vcsrepo provider officially [supported by Puppet Labs](https://forge.puppetlabs.com/supported).
+
+###Git
+
+####Create a blank repository
+
+To create a blank repository, suitable for use as a central repository, define `vcsrepo` without `source` or `revision`:
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => present,
+ provider => git,
+}
+~~~
+
+If you're managing a central or official repository, you might want to make it a bare repository. To do this, set `ensure` to 'bare':
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => bare,
+ provider => git,
+}
+~~~
+
+####Clone/pull a repository
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => present,
+ provider => git,
+ source => 'git://example.com/repo.git',
+}
+~~~
+
+If you want to clone your repository as bare or mirror, you can set `ensure` to 'bare' or 'mirror':
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => mirror,
+ provider => git,
+ source => 'git://example.com/repo.git',
+}
+~~~
+
+By default, `vcsrepo` will use the HEAD of the source repository's master branch. To use another branch or a specific commit, set `revision` to either a branch name or a commit SHA or tag.
+
+Branch name:
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => present,
+ provider => git,
+ source => 'git://example.com/repo.git',
+ revision => 'development',
+}
+~~~
+
+SHA:
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => present,
+ provider => git,
+ source => 'git://example.com/repo.git',
+ revision => '0c466b8a5a45f6cd7de82c08df2fb4ce1e920a31',
+}
+~~~
+
+Tag:
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => present,
+ provider => git,
+ source => 'git://example.com/repo.git',
+ revision => '1.1.2rc1',
+}
+~~~
+
+To check out a branch as a specific user, supply the `user` parameter:
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => present,
+ provider => git,
+ source => 'git://example.com/repo.git',
+ revision => '0c466b8a5a45f6cd7de82c08df2fb4ce1e920a31',
+ user => 'someUser',
+}
+~~~
+
+To keep the repository at the latest revision, set `ensure` to 'latest'.
+
+**WARNING:** this overwrites any local changes to the repository:
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => latest,
+ provider => git,
+ source => 'git://example.com/repo.git',
+ revision => 'master',
+}
+~~~
+
+To clone the repository but skip initializing submodules, set `submodules` to 'false':
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => latest,
+ provider => git,
+ source => 'git://example.com/repo.git',
+ submodules => false,
+}
+~~~
+
+####Use multiple remotes with a repository
+In place of a single string, you can set `source` to a hash of one or more name => URL pairs:
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => present,
+ provider => git,
+ remote => 'origin'
+ source => {
+ 'origin' => 'https://github.com/puppetlabs/puppetlabs-vcsrepo.git',
+ 'other_remote' => 'https://github.com/other_user/puppetlabs-vcsrepo.git'
+ },
+}
+~~~
+
+**Note:** if you set `source` to a hash, one of the names you specify must match the value of the `remote` parameter. That remote serves as the upstream of your managed repository.
+
+####Connect via SSH
+
+To connect to your source repository via SSH (e.g., 'username@server:…'), we recommend managing your SSH keys with Puppet and using the [`require`](http://docs.puppetlabs.com/references/stable/metaparameter.html#require) metaparameter to make sure they are present before the `vcsrepo` resource is applied.
+
+To use SSH keys associated with a user, specify the username in the `user` parameter:
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => latest,
+ provider => git,
+ source => 'git://username@example.com/repo.git',
+ user => 'toto', #uses toto's $HOME/.ssh setup
+ require => File['/home/toto/.ssh/id_rsa'],
+}
+~~~
+
+###Bazaar
+
+####Create a blank repository
+
+To create a blank repository, suitable for use as a central repository, define `vcsrepo` without `source` or `revision`:
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => present,
+ provider => bzr,
+}
+~~~
+
+####Branch from an existing repository
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => present,
+ provider => bzr,
+ source => '/some/path',
+}
+~~~
+
+To branch from a specific revision, set `revision` to a valid [Bazaar revision spec](http://wiki.bazaar.canonical.com/BzrRevisionSpec):
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => present,
+ provider => bzr,
+ source => '/some/path',
+ revision => 'menesis@pov.lt-20100309191856-4wmfqzc803fj300x',
+}
+~~~
+
+####Connect via SSH
+
+To connect to your source repository via SSH (e.g., `'bzr+ssh://...'` or `'sftp://...,'`), we recommend using the [`require`](http://docs.puppetlabs.com/references/stable/metaparameter.html#require) metaparameter to make sure your SSH keys are present before the `vcsrepo` resource is applied:
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => latest,
+ provider => bzr,
+ source => 'bzr+ssh://bzr.example.com/some/path',
+ user => 'toto', #uses toto's $HOME/.ssh setup
+ require => File['/home/toto/.ssh/id_rsa'],
+}
+~~~
+
+###CVS
+
+####Create a blank repository
+
+To create a blank repository, suitable for use as a central repository, define `vcsrepo` without `source` or `revision`:
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => present,
+ provider => cvs,
+}
+~~~
+
+####Checkout/update from a repository
+
+~~~
+vcsrepo { '/path/to/workspace':
+ ensure => present,
+ provider => cvs,
+ source => ':pserver:anonymous@example.com:/sources/myproj',
+}
+~~~
+
+To get a specific module on the current mainline, supply the `module` parameter:
+
+~~~
+vcsrepo {'/vagrant/lockss-daemon-source':
+ ensure => present,
+ provider => cvs,
+ source => ':pserver:anonymous@lockss.cvs.sourceforge.net:/cvsroot/lockss',
+ module => 'lockss-daemon',
+}
+~~~
+
+To set the GZIP compression levels for your repository history, use the `compression` parameter:
+
+~~~
+vcsrepo { '/path/to/workspace':
+ ensure => present,
+ provider => cvs,
+ compression => 3,
+ source => ':pserver:anonymous@example.com:/sources/myproj',
+}
+~~~
+
+To get a specific revision, set `revision` to the revision number.
+
+~~~
+vcsrepo { '/path/to/workspace':
+ ensure => present,
+ provider => cvs,
+ compression => 3,
+ source => ':pserver:anonymous@example.com:/sources/myproj',
+ revision => '1.2',
+}
+~~~
+
+You can also set `revision` to a tag:
+
+~~~
+vcsrepo { '/path/to/workspace':
+ ensure => present,
+ provider => cvs,
+ compression => 3,
+ source => ':pserver:anonymous@example.com:/sources/myproj',
+ revision => 'SOMETAG',
+}
+~~~
+
+####Connect via SSH
+
+To connect to your source repository via SSH, we recommend using the [`require`](http://docs.puppetlabs.com/references/stable/metaparameter.html#require) metaparameter to make sure your SSH keys are present before the `vcsrepo` resource is applied:
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => latest,
+ provider => cvs,
+ source => ':pserver:anonymous@example.com:/sources/myproj',
+ user => 'toto', #uses toto's $HOME/.ssh setup
+ require => File['/home/toto/.ssh/id_rsa'],
+}
+~~~
+
+###Mercurial
+
+####Create a blank repository
+
+To create a blank repository, suitable for use as a central repository, define `vcsrepo` without `source` or `revision`:
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => present,
+ provider => hg,
+}
+~~~
+
+####Clone/pull & update a repository
+
+To get the default branch tip:
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => present,
+ provider => hg,
+ source => 'http://hg.example.com/myrepo',
+}
+~~~
+
+For a specific changeset, use `revision`:
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => present,
+ provider => hg,
+ source => 'http://hg.example.com/myrepo',
+ revision => '21ea4598c962',
+}
+~~~
+
+You can also set `revision` to a tag:
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => present,
+ provider => hg,
+ source => 'http://hg.example.com/myrepo',
+ revision => '1.1.2',
+}
+~~~
+
+To check out as a specific user:
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => present,
+ provider => hg,
+ source => 'http://hg.example.com/myrepo',
+ user => 'user',
+}
+~~~
+
+To specify an SSH identity key:
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => present,
+ provider => hg,
+ source => 'ssh://hg@hg.example.com/myrepo',
+ identity => '/home/user/.ssh/id_dsa1,
+}
+~~~
+
+To specify a username and password for HTTP Basic authentication:
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => latest,
+ provider => hg,
+ source => 'http://hg.example.com/myrepo',
+ basic_auth_username => 'hgusername',
+ basic_auth_password => 'hgpassword',
+}
+~~~
+
+####Connect via SSH
+
+To connect to your source repository via SSH (e.g., `'ssh://...'`), we recommend using the [`require` metaparameter](http://docs.puppetlabs.com/references/stable/metaparameter.html#require) to make sure your SSH keys are present before the `vcsrepo` resource is applied:
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => latest,
+ provider => hg,
+ source => 'ssh://hg.example.com//path/to/myrepo',
+ user => 'toto', #uses toto's $HOME/.ssh setup
+ require => File['/home/toto/.ssh/id_rsa'],
+}
+~~~
+
+###Perforce
+
+####Create an empty workspace
+
+To set up the connection to your Perforce service, set `p4config` to the location of a valid Perforce [config file](http://www.perforce.com/perforce/doc.current/manuals/p4guide/chapter.configuration.html#configuration.settings.configfiles) stored on the node:
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => present,
+ provider => p4,
+ p4config => '/root/.p4config'
+}
+~~~
+
+**Note:** If you don't include the `P4CLIENT` setting in your config file, the provider generates a workspace name based on the digest of `path` and the node's hostname (e.g., `puppet-91bc00640c4e5a17787286acbe2c021c`):
+
+####Create/update and sync a Perforce workspace
+
+To sync a depot path to head, set `ensure` to 'latest':
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => latest,
+ provider => p4,
+ source => '//depot/branch/...'
+}
+~~~
+
+To sync to a specific changelist, specify its revision number with the `revision` parameter:
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => present,
+ provider => p4,
+ source => '//depot/branch/...',
+ revision => '2341'
+}
+~~~
+
+You can also set `revision` to a label:
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => present,
+ provider => p4,
+ source => '//depot/branch/...',
+ revision => 'my_label'
+}
+~~~
+
+###Subversion
+
+####Create a blank repository
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => present,
+ provider => svn,
+}
+~~~
+
+####Check out from an existing repository
+
+Provide a `source` pointing to the branch or tag you want to check out:
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => present,
+ provider => svn,
+ source => 'svn://svnrepo/hello/branches/foo',
+}
+~~~
+
+You can also designate a specific revision:
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => present,
+ provider => svn,
+ source => 'svn://svnrepo/hello/branches/foo',
+ revision => '1234',
+}
+~~~
+
+####Use a specific Subversion configuration directory
+
+Use the `configuration` parameter to designate the directory that contains your Subversion configuration files (typically, '/path/to/.subversion'):
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => present,
+ provider => svn,
+ source => 'svn://svnrepo/hello/branches/foo',
+ configuration => '/path/to/.subversion',
+}
+~~~
+
+####Connect via SSH
+
+To connect to your source repository via SSH (e.g., `'svn+ssh://...'`), we recommend using the [`require` metaparameter](http://docs.puppetlabs.com/references/stable/metaparameter.html#require) to make sure your SSH keys are present before the `vcsrepo` resource is applied:
+
+~~~
+vcsrepo { '/path/to/repo':
+ ensure => latest,
+ provider => svn,
+ source => 'svn+ssh://svnrepo/hello/branches/foo',
+ user => 'toto', #uses toto's $HOME/.ssh setup
+ require => File['/home/toto/.ssh/id_rsa'],
+}
+~~~
+
+##Reference
+
+###Type: vcsrepo
+
+The vcsrepo module adds only one type with several providers. Each provider abstracts a different VCS, and each provider includes a set of features according to its needs.
+
+####Providers
+
+**Note:** Not all features are available with all providers.
+
+#####`git` - Supports the Git VCS.
+
+Features: `bare_repositories`, `depth`, `multiple_remotes`, `reference_tracking`, `ssh_identity`, `submodules`, `user`
+
+Parameters: `depth`, `ensure`, `excludes`, `force`, `group`, `identity`, `owner`, `path`, `provider`, `remote`, `revision`, `source`, `user`
+
+#####`bzr` - Supports the Bazaar VCS.
+
+Features: `reference_tracking`
+
+Parameters: `ensure`, `excludes`, `force`, `group`, `owner`, `path`, `provider`, `revision`, `source`
+
+#####`cvs` - Supports the CVS VCS.
+
+Features: `cvs_rsh`, `gzip_compression`, `modules`, `reference_tracking`, `user`
+
+Parameters: `compression`, `cvs_rsh`, `ensure`, `excludes`, `force`, `group`, `module`, `owner`, `path`, `provider`
+
+#####`hg` - Supports the Mercurial VCS.
+
+Features: `reference_tracking`, `ssh_identity`, `user`
+
+Parameters: `ensure`, `excludes`, `force`, `group`, `identity`, `owner`, `path`, `provider`, `revision`, `source`, `user`
+
+#####`p4` - Supports the Perforce VCS.
+
+Features: `p4config`, `reference_tracking`
+
+Parameters: `ensure`, `excludes`, `force`, `group`, `owner`, `p4config`, `path`, `provider`, `revision`, `source`
+
+#####`svn` - Supports the Subversion VCS.
+
+Features: `basic_auth`, `configuration`, `conflict`, `depth`, `filesystem_types`, `reference_tracking`
+
+Parameters: `basic_auth_password`, `basic_auth_username`, `configuration`, `conflict`, `ensure`, `excludes`, `force`, `fstype`, `group`, `owner`, `path`, `provider`, `revision`, `source`, `trust_server_cert`
+
+####Features
+
+**Note:** Not all features are available with all providers.
+
+* `bare_repositories` - Differentiates between bare repositories and those with working copies. (Available with `git`.)
+* `basic_auth` - Supports HTTP Basic authentication. (Available with `svn`.)
+* `conflict` - Lets you decide how to resolve any conflicts between the source repository and your working copy. (Available with `svn`.)
+* `configuration` - Lets you specify the location of your configuration files. (Available with `svn`.)
+* `cvs_rsh` - Understands the `CVS_RSH` environment variable. (Available with `cvs`.)
+* `depth` - Supports shallow clones in `git` or sets scope limit in `svn`. (Available with `git` and `svn`.)
+* `filesystem_types` - Supports multiple types of filesystem. (Available with `svn`.)
+* `gzip_compression` - Supports explicit GZip compression levels. (Available with `cvs`.)
+* `modules` - Lets you choose a specific repository module. (Available with `cvs`.)
+* `multiple_remotes` - Tracks multiple remote repositories. (Available with `git`.)
+* `reference_tracking` - Lets you track revision references that can change over time (e.g., some VCS tags and branch names). (Available with all providers)
+* `ssh_identity` - Lets you specify an SSH identity file. (Available with `git` and `hg`.)
+* `user` - Can run as a different user. (Available with `git`, `hg` and `cvs`.)
+* `p4config` - Supports setting the `P4CONFIG` environment. (Available with `p4`.)
+* `submodules` - Supports repository submodules which can be optionally initialized. (Available with `git`.)
+
+####Parameters
+
+All parameters are optional, except where specified otherwise.
+
+##### `basic_auth_password`
+
+Specifies the password for HTTP Basic authentication. (Requires the `basic_auth` feature.) Valid options: a string. Default: none.
+
+##### `basic_auth_username`
+
+Specifies the username for HTTP Basic authentication. (Requires the `basic_auth` feature.) Valid options: a string. Default: none.
+
+##### `compression`
+
+Sets the GZIP compression level for the repository history. (Requires the `gzip_compression` feature.) Valid options: an integer between 0 and 6. Default: none.
+
+##### `configuration`
+
+Sets the configuration directory to use. (Requires the `configuration` feature.) Valid options: a string containing an absolute path. Default: none.
+
+##### `conflict`
+
+Tells Subversion how to resolve any conflicts between the source repository and your working copy. (Requires the `conflict` feature.) Valid options: 'base', 'mine-full', 'theirs-full', and 'working'. Default: none.
+
+##### `cvs_rsh`
+
+Provides a value for the `CVS_RSH` environment variable. (Requires the `cvs_rsh` feature.) Valid options: a string. Default: none.
+
+##### `depth`
+
+In `git` sets the number of commits to include when creating a shallow clone. (Requires the `depth` feature.) Valid options: an integer. Default: none.
+
+In `svn` instructs Subversion to limit the scope of an operation to a particular tree depth. (Requires the `depth` feature.) Valid options: 'empty', 'files', 'immediates', 'infinity'. Default: none.
+
+##### `ensure`
+
+Specifies whether the repository should exist. Valid options: 'present', 'bare', 'absent', and 'latest'. Default: 'present'.
+
+##### `excludes`
+
+Lists any files the repository shouldn't track (similar to .gitignore). Valid options: a string (separate multiple values with the newline character). Default: none.
+
+##### `force`
+
+Specifies whether to delete any existing files in the repository path if creating a new repository. **Use with care.** Valid options: 'true' and 'false'. Default: 'false'.
+
+##### `fstype`
+
+Sets the filesystem type. (Requires the `filesystem_types` feature.) Valid options: 'fsfs' or 'bdb'. Default: none.
+
+##### `group`
+
+Specifies a group to own the repository files. Valid options: a string containing a group name or GID. Default: none.
+
+##### `identity`
+
+Specifies an identity file to use for SSH authentication. (Requires the `ssh_identity` feature.) Valid options: a string containing an absolute path. Default: none.
+
+##### `module`
+
+Specifies the repository module to manage. (Requires the `modules` feature.) Valid options: a string containing the name of a CVS module. Default: none.
+
+##### `owner`
+
+Specifies a user to own the repository files. Valid options: a string containing a username or UID. Default: none.
+
+##### `p4config`
+
+Specifies a config file that contains settings for connecting to the Perforce service. (Requires the `p4config` feature.) Valid options: a string containing the absolute path to a valid [Perforce config file](http://www.perforce.com/perforce/doc.current/manuals/p4guide/chapter.configuration.html#configuration.settings.configfiles). Default: none.
+
+##### `path`
+
+Specifies a location for the managed repository. Valid options: a string containing an absolute path. Default: the title of your declared resource.
+
+##### `provider`
+
+*Required.* Specifies the backend to use for this vcsrepo resource. Valid options: 'bzr', 'cvs', 'git', 'hg', 'p4', and 'svn'.
+
+##### `remote`
+
+Specifies the remote repository to track. (Requires the `multiple_remotes` feature.) Valid options: a string containing one of the remote names specified in `source`. Default: 'origin'.
+
+##### `revision`
+
+Sets the revision of the repository. Valid options vary by provider:
+
+* `git` - a string containing a Git branch name, or a commit SHA or tag
+* `bzr` - a string containing a Bazaar [revision spec](http://wiki.bazaar.canonical.com/BzrRevisionSpec)
+* `cvs` - a string containing a CVS [tag or revision number](http://www.thathost.com/wincvs-howto/cvsdoc/cvs_4.html)
+* `hg` - a string containing a Mercurial [changeset ID](http://mercurial.selenic.com/wiki/ChangeSetID) or [tag](http://mercurial.selenic.com/wiki/Tag)
+* `p4` - a string containing a Perforce [change number, label name, client name, or date spec](http://www.perforce.com/perforce/r12.1/manuals/cmdref/o.fspecs.html)
+* `svn` - a string containing a Subversion [revision number](http://svnbook.red-bean.com/en/1.7/svn.basic.in-action.html#svn.basic.in-action.revs), [revision keyword, or revision date](http://svnbook.red-bean.com/en/1.7/svn.tour.revs.specifiers.html)
+
+Default: none.
+
+##### `source`
+
+Specifies a source repository to serve as the upstream for your managed repository. Default: none. Valid options vary by provider:
+
+* `git` - a string containing a [Git repository URL](https://www.kernel.org/pub/software/scm/git/docs/git-clone.html#_git_urls_a_id_urls_a) or a hash of name => URL mappings. See also [`remote`](#remote).
+* `bzr` - a string containing a Bazaar branch location
+* `cvs` - a string containing a CVS root
+* `hg` - a string containing the local path or URL of a Mercurial repository
+* `p4` - a string containing a Perforce depot path
+* `svn` - a string containing a Subversion repository URL
+
+Default: none.
+
+##### `submodules`
+
+Specifies whether to initialize and update each submodule in the repository. (Requires the `submodules` feature.) Valid options: 'true' and 'false'. Default: 'true'.
+
+##### `trust_server_cert`
+
+Instructs Subversion to accept SSL server certificates issued by unknown certificate authorities. Valid options: 'true' and 'false'. Default: 'false'.
+
+##### `user`
+
+Specifies the user to run as for repository operations. (Requires the `user` feature.) Valid options: a string containing a username or UID. Default: none.
+
+##Limitations
+
+Git is the only VCS provider officially [supported](https://forge.puppetlabs.com/supported) by Puppet Labs.
+
+This module has been tested with Puppet 2.7 and higher.
+
+The module has been tested on:
+
+* CentOS 5/6/7
+* Debian 6/7
+* Oracle 5/6/7
+* Red Hat Enterprise Linux 5/6/7
+* Scientific Linux 5/6/7
+* SLES 10/11/12
+* Ubuntu 10.04/12.04/14.04
+
+Testing on other platforms has been light and cannot be guaranteed.
+
+##Development
+
+Puppet Labs modules on the Puppet Forge are open projects, and community contributions are essential for keeping them great. We can't access the huge number of platforms and myriad of hardware, software, and deployment configurations that Puppet is intended to serve.
+
+We want to keep it as easy as possible to contribute changes so that our modules work in your environment. There are a few guidelines that we need contributors to follow so that we can have a chance of keeping on top of things.
+
+You can read the complete module contribution guide [on the Puppet Labs wiki.](http://projects.puppetlabs.com/projects/module-site/wiki/Module_contributing)
diff --git a/puppet/modules/vcsrepo/Rakefile b/puppet/modules/vcsrepo/Rakefile
new file mode 100755
index 00000000..7e9a13d5
--- /dev/null
+++ b/puppet/modules/vcsrepo/Rakefile
@@ -0,0 +1,42 @@
+require 'puppet_blacksmith/rake_tasks'
+require 'puppet-lint/tasks/puppet-lint'
+require 'puppetlabs_spec_helper/rake_tasks'
+
+PuppetLint.configuration.fail_on_warnings = true
+PuppetLint.configuration.send('relative')
+PuppetLint.configuration.send('disable_80chars')
+PuppetLint.configuration.send('disable_class_inherits_from_params_class')
+PuppetLint.configuration.send('disable_documentation')
+PuppetLint.configuration.send('disable_single_quote_string_with_variables')
+PuppetLint.configuration.ignore_paths = ["spec/**/*.pp", "pkg/**/*.pp"]
+
+desc 'Generate pooler nodesets'
+task :gen_nodeset do
+ require 'beaker-hostgenerator'
+ require 'securerandom'
+ require 'fileutils'
+
+ agent_target = ENV['TEST_TARGET']
+ if ! agent_target
+ STDERR.puts 'TEST_TARGET environment variable is not set'
+ STDERR.puts 'setting to default value of "redhat-64default."'
+ agent_target = 'redhat-64default.'
+ end
+
+ master_target = ENV['MASTER_TEST_TARGET']
+ if ! master_target
+ STDERR.puts 'MASTER_TEST_TARGET environment variable is not set'
+ STDERR.puts 'setting to default value of "redhat7-64mdcl"'
+ master_target = 'redhat7-64mdcl'
+ end
+
+ targets = "#{master_target}-#{agent_target}"
+ cli = BeakerHostGenerator::CLI.new([targets])
+ nodeset_dir = "tmp/nodesets"
+ nodeset = "#{nodeset_dir}/#{targets}-#{SecureRandom.uuid}.yaml"
+ FileUtils.mkdir_p(nodeset_dir)
+ File.open(nodeset, 'w') do |fh|
+ fh.print(cli.execute)
+ end
+ puts nodeset
+end
diff --git a/puppet/modules/vcsrepo/examples/bzr/branch.pp b/puppet/modules/vcsrepo/examples/bzr/branch.pp
new file mode 100644
index 00000000..0ed0705e
--- /dev/null
+++ b/puppet/modules/vcsrepo/examples/bzr/branch.pp
@@ -0,0 +1,6 @@
+vcsrepo { '/tmp/vcstest-bzr-branch':
+ ensure => present,
+ provider => bzr,
+ source => 'lp:do',
+ revision => '1312',
+}
diff --git a/puppet/modules/vcsrepo/examples/bzr/init_repo.pp b/puppet/modules/vcsrepo/examples/bzr/init_repo.pp
new file mode 100644
index 00000000..1129dd7d
--- /dev/null
+++ b/puppet/modules/vcsrepo/examples/bzr/init_repo.pp
@@ -0,0 +1,4 @@
+vcsrepo { '/tmp/vcstest-bzr-init':
+ ensure => present,
+ provider => bzr,
+}
diff --git a/puppet/modules/vcsrepo/examples/cvs/local.pp b/puppet/modules/vcsrepo/examples/cvs/local.pp
new file mode 100644
index 00000000..155742e3
--- /dev/null
+++ b/puppet/modules/vcsrepo/examples/cvs/local.pp
@@ -0,0 +1,11 @@
+vcsrepo { '/tmp/vcstest-cvs-repo':
+ ensure => present,
+ provider => cvs,
+}
+
+vcsrepo { '/tmp/vcstest-cvs-workspace-local':
+ ensure => present,
+ provider => cvs,
+ source => '/tmp/vcstest-cvs-repo',
+ require => Vcsrepo['/tmp/vcstest-cvs-repo'],
+}
diff --git a/puppet/modules/vcsrepo/examples/cvs/remote.pp b/puppet/modules/vcsrepo/examples/cvs/remote.pp
new file mode 100644
index 00000000..eb9665a9
--- /dev/null
+++ b/puppet/modules/vcsrepo/examples/cvs/remote.pp
@@ -0,0 +1,5 @@
+vcsrepo { '/tmp/vcstest-cvs-workspace-remote':
+ ensure => present,
+ provider => cvs,
+ source => ':pserver:anonymous@cvs.sv.gnu.org:/sources/leetcvrt',
+}
diff --git a/puppet/modules/vcsrepo/examples/git/bare_init.pp b/puppet/modules/vcsrepo/examples/git/bare_init.pp
new file mode 100644
index 00000000..4166f6e6
--- /dev/null
+++ b/puppet/modules/vcsrepo/examples/git/bare_init.pp
@@ -0,0 +1,4 @@
+vcsrepo { '/tmp/vcstest-git-bare':
+ ensure => bare,
+ provider => git,
+}
diff --git a/puppet/modules/vcsrepo/examples/git/clone.pp b/puppet/modules/vcsrepo/examples/git/clone.pp
new file mode 100644
index 00000000..b29a4fdb
--- /dev/null
+++ b/puppet/modules/vcsrepo/examples/git/clone.pp
@@ -0,0 +1,5 @@
+vcsrepo { '/tmp/vcstest-git-clone':
+ ensure => present,
+ provider => git,
+ source => 'git://github.com/bruce/rtex.git',
+}
diff --git a/puppet/modules/vcsrepo/examples/git/shallow-clone-with-just-one-commit.pp b/puppet/modules/vcsrepo/examples/git/shallow-clone-with-just-one-commit.pp
new file mode 100644
index 00000000..cd5a05db
--- /dev/null
+++ b/puppet/modules/vcsrepo/examples/git/shallow-clone-with-just-one-commit.pp
@@ -0,0 +1,7 @@
+vcsrepo { '/tmp/git':
+ ensure => 'present',
+ provider => 'git',
+ source => 'https://github.com/git/git.git',
+ branch => 'v2.2.0',
+ depth => 1,
+}
diff --git a/puppet/modules/vcsrepo/examples/git/working_copy_init.pp b/puppet/modules/vcsrepo/examples/git/working_copy_init.pp
new file mode 100644
index 00000000..e3352eb7
--- /dev/null
+++ b/puppet/modules/vcsrepo/examples/git/working_copy_init.pp
@@ -0,0 +1,4 @@
+vcsrepo { '/tmp/vcstest-git-wc':
+ ensure => present,
+ provider => git,
+}
diff --git a/puppet/modules/vcsrepo/examples/hg/clone.pp b/puppet/modules/vcsrepo/examples/hg/clone.pp
new file mode 100644
index 00000000..be2d955d
--- /dev/null
+++ b/puppet/modules/vcsrepo/examples/hg/clone.pp
@@ -0,0 +1,6 @@
+vcsrepo { '/tmp/vcstest-hg-clone':
+ ensure => present,
+ provider => hg,
+ source => 'http://hg.basho.com/riak',
+ revision => 'riak-0.5.3',
+}
diff --git a/puppet/modules/vcsrepo/examples/hg/clone_basic_auth.pp b/puppet/modules/vcsrepo/examples/hg/clone_basic_auth.pp
new file mode 100644
index 00000000..984f8eaf
--- /dev/null
+++ b/puppet/modules/vcsrepo/examples/hg/clone_basic_auth.pp
@@ -0,0 +1,7 @@
+vcsrepo { '/path/to/repo':
+ ensure => latest,
+ provider => 'hg',
+ source => 'http://hg.example.com/myrepo',
+ basic_auth_username => 'hgusername',
+ basic_auth_password => 'hgpassword',
+}
diff --git a/puppet/modules/vcsrepo/examples/hg/init_repo.pp b/puppet/modules/vcsrepo/examples/hg/init_repo.pp
new file mode 100644
index 00000000..a8908040
--- /dev/null
+++ b/puppet/modules/vcsrepo/examples/hg/init_repo.pp
@@ -0,0 +1,4 @@
+vcsrepo { '/tmp/vcstest-hg-init':
+ ensure => present,
+ provider => hg,
+}
diff --git a/puppet/modules/vcsrepo/examples/p4/create_client.pp b/puppet/modules/vcsrepo/examples/p4/create_client.pp
new file mode 100644
index 00000000..3cf91602
--- /dev/null
+++ b/puppet/modules/vcsrepo/examples/p4/create_client.pp
@@ -0,0 +1,4 @@
+vcsrepo { '/tmp/vcstest/p4_client_root':
+ ensure => present,
+ provider => 'p4',
+}
diff --git a/puppet/modules/vcsrepo/examples/p4/delete_client.pp b/puppet/modules/vcsrepo/examples/p4/delete_client.pp
new file mode 100644
index 00000000..82c9c952
--- /dev/null
+++ b/puppet/modules/vcsrepo/examples/p4/delete_client.pp
@@ -0,0 +1,4 @@
+vcsrepo { '/tmp/vcstest/p4_client_root':
+ ensure => absent,
+ provider => 'p4',
+}
diff --git a/puppet/modules/vcsrepo/examples/p4/latest_client.pp b/puppet/modules/vcsrepo/examples/p4/latest_client.pp
new file mode 100644
index 00000000..106ef9e9
--- /dev/null
+++ b/puppet/modules/vcsrepo/examples/p4/latest_client.pp
@@ -0,0 +1,5 @@
+vcsrepo { '/tmp/vcstest/p4_client_root':
+ ensure => latest,
+ provider => 'p4',
+ source => '//depot/...',
+}
diff --git a/puppet/modules/vcsrepo/examples/p4/sync_client.pp b/puppet/modules/vcsrepo/examples/p4/sync_client.pp
new file mode 100644
index 00000000..33e47317
--- /dev/null
+++ b/puppet/modules/vcsrepo/examples/p4/sync_client.pp
@@ -0,0 +1,6 @@
+vcsrepo { '/tmp/vcstest/p4_client_root':
+ ensure => present,
+ provider => 'p4',
+ source => '//depot/...',
+ revision => '30',
+}
diff --git a/puppet/modules/vcsrepo/examples/svn/checkout.pp b/puppet/modules/vcsrepo/examples/svn/checkout.pp
new file mode 100644
index 00000000..f9fc2730
--- /dev/null
+++ b/puppet/modules/vcsrepo/examples/svn/checkout.pp
@@ -0,0 +1,5 @@
+vcsrepo { '/tmp/vcstest-svn-checkout':
+ ensure => present,
+ provider => svn,
+ source => 'http://svn.edgewall.org/repos/babel/trunk',
+}
diff --git a/puppet/modules/vcsrepo/examples/svn/server.pp b/puppet/modules/vcsrepo/examples/svn/server.pp
new file mode 100644
index 00000000..de7c390f
--- /dev/null
+++ b/puppet/modules/vcsrepo/examples/svn/server.pp
@@ -0,0 +1,4 @@
+vcsrepo { '/tmp/vcstest-svn-server':
+ ensure => present,
+ provider => svn,
+}
diff --git a/puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo.rb b/puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo.rb
new file mode 100644
index 00000000..8793e632
--- /dev/null
+++ b/puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo.rb
@@ -0,0 +1,42 @@
+require 'tmpdir'
+require 'digest/md5'
+require 'fileutils'
+
+# Abstract
+class Puppet::Provider::Vcsrepo < Puppet::Provider
+
+ private
+
+ def set_ownership
+ owner = @resource.value(:owner) || nil
+ group = @resource.value(:group) || nil
+ FileUtils.chown_R(owner, group, @resource.value(:path))
+ end
+
+ def path_exists?
+ File.directory?(@resource.value(:path))
+ end
+
+ def path_empty?
+ # Path is empty if the only entries are '.' and '..'
+ d = Dir.new(@resource.value(:path))
+ d.read # should return '.'
+ d.read # should return '..'
+ d.read.nil?
+ end
+
+ # Note: We don't rely on Dir.chdir's behavior of automatically returning the
+ # value of the last statement -- for easier stubbing.
+ def at_path(&block) #:nodoc:
+ value = nil
+ Dir.chdir(@resource.value(:path)) do
+ value = yield
+ end
+ value
+ end
+
+ def tempdir
+ @tempdir ||= File.join(Dir.tmpdir, 'vcsrepo-' + Digest::MD5.hexdigest(@resource.value(:path)))
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo/bzr.rb b/puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo/bzr.rb
new file mode 100644
index 00000000..797d84d2
--- /dev/null
+++ b/puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo/bzr.rb
@@ -0,0 +1,93 @@
+require File.join(File.dirname(__FILE__), '..', 'vcsrepo')
+
+Puppet::Type.type(:vcsrepo).provide(:bzr, :parent => Puppet::Provider::Vcsrepo) do
+ desc "Supports Bazaar repositories"
+
+ commands :bzr => 'bzr'
+ has_features :reference_tracking
+
+ def create
+ if !@resource.value(:source)
+ create_repository(@resource.value(:path))
+ else
+ clone_repository(@resource.value(:revision))
+ end
+ end
+
+ def working_copy_exists?
+ File.directory?(File.join(@resource.value(:path), '.bzr'))
+ end
+
+ def exists?
+ working_copy_exists?
+ end
+
+ def destroy
+ FileUtils.rm_rf(@resource.value(:path))
+ end
+
+ def revision
+ at_path do
+ current_revid = bzr('version-info')[/^revision-id:\s+(\S+)/, 1]
+ desired = @resource.value(:revision)
+ begin
+ desired_revid = bzr('revision-info', desired).strip.split(/\s+/).last
+ rescue Puppet::ExecutionFailure
+ # Possible revid available during update (but definitely not current)
+ desired_revid = nil
+ end
+ if current_revid == desired_revid
+ desired
+ else
+ current_revid
+ end
+ end
+ end
+
+ def revision=(desired)
+ at_path do
+ begin
+ bzr('update', '-r', desired)
+ rescue Puppet::ExecutionFailure
+ bzr('update', '-r', desired, ':parent')
+ end
+ end
+ update_owner
+ end
+
+ def latest
+ at_path do
+ bzr('version-info', ':parent')[/^revision-id:\s+(\S+)/, 1]
+ end
+ end
+
+ def latest?
+ at_path do
+ return self.revision == self.latest
+ end
+ end
+
+ private
+
+ def create_repository(path)
+ bzr('init', path)
+ update_owner
+ end
+
+ def clone_repository(revision)
+ args = ['branch']
+ if revision
+ args.push('-r', revision)
+ end
+ args.push(@resource.value(:source),
+ @resource.value(:path))
+ bzr(*args)
+ update_owner
+ end
+
+ def update_owner
+ if @resource.value(:owner) or @resource.value(:group)
+ set_ownership
+ end
+ end
+end
diff --git a/puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo/cvs.rb b/puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo/cvs.rb
new file mode 100644
index 00000000..7a8f6ef3
--- /dev/null
+++ b/puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo/cvs.rb
@@ -0,0 +1,135 @@
+require File.join(File.dirname(__FILE__), '..', 'vcsrepo')
+
+Puppet::Type.type(:vcsrepo).provide(:cvs, :parent => Puppet::Provider::Vcsrepo) do
+ desc "Supports CVS repositories/workspaces"
+
+ commands :cvs => 'cvs'
+ has_features :gzip_compression, :reference_tracking, :modules, :cvs_rsh, :user
+
+ def create
+ if !@resource.value(:source)
+ create_repository(@resource.value(:path))
+ else
+ checkout_repository
+ end
+ update_owner
+ end
+
+ def exists?
+ if @resource.value(:source)
+ directory = File.join(@resource.value(:path), 'CVS')
+ else
+ directory = File.join(@resource.value(:path), 'CVSROOT')
+ end
+ File.directory?(directory)
+ end
+
+ def working_copy_exists?
+ File.directory?(File.join(@resource.value(:path), 'CVS'))
+ end
+
+ def destroy
+ FileUtils.rm_rf(@resource.value(:path))
+ end
+
+ def latest?
+ Puppet.debug "Checking for updates because 'ensure => latest'"
+ at_path do
+ # We cannot use -P to prune empty dirs, otherwise
+ # CVS would report those as "missing", regardless
+ # if they have contents or updates.
+ is_current = (runcvs('-nq', 'update', '-d').strip == "")
+ if (!is_current) then Puppet.debug "There are updates available on the checkout's current branch/tag." end
+ return is_current
+ end
+ end
+
+ def latest
+ # CVS does not have a conecpt like commit-IDs or change
+ # sets, so we can only have the current branch name (or the
+ # requested one, if that differs) as the "latest" revision.
+ should = @resource.value(:revision)
+ current = self.revision
+ return should != current ? should : current
+ end
+
+ def revision
+ if !@rev
+ if File.exist?(tag_file)
+ contents = File.read(tag_file).strip
+ # Note: Doesn't differentiate between N and T entries
+ @rev = contents[1..-1]
+ else
+ @rev = 'HEAD'
+ end
+ Puppet.debug "Checkout is on branch/tag '#{@rev}'"
+ end
+ return @rev
+ end
+
+ def revision=(desired)
+ at_path do
+ runcvs('update', '-dr', desired, '.')
+ update_owner
+ @rev = desired
+ end
+ end
+
+ private
+
+ def tag_file
+ File.join(@resource.value(:path), 'CVS', 'Tag')
+ end
+
+ def checkout_repository
+ dirname, basename = File.split(@resource.value(:path))
+ Dir.chdir(dirname) do
+ args = ['-d', @resource.value(:source)]
+ if @resource.value(:compression)
+ args.push('-z', @resource.value(:compression))
+ end
+ args.push('checkout')
+ if @resource.value(:revision)
+ args.push('-r', @resource.value(:revision))
+ end
+ args.push('-d', basename, module_name)
+ runcvs(*args)
+ end
+ end
+
+ # When the source:
+ # * Starts with ':' (eg, :pserver:...)
+ def module_name
+ if (m = @resource.value(:module))
+ m
+ elsif (source = @resource.value(:source))
+ source[0, 1] == ':' ? File.basename(source) : '.'
+ end
+ end
+
+ def create_repository(path)
+ runcvs('-d', path, 'init')
+ end
+
+ def update_owner
+ if @resource.value(:owner) or @resource.value(:group)
+ set_ownership
+ end
+ end
+
+ def runcvs(*args)
+ if @resource.value(:cvs_rsh)
+ Puppet.debug "Using CVS_RSH = " + @resource.value(:cvs_rsh)
+ e = { :CVS_RSH => @resource.value(:cvs_rsh) }
+ else
+ e = {}
+ end
+
+ if @resource.value(:user) and @resource.value(:user) != Facter['id'].value
+ Puppet.debug "Running as user " + @resource.value(:user)
+ Puppet::Util::Execution.execute([:cvs, *args], :uid => @resource.value(:user), :custom_environment => e)
+ else
+ Puppet::Util::Execution.execute([:cvs, *args], :custom_environment => e)
+ end
+ end
+end
diff --git a/puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo/dummy.rb b/puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo/dummy.rb
new file mode 100644
index 00000000..27bfbbed
--- /dev/null
+++ b/puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo/dummy.rb
@@ -0,0 +1,12 @@
+require File.join(File.dirname(__FILE__), '..', 'vcsrepo')
+
+Puppet::Type.type(:vcsrepo).provide(:dummy, :parent => Puppet::Provider::Vcsrepo) do
+ desc "Dummy default provider"
+
+ defaultfor :feature => :posix
+
+ def working_copy_exists?
+ providers = @resource.class.providers.map{|x| x.to_s}.sort.reject{|x| x == "dummy"}.join(", ") rescue "none"
+ raise("vcsrepo resource must have a provider, available: #{providers}")
+ end
+end
diff --git a/puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo/git.rb b/puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo/git.rb
new file mode 100644
index 00000000..9d18b474
--- /dev/null
+++ b/puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo/git.rb
@@ -0,0 +1,483 @@
+require File.join(File.dirname(__FILE__), '..', 'vcsrepo')
+
+Puppet::Type.type(:vcsrepo).provide(:git, :parent => Puppet::Provider::Vcsrepo) do
+ desc "Supports Git repositories"
+
+ has_command(:git, 'git') do
+ environment({ 'HOME' => ENV['HOME'] })
+ end
+
+ has_features :bare_repositories, :reference_tracking, :ssh_identity, :multiple_remotes, :user, :depth, :branch, :submodules
+
+ def create
+ if @resource.value(:revision) and ensure_bare_or_mirror?
+ fail("Cannot set a revision (#{@resource.value(:revision)}) on a bare repository")
+ end
+ if !@resource.value(:source)
+ if @resource.value(:ensure) == :mirror
+ fail("Cannot init repository with mirror option, try bare instead")
+ end
+
+ init_repository(@resource.value(:path))
+ else
+ clone_repository(default_url, @resource.value(:path))
+ update_remotes
+
+ if @resource.value(:revision)
+ checkout
+ end
+ if !ensure_bare_or_mirror? && @resource.value(:submodules) == :true
+ update_submodules
+ end
+
+ end
+ update_owner_and_excludes
+ end
+
+ def destroy
+ FileUtils.rm_rf(@resource.value(:path))
+ end
+
+ # Checks to see if the current revision is equal to the revision on the
+ # remote (whether on a branch, tag, or reference)
+ #
+ # @return [Boolean] Returns true if the repo is on the latest revision
+ def latest?
+ return revision == latest_revision
+ end
+
+ # Just gives the `should` value that we should be setting the repo to if
+ # latest? returns false
+ #
+ # @return [String] Returns the target sha/tag/branch
+ def latest
+ if not @resource.value(:revision) and branch = on_branch?
+ return branch
+ else
+ return @resource.value(:revision)
+ end
+ end
+
+ # Get the current revision of the repo (tag/branch/sha)
+ #
+ # @return [String] Returns the branch/tag if the current sha matches the
+ # remote; otherwise returns the current sha.
+ def revision
+ #HEAD is the default, but lets just be explicit here.
+ get_revision('HEAD')
+ end
+
+ # Is passed the desired reference, whether a tag, rev, or branch. Should
+ # handle transitions from a rev/branch/tag to a rev/branch/tag. Detached
+ # heads should be treated like bare revisions.
+ #
+ # @param [String] desired The desired revision to which the repo should be
+ # set.
+ def revision=(desired)
+ #just checkout tags and shas; fetch has already happened so they should be updated.
+ checkout(desired)
+ #branches require more work.
+ if local_branch_revision?(desired)
+ #reset instead of pull to avoid merge conflicts. assuming remote is
+ #updated and authoritative.
+ #TODO might be worthwhile to have an allow_local_changes param to decide
+ #whether to reset or pull when we're ensuring latest.
+ if @resource.value(:source)
+ at_path { git_with_identity('reset', '--hard', "#{@resource.value(:remote)}/#{desired}") }
+ else
+ at_path { git_with_identity('reset', '--hard', "#{desired}") }
+ end
+ end
+ #TODO Would this ever reach here if it is bare?
+ if !ensure_bare_or_mirror? && @resource.value(:submodules) == :true
+ update_submodules
+ end
+ update_owner_and_excludes
+ end
+
+ def bare_exists?
+ bare_git_config_exists? && !working_copy_exists?
+ end
+
+ def ensure_bare_or_mirror?
+ [:bare, :mirror].include? @resource.value(:ensure)
+ end
+
+ # If :source is set to a hash (for supporting multiple remotes),
+ # we search for the URL for :remote. If it doesn't exist,
+ # we throw an error. If :source is just a string, we use that
+ # value for the default URL.
+ def default_url
+ if @resource.value(:source).is_a?(Hash)
+ if @resource.value(:source).has_key?(@resource.value(:remote))
+ @resource.value(:source)[@resource.value(:remote)]
+ else
+ fail("You must specify the URL for #{@resource.value(:remote)} in the :source hash")
+ end
+ else
+ @resource.value(:source)
+ end
+ end
+
+ def working_copy_exists?
+ if @resource.value(:source) and File.exists?(File.join(@resource.value(:path), '.git', 'config'))
+ File.readlines(File.join(@resource.value(:path), '.git', 'config')).grep(/#{Regexp.escape(default_url)}/).any?
+ else
+ File.directory?(File.join(@resource.value(:path), '.git'))
+ end
+ end
+
+ def exists?
+ working_copy_exists? || bare_exists?
+ end
+
+ def update_remote_url(remote_name, remote_url)
+ do_update = false
+ current = git_with_identity('config', '-l')
+
+ unless remote_url.nil?
+ # Check if remote exists at all, regardless of URL.
+ # If remote doesn't exist, add it
+ if not current.include? "remote.#{remote_name}.url"
+ git_with_identity('remote','add', remote_name, remote_url)
+ return true
+
+ # If remote exists, but URL doesn't match, update URL
+ elsif not current.include? "remote.#{remote_name}.url=#{remote_url}"
+ git_with_identity('remote','set-url', remote_name, remote_url)
+ return true
+ else
+ return false
+ end
+ end
+
+ end
+
+ def update_remotes
+ do_update = false
+
+ # If supplied source is a hash of remote name and remote url pairs, then
+ # we loop around the hash. Otherwise, we assume single url specified
+ # in source property
+ if @resource.value(:source).is_a?(Hash)
+ @resource.value(:source).keys.sort.each do |remote_name|
+ remote_url = @resource.value(:source)[remote_name]
+ at_path { do_update |= update_remote_url(remote_name, remote_url) }
+ end
+ else
+ at_path { do_update |= update_remote_url(@resource.value(:remote), @resource.value(:source)) }
+ end
+
+ # If at least one remote was added or updated, then we must
+ # call the 'git remote update' command
+ if do_update == true
+ at_path { git_with_identity('remote','update') }
+ end
+
+ end
+
+ def update_references
+ at_path do
+ update_remotes
+ git_with_identity('fetch', @resource.value(:remote))
+ git_with_identity('fetch', '--tags', @resource.value(:remote))
+ update_owner_and_excludes
+ end
+ end
+
+ private
+
+ def valid_repo?
+ Dir.chdir(@resource.value(:path)){ system('git rev-parse > /dev/null 2>&1')}
+ end
+
+ def bare_git_config_exists?
+ File.exist?(File.join(@resource.value(:path), 'config')) && valid_repo?
+ end
+
+ # @!visibility private
+ def clone_repository(source, path)
+ check_force
+ args = ['clone']
+ if @resource.value(:depth) and @resource.value(:depth).to_i > 0
+ args.push('--depth', @resource.value(:depth).to_s)
+ if @resource.value(:revision)
+ args.push('--branch', @resource.value(:revision).to_s)
+ end
+ end
+ if @resource.value(:branch)
+ args.push('--branch', @resource.value(:branch).to_s)
+ end
+
+ case @resource.value(:ensure)
+ when :bare then args << '--bare'
+ when :mirror then args << '--mirror'
+ end
+
+ if @resource.value(:remote) != 'origin'
+ args.push('--origin', @resource.value(:remote))
+ end
+ if !working_copy_exists?
+ args.push(source, path)
+ Dir.chdir("/") do
+ git_with_identity(*args)
+ end
+ else
+ notice "Repo has already been cloned"
+ end
+ end
+
+ # @!visibility private
+ def check_force
+ if path_exists? and not path_empty?
+ if @resource.value(:force) && !valid_repo?
+ notice "Removing %s to replace with vcsrepo." % @resource.value(:path)
+ destroy
+ else
+ raise Puppet::Error, "Could not create repository (non-repository at path)"
+ end
+ end
+ end
+
+ # @!visibility private
+ def init_repository(path)
+ check_force
+ if @resource.value(:ensure) == :bare && working_copy_exists?
+ convert_working_copy_to_bare
+ elsif @resource.value(:ensure) == :present && bare_exists?
+ convert_bare_to_working_copy
+ else
+ # normal init
+ FileUtils.mkdir(@resource.value(:path))
+ FileUtils.chown(@resource.value(:user), nil, @resource.value(:path)) if @resource.value(:user)
+ args = ['init']
+ if @resource.value(:ensure) == :bare
+ args << '--bare'
+ end
+ at_path do
+ git_with_identity(*args)
+ end
+ end
+ end
+
+ # Convert working copy to bare
+ #
+ # Moves:
+ # <path>/.git
+ # to:
+ # <path>/
+ # @!visibility private
+ def convert_working_copy_to_bare
+ notice "Converting working copy repository to bare repository"
+ FileUtils.mv(File.join(@resource.value(:path), '.git'), tempdir)
+ FileUtils.rm_rf(@resource.value(:path))
+ FileUtils.mv(tempdir, @resource.value(:path))
+ end
+
+ # Convert bare to working copy
+ #
+ # Moves:
+ # <path>/
+ # to:
+ # <path>/.git
+ # @!visibility private
+ def convert_bare_to_working_copy
+ notice "Converting bare repository to working copy repository"
+ FileUtils.mv(@resource.value(:path), tempdir)
+ FileUtils.mkdir(@resource.value(:path))
+ FileUtils.mv(tempdir, File.join(@resource.value(:path), '.git'))
+ if commits_in?(File.join(@resource.value(:path), '.git'))
+ reset('HEAD')
+ git_with_identity('checkout', '--force')
+ update_owner_and_excludes
+ end
+ end
+
+ # @!visibility private
+ def commits_in?(dot_git)
+ Dir.glob(File.join(dot_git, 'objects/info/*'), File::FNM_DOTMATCH) do |e|
+ return true unless %w(. ..).include?(File::basename(e))
+ end
+ false
+ end
+
+ # Will checkout a rev/branch/tag using the locally cached versions. Does not
+ # handle upstream branch changes
+ # @!visibility private
+ def checkout(revision = @resource.value(:revision))
+ if !local_branch_revision?(revision) && remote_branch_revision?(revision)
+ #non-locally existant branches (perhaps switching to a branch that has never been checked out)
+ at_path { git_with_identity('checkout', '--force', '-b', revision, '--track', "#{@resource.value(:remote)}/#{revision}") }
+ else
+ #tags, locally existant branches (perhaps outdated), and shas
+ at_path { git_with_identity('checkout', '--force', revision) }
+ end
+ end
+
+ # @!visibility private
+ def reset(desired)
+ at_path do
+ git_with_identity('reset', '--hard', desired)
+ end
+ end
+
+ # @!visibility private
+ def update_submodules
+ at_path do
+ git_with_identity('submodule', 'update', '--init', '--recursive')
+ end
+ end
+
+ # Determins if the branch exists at the upstream but has not yet been locally committed
+ # @!visibility private
+ def remote_branch_revision?(revision = @resource.value(:revision))
+ # git < 1.6 returns '#{@resource.value(:remote)}/#{revision}'
+ # git 1.6+ returns 'remotes/#{@resource.value(:remote)}/#{revision}'
+ branch = at_path { branches.grep /(remotes\/)?#{@resource.value(:remote)}\/#{revision}$/ }
+ branch unless branch.empty?
+ end
+
+ # Determins if the branch is already cached locally
+ # @!visibility private
+ def local_branch_revision?(revision = @resource.value(:revision))
+ at_path { branches.include?(revision) }
+ end
+
+ # @!visibility private
+ def tag_revision?(revision = @resource.value(:revision))
+ at_path { tags.include?(revision) }
+ end
+
+ # @!visibility private
+ def branches
+ at_path { git_with_identity('branch', '-a') }.gsub('*', ' ').split(/\n/).map { |line| line.strip }
+ end
+
+ # git < 2.4 returns 'detached from'
+ # git 2.4+ returns 'HEAD detached at'
+ # @!visibility private
+ def on_branch?
+ at_path {
+ matches = git_with_identity('branch', '-a').match /\*\s+(.*)/
+ matches[1] unless matches[1].match /(\(detached from|\(HEAD detached at|\(no branch)/
+ }
+ end
+
+ # @!visibility private
+ def tags
+ at_path { git_with_identity('tag', '-l') }.split(/\n/).map { |line| line.strip }
+ end
+
+ # @!visibility private
+ def set_excludes
+ # Excludes may be an Array or a String.
+ at_path do
+ open('.git/info/exclude', 'w') do |f|
+ if @resource.value(:excludes).respond_to?(:each)
+ @resource.value(:excludes).each { |ex| f.puts ex }
+ else
+ f.puts @resource.value(:excludes)
+ end
+ end
+ end
+ end
+
+ # Finds the latest revision or sha of the current branch if on a branch, or
+ # of HEAD otherwise.
+ # @note Calls create which can forcibly destroy and re-clone the repo if
+ # force => true
+ # @see get_revision
+ #
+ # @!visibility private
+ # @return [String] Returns the output of get_revision
+ def latest_revision
+ #TODO Why is create called here anyway?
+ create if @resource.value(:force) && working_copy_exists?
+ create if !working_copy_exists?
+
+ if branch = on_branch?
+ return get_revision("#{@resource.value(:remote)}/#{branch}")
+ else
+ return get_revision
+ end
+ end
+
+ # Returns the current revision given if the revision is a tag or branch and
+ # matches the current sha. If the current sha does not match the sha of a tag
+ # or branch, then it will just return the sha (ie, is not in sync)
+ #
+ # @!visibility private
+ #
+ # @param [String] rev The revision of which to check if it is current
+ # @return [String] Returns the tag/branch of the current repo if it's up to
+ # date; otherwise returns the sha of the requested revision.
+ def get_revision(rev = 'HEAD')
+ if @resource.value(:source)
+ update_references
+ else
+ status = at_path { git_with_identity('status')}
+ is_it_new = status =~ /Initial commit/
+ if is_it_new
+ status =~ /On branch (.*)/
+ branch = $1
+ return branch
+ end
+ end
+ current = at_path { git_with_identity('rev-parse', rev).strip }
+ if @resource.value(:revision)
+ if tag_revision?
+ # git-rev-parse will give you the hash of the tag object itself rather
+ # than the commit it points to by default. Using tag^0 will return the
+ # actual commit.
+ canonical = at_path { git_with_identity('rev-parse', "#{@resource.value(:revision)}^0").strip }
+ elsif local_branch_revision?
+ canonical = at_path { git_with_identity('rev-parse', @resource.value(:revision)).strip }
+ elsif remote_branch_revision?
+ canonical = at_path { git_with_identity('rev-parse', "#{@resource.value(:remote)}/#{@resource.value(:revision)}").strip }
+ else
+ #look for a sha (could match invalid shas)
+ canonical = at_path { git_with_identity('rev-parse', '--revs-only', @resource.value(:revision)).strip }
+ end
+ fail("#{@resource.value(:revision)} is not a local or remote ref") if canonical.nil? or canonical.empty?
+ current = @resource.value(:revision) if current == canonical
+ end
+ return current
+ end
+
+ # @!visibility private
+ def update_owner_and_excludes
+ if @resource.value(:owner) or @resource.value(:group)
+ set_ownership
+ end
+ if @resource.value(:excludes)
+ set_excludes
+ end
+ end
+
+ # @!visibility private
+ def git_with_identity(*args)
+ if @resource.value(:identity)
+ Tempfile.open('git-helper', Puppet[:statedir]) do |f|
+ f.puts '#!/bin/sh'
+ f.puts 'export SSH_AUTH_SOCKET='
+ f.puts "exec ssh -oStrictHostKeyChecking=no -oPasswordAuthentication=no -oKbdInteractiveAuthentication=no -oChallengeResponseAuthentication=no -oConnectTimeout=120 -i #{@resource.value(:identity)} $*"
+ f.close
+
+ FileUtils.chmod(0755, f.path)
+ env_save = ENV['GIT_SSH']
+ ENV['GIT_SSH'] = f.path
+
+ ret = git(*args)
+
+ ENV['GIT_SSH'] = env_save
+
+ return ret
+ end
+ elsif @resource.value(:user) and @resource.value(:user) != Facter['id'].value
+ env = Etc.getpwnam(@resource.value(:user))
+ Puppet::Util::Execution.execute("git #{args.join(' ')}", :uid => @resource.value(:user), :failonfail => true, :custom_environment => {'HOME' => env['dir']})
+ else
+ git(*args)
+ end
+ end
+end
diff --git a/puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo/hg.rb b/puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo/hg.rb
new file mode 100644
index 00000000..294c2a97
--- /dev/null
+++ b/puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo/hg.rb
@@ -0,0 +1,130 @@
+require File.join(File.dirname(__FILE__), '..', 'vcsrepo')
+
+Puppet::Type.type(:vcsrepo).provide(:hg, :parent => Puppet::Provider::Vcsrepo) do
+ desc "Supports Mercurial repositories"
+
+ commands :hg => 'hg'
+
+ has_features :reference_tracking, :ssh_identity, :user, :basic_auth
+
+ def create
+ if !@resource.value(:source)
+ create_repository(@resource.value(:path))
+ else
+ clone_repository(@resource.value(:revision))
+ end
+ update_owner
+ end
+
+ def working_copy_exists?
+ File.directory?(File.join(@resource.value(:path), '.hg'))
+ end
+
+ def exists?
+ working_copy_exists?
+ end
+
+ def destroy
+ FileUtils.rm_rf(@resource.value(:path))
+ end
+
+ def latest?
+ at_path do
+ return self.revision == self.latest
+ end
+ end
+
+ def latest
+ at_path do
+ begin
+ hg_wrapper('incoming', '--branch', '.', '--newest-first', '--limit', '1', { :remote => true })[/^changeset:\s+(?:-?\d+):(\S+)/m, 1]
+ rescue Puppet::ExecutionFailure
+ # If there are no new changesets, return the current nodeid
+ self.revision
+ end
+ end
+ end
+
+ def revision
+ at_path do
+ current = hg_wrapper('parents')[/^changeset:\s+(?:-?\d+):(\S+)/m, 1]
+ desired = @resource.value(:revision)
+ if desired
+ # Return the tag name if it maps to the current nodeid
+ mapped = hg_wrapper('tags')[/^#{Regexp.quote(desired)}\s+\d+:(\S+)/m, 1]
+ if current == mapped
+ desired
+ else
+ current
+ end
+ else
+ current
+ end
+ end
+ end
+
+ def revision=(desired)
+ at_path do
+ begin
+ hg_wrapper('pull', { :remote => true })
+ rescue
+ end
+ begin
+ hg_wrapper('merge')
+ rescue Puppet::ExecutionFailure
+ # If there's nothing to merge, just skip
+ end
+ hg_wrapper('update', '--clean', '-r', desired)
+ end
+ update_owner
+ end
+
+ private
+
+ def create_repository(path)
+ hg_wrapper('init', path)
+ end
+
+ def clone_repository(revision)
+ args = ['clone']
+ if revision
+ args.push('-u', revision)
+ end
+ args.push(@resource.value(:source),
+ @resource.value(:path))
+ args.push({ :remote => true })
+ hg_wrapper(*args)
+ end
+
+ def update_owner
+ if @resource.value(:owner) or @resource.value(:group)
+ set_ownership
+ end
+ end
+
+ def hg_wrapper(*args)
+ options = { :remote => false }
+ if args.length > 0 and args[-1].is_a? Hash
+ options.merge!(args.pop)
+ end
+
+ if @resource.value(:basic_auth_username) && @resource.value(:basic_auth_password)
+ args += [
+ "--config", "\"auth.x.prefix=#{@resource.value(:source)}\"",
+ "--config", "\"auth.x.username=#{@resource.value(:basic_auth_username)}\"",
+ "--config", "\"auth.x.password=#{@resource.value(:basic_auth_password)}\"",
+ "--config", "\"auth.x.schemes=http https\""
+ ]
+ end
+
+ if options[:remote] and @resource.value(:identity)
+ args += ["--ssh", "ssh -oStrictHostKeyChecking=no -oPasswordAuthentication=no -oKbdInteractiveAuthentication=no -oChallengeResponseAuthentication=no -i #{@resource.value(:identity)}"]
+ end
+ if @resource.value(:user) and @resource.value(:user) != Facter['id'].value
+ args.map! { |a| if a =~ /\s/ then "'#{a}'" else a end } # Adds quotes to arguments with whitespaces.
+ Puppet::Util::Execution.execute("hg #{args.join(' ')}", :uid => @resource.value(:user), :failonfail => true)
+ else
+ hg(*args)
+ end
+ end
+end
diff --git a/puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo/p4.rb b/puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo/p4.rb
new file mode 100644
index 00000000..b429bcbb
--- /dev/null
+++ b/puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo/p4.rb
@@ -0,0 +1,278 @@
+require File.join(File.dirname(__FILE__), '..', 'vcsrepo')
+
+Puppet::Type.type(:vcsrepo).provide(:p4, :parent => Puppet::Provider::Vcsrepo) do
+ desc "Supports Perforce depots"
+
+ has_features :filesystem_types, :reference_tracking, :p4config
+
+ def create
+ # create or update client
+ create_client(client_name)
+
+ # if source provided, sync client
+ source = @resource.value(:source)
+ if source
+ revision = @resource.value(:revision)
+ sync_client(source, revision)
+ end
+
+ update_owner
+ end
+
+ def working_copy_exists?
+ # Check if the server is there, or raise error
+ p4(['info'], {:marshal => false})
+
+ # Check if workspace is setup
+ args = ['where']
+ args.push(@resource.value(:path) + "...")
+ hash = p4(args, {:raise => false})
+
+ return (hash['code'] != "error")
+ end
+
+ def exists?
+ working_copy_exists?
+ end
+
+ def destroy
+ args = ['client']
+ args.push('-d', '-f')
+ args.push(client_name)
+ p4(args)
+ FileUtils.rm_rf(@resource.value(:path))
+ end
+
+ def latest?
+ rev = self.revision
+ if rev
+ (rev >= self.latest)
+ else
+ true
+ end
+ end
+
+ def latest
+ args = ['changes']
+ args.push('-m1', @resource.value(:source))
+ hash = p4(args)
+
+ return hash['change'].to_i
+ end
+
+ def revision
+ args = ['cstat']
+ args.push(@resource.value(:source))
+ hash = p4(args, {:marshal => false})
+ hash = marshal_cstat(hash)
+
+ revision = 0
+ if hash && hash['code'] != 'error'
+ hash['data'].each do |c|
+ if c['status'] == 'have'
+ change = c['change'].to_i
+ revision = change if change > revision
+ end
+ end
+ end
+ return revision
+ end
+
+ def revision=(desired)
+ sync_client(@resource.value(:source), desired)
+ update_owner
+ end
+
+ private
+
+ def update_owner
+ if @resource.value(:owner) or @resource.value(:group)
+ set_ownership
+ end
+ end
+
+ # Sync the client workspace files to head or specified revision.
+ # Params:
+ # +source+:: Depot path to sync
+ # +revision+:: Perforce change list to sync to (optional)
+ def sync_client(source, revision)
+ Puppet.debug "Syncing: #{source}"
+ args = ['sync']
+ if revision
+ args.push(source + "@#{revision}")
+ else
+ args.push(source)
+ end
+ p4(args)
+ end
+
+ # Returns the name of the Perforce client workspace
+ def client_name
+ p4config = @resource.value(:p4config)
+
+ # default (generated) client name
+ path = @resource.value(:path)
+ host = Facter.value('hostname')
+ default = "puppet-" + Digest::MD5.hexdigest(path + host)
+
+ # check config for client name
+ set_client = nil
+ if p4config && File.file?(p4config)
+ open(p4config) do |f|
+ m = f.grep(/^P4CLIENT=/).pop
+ p = /^P4CLIENT=(.*)$/
+ set_client = p.match(m)[1] if m
+ end
+ end
+
+ return set_client || ENV['P4CLIENT'] || default
+ end
+
+ # Create (or update) a client workspace spec.
+ # If a client name is not provided then a hash based on the path is used.
+ # Params:
+ # +client+:: Name of client workspace
+ # +path+:: The Root location of the Perforce client workspace
+ def create_client(client)
+ Puppet.debug "Creating client: #{client}"
+
+ # fetch client spec
+ hash = parse_client(client)
+ hash['Root'] = @resource.value(:path)
+ hash['Description'] = "Generated by Puppet VCSrepo"
+
+ # check is source is a Stream
+ source = @resource.value(:source)
+ if source
+ parts = source.split(/\//)
+ if parts && parts.length >= 4
+ source = "//" + parts[2] + "/" + parts[3]
+ streams = p4(['streams', source], {:raise => false})
+ if streams['code'] == "stat"
+ hash['Stream'] = streams['Stream']
+ notice "Streams" + streams['Stream'].inspect
+ end
+ end
+ end
+
+ # save client spec
+ save_client(hash)
+ end
+
+
+ # Fetches a client workspace spec from Perforce and returns a hash map representation.
+ # Params:
+ # +client+:: name of the client workspace
+ def parse_client(client)
+ args = ['client']
+ args.push('-o', client)
+ hash = p4(args)
+
+ return hash
+ end
+
+
+ # Saves the client workspace spec from the given hash
+ # Params:
+ # +hash+:: hash map of client spec
+ def save_client(hash)
+ spec = String.new
+ view = "\nView:\n"
+
+ hash.keys.sort.each do |k|
+ v = hash[k]
+ next if( k == "code" )
+ if(k.to_s =~ /View/ )
+ view += "\t#{v}\n"
+ else
+ spec += "#{k.to_s}: #{v.to_s}\n"
+ end
+ end
+ spec += view
+
+ args = ['client']
+ args.push('-i')
+ p4(args, {:input => spec, :marshal => false})
+ end
+
+ # Sets Perforce Configuration environment.
+ # P4CLIENT generated, but overwitten if defined in config.
+ def config
+ p4config = @resource.value(:p4config)
+
+ cfg = Hash.new
+ cfg.store 'P4CONFIG', p4config if p4config
+ cfg.store 'P4CLIENT', client_name
+ return cfg
+ end
+
+ def p4(args, options = {})
+ # Merge custom options with defaults
+ opts = {
+ :raise => true, # Raise errors
+ :marshal => true, # Marshal output
+ }.merge(options)
+
+ cmd = ['p4']
+ cmd.push '-R' if opts[:marshal]
+ cmd.push args
+ cmd_str = cmd.respond_to?(:join) ? cmd.join(' ') : cmd
+
+ Puppet.debug "environment: #{config}"
+ Puppet.debug "command: #{cmd_str}"
+
+ hash = Hash.new
+ Open3.popen3(config, cmd_str) do |i, o, e, t|
+ # Send input stream if provided
+ if(opts[:input])
+ Puppet.debug "input:\n" + opts[:input]
+ i.write opts[:input]
+ i.close
+ end
+
+ if(opts[:marshal])
+ hash = Marshal.load(o)
+ else
+ hash['data'] = o.read
+ end
+
+ # Raise errors, Perforce or Exec
+ if(opts[:raise] && !e.eof && t.value != 0)
+ raise Puppet::Error, "\nP4: #{e.read}"
+ end
+ if(opts[:raise] && hash['code'] == 'error' && t.value != 0)
+ raise Puppet::Error, "\nP4: #{hash['data']}"
+ end
+ end
+
+ Puppet.debug "hash: #{hash}\n"
+ return hash
+ end
+
+ # helper method as cstat does not Marshal
+ def marshal_cstat(hash)
+ data = hash['data']
+ code = 'error'
+
+ list = Array.new
+ change = Hash.new
+ data.each_line do |l|
+ p = /^\.\.\. (.*) (.*)$/
+ m = p.match(l)
+ if m
+ change[m[1]] = m[2]
+ if m[1] == 'status'
+ code = 'stat'
+ list.push change
+ change = Hash.new
+ end
+ end
+ end
+
+ hash = Hash.new
+ hash.store 'code', code
+ hash.store 'data', list
+ return hash
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo/svn.rb b/puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo/svn.rb
new file mode 100644
index 00000000..fccfaa5a
--- /dev/null
+++ b/puppet/modules/vcsrepo/lib/puppet/provider/vcsrepo/svn.rb
@@ -0,0 +1,139 @@
+require File.join(File.dirname(__FILE__), '..', 'vcsrepo')
+
+Puppet::Type.type(:vcsrepo).provide(:svn, :parent => Puppet::Provider::Vcsrepo) do
+ desc "Supports Subversion repositories"
+
+ commands :svn => 'svn',
+ :svnadmin => 'svnadmin',
+ :svnlook => 'svnlook'
+
+ has_features :filesystem_types, :reference_tracking, :basic_auth, :configuration, :conflict, :depth
+
+ def create
+ if !@resource.value(:source)
+ create_repository(@resource.value(:path))
+ else
+ checkout_repository(@resource.value(:source),
+ @resource.value(:path),
+ @resource.value(:revision),
+ @resource.value(:depth))
+ end
+ update_owner
+ end
+
+ def working_copy_exists?
+ if File.directory?(@resource.value(:path))
+ # :path is an svn checkout
+ return true if File.directory?(File.join(@resource.value(:path), '.svn'))
+ if File.file?(File.join(@resource.value(:path), 'format'))
+ # :path is an svn server
+ return true if svnlook('uuid', @resource.value(:path))
+ end
+ end
+ false
+ end
+
+ def exists?
+ working_copy_exists?
+ end
+
+ def destroy
+ FileUtils.rm_rf(@resource.value(:path))
+ end
+
+ def latest?
+ at_path do
+ (self.revision >= self.latest) and (@resource.value(:source) == self.sourceurl)
+ end
+ end
+
+ def buildargs
+ args = ['--non-interactive']
+ if @resource.value(:basic_auth_username) && @resource.value(:basic_auth_password)
+ args.push('--username', @resource.value(:basic_auth_username))
+ args.push('--password', @resource.value(:basic_auth_password))
+ args.push('--no-auth-cache')
+ end
+
+ if @resource.value(:force)
+ args.push('--force')
+ end
+
+ if @resource.value(:configuration)
+ args.push('--config-dir', @resource.value(:configuration))
+ end
+
+ if @resource.value(:trust_server_cert) != :false
+ args.push('--trust-server-cert')
+ end
+
+ args
+ end
+
+ def latest
+ args = buildargs.push('info', '-r', 'HEAD')
+ at_path do
+ svn(*args)[/^Revision:\s+(\d+)/m, 1]
+ end
+ end
+
+ def sourceurl
+ args = buildargs.push('info')
+ at_path do
+ svn(*args)[/^URL:\s+(\S+)/m, 1]
+ end
+ end
+
+ def revision
+ args = buildargs.push('info')
+ at_path do
+ svn(*args)[/^Revision:\s+(\d+)/m, 1]
+ end
+ end
+
+ def revision=(desired)
+ args = if @resource.value(:source)
+ buildargs.push('switch', '-r', desired, @resource.value(:source))
+ else
+ buildargs.push('update', '-r', desired)
+ end
+
+ if @resource.value(:conflict)
+ args.push('--accept', @resource.value(:conflict))
+ end
+
+ at_path do
+ svn(*args)
+ end
+ update_owner
+ end
+
+ private
+
+ def checkout_repository(source, path, revision, depth)
+ args = buildargs.push('checkout')
+ if revision
+ args.push('-r', revision)
+ end
+ if depth
+ args.push('--depth', depth)
+ end
+ args.push(source, path)
+ svn(*args)
+ end
+
+ def create_repository(path)
+ args = ['create']
+ if @resource.value(:fstype)
+ args.push('--fs-type', @resource.value(:fstype))
+ end
+ args << path
+ svnadmin(*args)
+ end
+
+ def update_owner
+ if @resource.value(:owner) or @resource.value(:group)
+ set_ownership
+ end
+ end
+end
diff --git a/puppet/modules/vcsrepo/lib/puppet/type/vcsrepo.rb b/puppet/modules/vcsrepo/lib/puppet/type/vcsrepo.rb
new file mode 100644
index 00000000..e2ef0b7e
--- /dev/null
+++ b/puppet/modules/vcsrepo/lib/puppet/type/vcsrepo.rb
@@ -0,0 +1,248 @@
+require 'pathname'
+
+Puppet::Type.newtype(:vcsrepo) do
+ desc "A local version control repository"
+
+ feature :gzip_compression,
+ "The provider supports explicit GZip compression levels"
+ feature :basic_auth,
+ "The provider supports HTTP Basic Authentication"
+ feature :bare_repositories,
+ "The provider differentiates between bare repositories
+ and those with working copies",
+ :methods => [:bare_exists?, :working_copy_exists?]
+
+ feature :filesystem_types,
+ "The provider supports different filesystem types"
+
+ feature :reference_tracking,
+ "The provider supports tracking revision references that can change
+ over time (eg, some VCS tags and branch names)"
+
+ feature :ssh_identity,
+ "The provider supports a configurable SSH identity file"
+
+ feature :user,
+ "The provider can run as a different user"
+
+ feature :modules,
+ "The repository contains modules that can be chosen of"
+
+ feature :multiple_remotes,
+ "The repository tracks multiple remote repositories"
+
+ feature :configuration,
+ "The configuration directory to use"
+
+ feature :cvs_rsh,
+ "The provider understands the CVS_RSH environment variable"
+
+ feature :depth,
+ "The provider can do shallow clones or set scope limit"
+
+ feature :branch,
+ "The name of the branch"
+
+ feature :p4config,
+ "The provider understands Perforce Configuration"
+
+ feature :submodules,
+ "The repository contains submodules which can be optionally initialized"
+
+ feature :conflict,
+ "The provider supports automatic conflict resolution"
+
+ ensurable do
+ attr_accessor :latest
+
+ def insync?(is)
+ @should ||= []
+
+ case should
+ when :present
+ return true unless [:absent, :purged, :held].include?(is)
+ when :latest
+ if is == :latest
+ return true
+ else
+ return false
+ end
+ when :bare
+ return is == :bare
+ when :mirror
+ return is == :mirror
+ end
+ end
+
+ newvalue :present do
+ notice "Creating repository from present"
+ provider.create
+ end
+
+ newvalue :bare, :required_features => [:bare_repositories] do
+ if !provider.exists?
+ provider.create
+ end
+ end
+
+ newvalue :mirror, :required_features => [:bare_repositories] do
+ if !provider.exists?
+ provider.create
+ end
+ end
+
+ newvalue :absent do
+ provider.destroy
+ end
+
+ newvalue :latest, :required_features => [:reference_tracking] do
+ if provider.exists? && !@resource.value(:force)
+ if provider.respond_to?(:update_references)
+ provider.update_references
+ end
+ if provider.respond_to?(:latest?)
+ reference = provider.latest || provider.revision
+ else
+ reference = resource.value(:revision) || provider.revision
+ end
+ notice "Updating to latest '#{reference}' revision"
+ provider.revision = reference
+ else
+ notice "Creating repository from latest"
+ provider.create
+ end
+ end
+
+ def retrieve
+ prov = @resource.provider
+ if prov
+ if prov.working_copy_exists?
+ (@should.include?(:latest) && prov.latest?) ? :latest : :present
+ elsif prov.class.feature?(:bare_repositories) and prov.bare_exists?
+ :bare
+ else
+ :absent
+ end
+ else
+ raise Puppet::Error, "Could not find provider"
+ end
+ end
+
+ end
+
+ newparam :path do
+ desc "Absolute path to repository"
+ isnamevar
+ validate do |value|
+ path = Pathname.new(value)
+ unless path.absolute?
+ raise ArgumentError, "Path must be absolute: #{path}"
+ end
+ end
+ end
+
+ newparam :source do
+ desc "The source URI for the repository"
+ end
+
+ newparam :fstype, :required_features => [:filesystem_types] do
+ desc "Filesystem type"
+ end
+
+ newproperty :revision do
+ desc "The revision of the repository"
+ newvalue(/^\S+$/)
+ end
+
+ newparam :owner do
+ desc "The user/uid that owns the repository files"
+ end
+
+ newparam :group do
+ desc "The group/gid that owns the repository files"
+ end
+
+ newparam :user do
+ desc "The user to run for repository operations"
+ end
+
+ newparam :excludes do
+ desc "Files to be excluded from the repository"
+ end
+
+ newparam :force do
+ desc "Force repository creation, destroying any files on the path in the process."
+ newvalues(:true, :false)
+ defaultto false
+ end
+
+ newparam :compression, :required_features => [:gzip_compression] do
+ desc "Compression level"
+ validate do |amount|
+ unless Integer(amount).between?(0, 6)
+ raise ArgumentError, "Unsupported compression level: #{amount} (expected 0-6)"
+ end
+ end
+ end
+
+ newparam :basic_auth_username, :required_features => [:basic_auth] do
+ desc "HTTP Basic Auth username"
+ end
+
+ newparam :basic_auth_password, :required_features => [:basic_auth] do
+ desc "HTTP Basic Auth password"
+ end
+
+ newparam :identity, :required_features => [:ssh_identity] do
+ desc "SSH identity file"
+ end
+
+ newparam :module, :required_features => [:modules] do
+ desc "The repository module to manage"
+ end
+
+ newparam :remote, :required_features => [:multiple_remotes] do
+ desc "The remote repository to track"
+ defaultto "origin"
+ end
+
+ newparam :configuration, :required_features => [:configuration] do
+ desc "The configuration directory to use"
+ end
+
+ newparam :cvs_rsh, :required_features => [:cvs_rsh] do
+ desc "The value to be used for the CVS_RSH environment variable."
+ end
+
+ newparam :depth, :required_features => [:depth] do
+ desc "The value to be used to do a shallow clone."
+ end
+
+ newparam :branch, :required_features => [:branch] do
+ desc "The name of the branch to clone."
+ end
+
+ newparam :p4config, :required_features => [:p4config] do
+ desc "The Perforce P4CONFIG environment."
+ end
+
+ newparam :submodules, :required_features => [:submodules] do
+ desc "Initialize and update each submodule in the repository."
+ newvalues(:true, :false)
+ defaultto true
+ end
+
+ newparam :conflict do
+ desc "The action to take if conflicts exist between repository and working copy"
+ end
+
+ newparam :trust_server_cert do
+ desc "Trust server certificate"
+ newvalues(:true, :false)
+ defaultto :false
+ end
+
+ autorequire(:package) do
+ ['git', 'git-core', 'mercurial']
+ end
+end
diff --git a/puppet/modules/vcsrepo/metadata.json b/puppet/modules/vcsrepo/metadata.json
new file mode 100644
index 00000000..c505faac
--- /dev/null
+++ b/puppet/modules/vcsrepo/metadata.json
@@ -0,0 +1,81 @@
+{
+ "name": "puppetlabs-vcsrepo",
+ "version": "1.3.2",
+ "author": "Puppet Labs",
+ "summary": "Puppet module providing a type to manage repositories from various version control systems",
+ "license": "GPL-2.0+",
+ "source": "https://github.com/puppetlabs/puppetlabs-vcsrepo",
+ "project_page": "https://github.com/puppetlabs/puppetlabs-vcsrepo",
+ "issues_url": "https://tickets.puppetlabs.com/browse/MODULES",
+ "dependencies": [
+
+ ],
+ "operatingsystem_support": [
+ {
+ "operatingsystem": "RedHat",
+ "operatingsystemrelease": [
+ "5",
+ "6",
+ "7"
+ ]
+ },
+ {
+ "operatingsystem": "CentOS",
+ "operatingsystemrelease": [
+ "5",
+ "6",
+ "7"
+ ]
+ },
+ {
+ "operatingsystem": "OracleLinux",
+ "operatingsystemrelease": [
+ "5",
+ "6",
+ "7"
+ ]
+ },
+ {
+ "operatingsystem": "Scientific",
+ "operatingsystemrelease": [
+ "5",
+ "6",
+ "7"
+ ]
+ },
+ {
+ "operatingsystem": "SLES",
+ "operatingsystemrelease": [
+ "10 SP4",
+ "11 SP1",
+ "12"
+ ]
+ },
+ {
+ "operatingsystem": "Debian",
+ "operatingsystemrelease": [
+ "6",
+ "7",
+ "8"
+ ]
+ },
+ {
+ "operatingsystem": "Ubuntu",
+ "operatingsystemrelease": [
+ "10.04",
+ "12.04",
+ "14.04"
+ ]
+ }
+ ],
+ "requirements": [
+ {
+ "name": "pe",
+ "version_requirement": ">= 3.0.0 < 2015.4.0"
+ },
+ {
+ "name": "puppet",
+ "version_requirement": ">= 3.0.0 < 5.0.0"
+ }
+ ]
+}
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/basic_auth/basic_auth_checkout_http.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/basic_auth/basic_auth_checkout_http.rb
new file mode 100644
index 00000000..421c5f06
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/basic_auth/basic_auth_checkout_http.rb
@@ -0,0 +1,69 @@
+test_name 'C3492 - checkout with basic auth (http protocol)'
+skip_test 'HTTP not supported yet for basic auth using git. See FM-1331'
+
+# Globals
+repo_name = 'testrepo_checkout'
+user = 'foo'
+password = 'bar'
+http_server_script = 'basic_auth_http_daemon.rb'
+
+hosts.each do |host|
+ ruby = '/opt/puppet/bin/ruby' if host.is_pe? || 'ruby'
+ gem = '/opt/puppet/bin/gem' if host.is_pe? || 'gem'
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ step 'setup - start http server' do
+ script =<<-EOF
+ require 'sinatra'
+
+ set :bind, '0.0.0.0'
+ set :static, true
+ set :public_folder, '#{tmpdir}'
+
+
+ use Rack::Auth::Basic do |username, password|
+ username == '#{user}' && password == '#{password}'
+ end
+ EOF
+ create_remote_file(host, "#{tmpdir}/#{http_server_script}", script)
+ on(host, "#{gem} install sinatra")
+ on(host, "#{ruby} #{tmpdir}/#{http_server_script} &")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ on(host, "ps ax | grep '#{ruby} #{tmpdir}/#{http_server_script}' | grep -v grep | awk '{print \"kill -9 \" $1}' | sh ; sleep 1")
+ end
+
+ step 'checkout with puppet using basic auth' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "http://#{host}:4567/testrepo.git",
+ provider => git,
+ basic_auth_username => '#{user}',
+ basic_auth_password => '#{password}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify checkout" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/basic_auth/basic_auth_checkout_https.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/basic_auth/basic_auth_checkout_https.rb
new file mode 100644
index 00000000..753e50ca
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/basic_auth/basic_auth_checkout_https.rb
@@ -0,0 +1,77 @@
+test_name 'C3493 - checkout with basic auth (https protocol)'
+skip_test 'waiting for CA trust solution'
+
+# Globals
+repo_name = 'testrepo_checkout'
+user = 'foo'
+password = 'bar'
+http_server_script = 'basic_auth_https_daemon.rb'
+
+hosts.each do |host|
+ ruby = (host.is_pe? && '/opt/puppet/bin/ruby') || 'ruby'
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ step 'setup - start https server' do
+ script =<<-EOF
+ require 'webrick'
+ require 'webrick/https'
+
+ authenticate = Proc.new do |req, res|
+ WEBrick::HTTPAuth.basic_auth(req, res, '') do |user, password|
+ user == '#{user}' && password == '#{password}'
+ end
+ end
+
+ server = WEBrick::HTTPServer.new(
+ :Port => 8443,
+ :DocumentRoot => "#{tmpdir}",
+ :DocumentRootOptions=> {:HandlerCallback => authenticate},
+ :SSLEnable => true,
+ :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE,
+ :SSLCertificate => OpenSSL::X509::Certificate.new( File.open("#{tmpdir}/server.crt").read),
+ :SSLPrivateKey => OpenSSL::PKey::RSA.new( File.open("#{tmpdir}/server.key").read),
+ :SSLCertName => [ [ "CN",WEBrick::Utils::getservername ] ])
+ WEBrick::Daemon.start
+ server.start
+ EOF
+ create_remote_file(host, "#{tmpdir}/#{http_server_script}", script)
+ on(host, "#{ruby} #{tmpdir}/#{http_server_script}")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ on(host, "ps ax | grep '#{ruby} #{tmpdir}/#{http_server_script}' | grep -v grep | awk '{print \"kill -9 \" $1}' | sh ; sleep 1")
+ end
+
+ step 'checkout with puppet using basic auth' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "http://#{host}:8443/testrepo.git",
+ provider => git,
+ basic_auth_username => '#{user}',
+ basic_auth_password => '#{password}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify checkout" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/basic_auth/negative/basic_auth_checkout_git.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/basic_auth/negative/basic_auth_checkout_git.rb
new file mode 100644
index 00000000..3b47c485
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/basic_auth/negative/basic_auth_checkout_git.rb
@@ -0,0 +1,53 @@
+test_name 'C3494 - checkout with basic auth (git protocol)'
+
+# Globals
+repo_name = 'testrepo_checkout'
+user = 'foo'
+password = 'bar'
+http_server_script = 'basic_auth_http_daemon.rb'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ step 'setup - start git daemon' do
+ install_package(host, 'git-daemon') unless host['platform'] =~ /debian|ubuntu/
+ on(host, "git daemon --base-path=#{tmpdir} --export-all --reuseaddr --verbose --detach")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ on(host, 'pkill -9 git-daemon ; sleep 1')
+ end
+
+ step 'checkout with puppet using basic auth' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "git://#{host}/testrepo.git",
+ provider => git,
+ basic_auth_username => '#{user}',
+ basic_auth_password => '#{password}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify checkout (silent error for basic auth using git protocol)" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/branch_checkout_file.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/branch_checkout_file.rb
new file mode 100644
index 00000000..3d2131c2
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/branch_checkout_file.rb
@@ -0,0 +1,48 @@
+test_name 'C3438 - checkout a branch (file protocol)'
+
+# Globals
+repo_name = 'testrepo_branch_checkout'
+branch = 'a_branch'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'checkout a branch with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ revision => '#{branch}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify checkout is on the #{branch} branch" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "cat #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('branch not found') unless res.stdout.include? "ref: refs/heads/#{branch}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/branch_checkout_file_path.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/branch_checkout_file_path.rb
new file mode 100644
index 00000000..49b034e3
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/branch_checkout_file_path.rb
@@ -0,0 +1,48 @@
+test_name 'C3437 - checkout a branch (file path)'
+
+# Globals
+repo_name = 'testrepo_branch_checkout'
+branch = 'a_branch'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'checkout a branch with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "#{tmpdir}/testrepo.git",
+ provider => git,
+ revision => '#{branch}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify checkout is on the #{branch} branch" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "cat #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('branch not found') unless res.stdout.include? "ref: refs/heads/#{branch}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/branch_checkout_git.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/branch_checkout_git.rb
new file mode 100644
index 00000000..9557de85
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/branch_checkout_git.rb
@@ -0,0 +1,53 @@
+test_name 'C3436 - checkout a branch (git protocol)'
+
+# Globals
+repo_name = 'testrepo_branch_checkout'
+branch = 'a_branch'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+ step 'setup - start git daemon' do
+ install_package(host, 'git-daemon') unless host['platform'] =~ /debian|ubuntu/
+ on(host, "git daemon --base-path=#{tmpdir} --export-all --reuseaddr --verbose --detach")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ on(host, 'pkill -9 git-daemon ; sleep 1')
+ end
+
+ step 'checkout a branch with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "git://#{host}/testrepo.git",
+ provider => git,
+ revision => '#{branch}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify checkout is on the #{branch} branch" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "cat #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('branch not found') unless res.stdout.include? "ref: refs/heads/#{branch}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/branch_checkout_http.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/branch_checkout_http.rb
new file mode 100644
index 00000000..fec60e2a
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/branch_checkout_http.rb
@@ -0,0 +1,61 @@
+test_name 'C3441 - checkout a branch (http protocol)'
+
+# Globals
+repo_name = 'testrepo_branch_checkout'
+branch = 'a_branch'
+
+hosts.each do |host|
+ ruby = (host.is_pe? && '/opt/puppet/bin/ruby') || 'ruby'
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ step 'setup - start http server' do
+ http_daemon =<<-EOF
+ require 'webrick'
+ server = WEBrick::HTTPServer.new(:Port => 8000, :DocumentRoot => "#{tmpdir}")
+ WEBrick::Daemon.start
+ server.start
+ EOF
+ create_remote_file(host, '/tmp/http_daemon.rb', http_daemon)
+ on(host, "#{ruby} /tmp/http_daemon.rb")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ on(host, "ps ax | grep '#{ruby} /tmp/http_daemon.rb' | grep -v grep | awk '{print \"kill -9 \" $1}' | sh ; sleep 1")
+ end
+
+ step 'checkout a branch with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "http://#{host}:8000/testrepo.git",
+ provider => git,
+ revision => '#{branch}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify checkout is on the #{branch} branch" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "cat #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('branch not found') unless res.stdout.include? "ref: refs/heads/#{branch}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/branch_checkout_https.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/branch_checkout_https.rb
new file mode 100644
index 00000000..3474c73d
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/branch_checkout_https.rb
@@ -0,0 +1,68 @@
+test_name 'C3442 - checkout a branch (https protocol)'
+
+# Globals
+repo_name = 'testrepo_branch_checkout'
+branch = 'a_branch'
+
+hosts.each do |host|
+ ruby = (host.is_pe? && '/opt/puppet/bin/ruby') || 'ruby'
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+ step 'setup - start https server' do
+ https_daemon =<<-EOF
+ require 'webrick'
+ require 'webrick/https'
+ server = WEBrick::HTTPServer.new(
+ :Port => 8443,
+ :DocumentRoot => "#{tmpdir}",
+ :SSLEnable => true,
+ :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE,
+ :SSLCertificate => OpenSSL::X509::Certificate.new( File.open("#{tmpdir}/server.crt").read),
+ :SSLPrivateKey => OpenSSL::PKey::RSA.new( File.open("#{tmpdir}/server.key").read),
+ :SSLCertName => [ [ "CN",WEBrick::Utils::getservername ] ])
+ WEBrick::Daemon.start
+ server.start
+ EOF
+ create_remote_file(host, '/tmp/https_daemon.rb', https_daemon)
+ #on(host, "#{ruby} /tmp/https_daemon.rb")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ on(host, "ps ax | grep '#{ruby} /tmp/https_daemon.rb' | grep -v grep | awk '{print \"kill -9 \" $1}' | sh ; sleep 1")
+ end
+
+ step 'checkout a branch with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "https://github.com/johnduarte/testrepo.git",
+ provider => git,
+ revision => '#{branch}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify checkout is on the #{branch} branch" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "cat #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('branch not found') unless res.stdout.include? "ref: refs/heads/#{branch}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/branch_checkout_scp.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/branch_checkout_scp.rb
new file mode 100644
index 00000000..493b3f49
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/branch_checkout_scp.rb
@@ -0,0 +1,59 @@
+test_name 'C3439 - checkout a branch (ssh protocol, scp syntax)'
+
+# Globals
+repo_name = 'testrepo_branch_checkout'
+branch = 'a_branch'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+ step 'setup - establish ssh keys' do
+ # create ssh keys
+ on(host, 'yes | ssh-keygen -q -t rsa -f /root/.ssh/id_rsa -N ""')
+
+ # copy public key to authorized_keys
+ on(host, 'cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys')
+ on(host, 'echo -e "Host *\n\tStrictHostKeyChecking no\n" >> /root/.ssh/config')
+ on(host, 'chown -R root:root /root/.ssh')
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ apply_manifest_on(host, "file{'/root/.ssh/id_rsa': ensure => absent, force => true }", :catch_failures => true)
+ apply_manifest_on(host, "file{'/root/.ssh/id_rsa.pub': ensure => absent, force => true }", :catch_failures => true)
+ end
+
+ step 'checkout a branch with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "root@#{host}:#{tmpdir}/testrepo.git",
+ provider => git,
+ revision => '#{branch}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify checkout is on the #{branch} branch" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "cat #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('branch not found') unless res.stdout.include? "ref: refs/heads/#{branch}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/branch_checkout_ssh.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/branch_checkout_ssh.rb
new file mode 100644
index 00000000..5195ab8c
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/branch_checkout_ssh.rb
@@ -0,0 +1,59 @@
+test_name 'C3440 - checkout a branch (ssh protocol)'
+
+# Globals
+repo_name = 'testrepo_branch_checkout'
+branch = 'a_branch'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+ step 'setup - establish ssh keys' do
+ # create ssh keys
+ on(host, 'yes | ssh-keygen -q -t rsa -f /root/.ssh/id_rsa -N ""')
+
+ # copy public key to authorized_keys
+ on(host, 'cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys')
+ on(host, 'echo -e "Host *\n\tStrictHostKeyChecking no\n" >> /root/.ssh/config')
+ on(host, 'chown -R root:root /root/.ssh')
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ apply_manifest_on(host, "file{'/root/.ssh/id_rsa': ensure => absent, force => true }", :catch_failures => true)
+ apply_manifest_on(host, "file{'/root/.ssh/id_rsa.pub': ensure => absent, force => true }", :catch_failures => true)
+ end
+
+ step 'checkout a branch with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "ssh://root@#{host}#{tmpdir}/testrepo.git",
+ provider => git,
+ revision => '#{branch}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify checkout is on the #{branch} branch" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "cat #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('branch not found') unless res.stdout.include? "ref: refs/heads/#{branch}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/negative/branch_checkout_not_exists.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/negative/branch_checkout_not_exists.rb
new file mode 100644
index 00000000..7b9e64d7
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/branch_checkout/negative/branch_checkout_not_exists.rb
@@ -0,0 +1,46 @@
+test_name 'C3609 - checkout a branch that does not exist'
+
+# Globals
+repo_name = 'testrepo_branch_checkout'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'checkout branch that does not exist with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ revision => 'non_existent_branch',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :expect_failures => true)
+ end
+
+ step 'verify that master branch is checked out' do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "cat #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('branch not found') unless res.stdout.include? "ref: refs/heads/master"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_file.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_file.rb
new file mode 100644
index 00000000..45413a96
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_file.rb
@@ -0,0 +1,46 @@
+test_name 'C3427 - clone (file protocol)'
+
+# Globals
+repo_name = 'testrepo_clone'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'clone with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify checkout is on the master branch" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "cat #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('master not found') unless res.stdout.include? "ref: refs/heads/master"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_file_path.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_file_path.rb
new file mode 100644
index 00000000..a57e05a4
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_file_path.rb
@@ -0,0 +1,46 @@
+test_name 'C3426 - clone (file path)'
+
+# Globals
+repo_name = 'testrepo_clone'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'clone with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "#{tmpdir}/testrepo.git",
+ provider => git,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify checkout is on the master branch" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "cat #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('master not found') unless res.stdout.include? "ref: refs/heads/master"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_git.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_git.rb
new file mode 100644
index 00000000..3bceb5dd
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_git.rb
@@ -0,0 +1,51 @@
+test_name 'C3425 - clone (git protocol)'
+
+# Globals
+repo_name = 'testrepo_clone'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+ step 'setup - start git daemon' do
+ install_package(host, 'git-daemon') unless host['platform'] =~ /debian|ubuntu/
+ on(host, "git daemon --base-path=#{tmpdir} --export-all --reuseaddr --verbose --detach")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ on(host, 'pkill -9 git-daemon ; sleep 1')
+ end
+
+ step 'clone with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "git://#{host}/testrepo.git",
+ provider => git,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify checkout is on the master branch" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "cat #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('master not found') unless res.stdout.include? "ref: refs/heads/master"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_http.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_http.rb
new file mode 100644
index 00000000..f545dab3
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_http.rb
@@ -0,0 +1,59 @@
+test_name 'C3430 - clone (http protocol)'
+
+# Globals
+repo_name = 'testrepo_clone'
+
+hosts.each do |host|
+ ruby = (host.is_pe? && '/opt/puppet/bin/ruby') || 'ruby'
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ step 'setup - start http server' do
+ http_daemon =<<-EOF
+ require 'webrick'
+ server = WEBrick::HTTPServer.new(:Port => 8000, :DocumentRoot => "#{tmpdir}")
+ WEBrick::Daemon.start
+ server.start
+ EOF
+ create_remote_file(host, '/tmp/http_daemon.rb', http_daemon)
+ on(host, "#{ruby} /tmp/http_daemon.rb")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ on(host, "ps ax | grep '#{ruby} /tmp/http_daemon.rb' | grep -v grep | awk '{print \"kill -9 \" $1}' | sh ; sleep 1")
+ end
+
+ step 'clone with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "http://#{host}:8000/testrepo.git",
+ provider => git,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify checkout is on the master branch" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "cat #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('master not found') unless res.stdout.include? "ref: refs/heads/master"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_https.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_https.rb
new file mode 100644
index 00000000..8758435a
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_https.rb
@@ -0,0 +1,66 @@
+test_name 'C3431 - clone (https protocol)'
+
+# Globals
+repo_name = 'testrepo_clone'
+
+hosts.each do |host|
+ ruby = (host.is_pe? && '/opt/puppet/bin/ruby') || 'ruby'
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+ step 'setup - start https server' do
+ https_daemon =<<-EOF
+ require 'webrick'
+ require 'webrick/https'
+ server = WEBrick::HTTPServer.new(
+ :Port => 8443,
+ :DocumentRoot => "#{tmpdir}",
+ :SSLEnable => true,
+ :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE,
+ :SSLCertificate => OpenSSL::X509::Certificate.new( File.open("#{tmpdir}/server.crt").read),
+ :SSLPrivateKey => OpenSSL::PKey::RSA.new( File.open("#{tmpdir}/server.key").read),
+ :SSLCertName => [ [ "CN",WEBrick::Utils::getservername ] ])
+ WEBrick::Daemon.start
+ server.start
+ EOF
+ create_remote_file(host, '/tmp/https_daemon.rb', https_daemon)
+ #on(host, "#{ruby} /tmp/https_daemon.rb")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ on(host, "ps ax | grep '#{ruby} /tmp/https_daemon.rb' | grep -v grep | awk '{print \"kill -9 \" $1}' | sh ; sleep 1")
+ end
+
+ step 'clone with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "https://github.com/johnduarte/testrepo.git",
+ provider => git,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify checkout is on the master branch" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "cat #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('master not found') unless res.stdout.include? "ref: refs/heads/master"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_over_different_exiting_repo_with_force.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_over_different_exiting_repo_with_force.rb
new file mode 100644
index 00000000..3bc3e304
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_over_different_exiting_repo_with_force.rb
@@ -0,0 +1,49 @@
+test_name 'C3511 - clone over an existing repo with force'
+
+# Globals
+repo_name = 'testrepo_already_exists'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ on(host, "mkdir #{tmpdir}/#{repo_name}")
+ on(host, "cd #{tmpdir}/#{repo_name} && git init")
+ on(host, "cd #{tmpdir}/#{repo_name} && touch a && git add a && git commit -m 'a'")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'clone over existing repo with force using puppet' do
+ on(host, "cd #{tmpdir}/#{repo_name} && git log --pretty=format:\"%h\"") do |res|
+ @existing_sha = res.stdout
+ end
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ force => true,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step 'verify new repo has replaced old one' do
+ on(host, "cd #{tmpdir}/#{repo_name} && git log --pretty=format:\"%h\"") do |res|
+ fail_test('original repo not replaced by force') if res.stdout.include? "#{@existing_sha}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_repo_with_excludes_in_repo.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_repo_with_excludes_in_repo.rb
new file mode 100644
index 00000000..dec275fa
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_repo_with_excludes_in_repo.rb
@@ -0,0 +1,46 @@
+test_name 'C3507 - clone repo with excludes in repo'
+
+# Globals
+repo_name = 'testrepo_with_excludes_in_repo'
+exclude1 = 'file1.txt'
+exclude2 ='file2.txt'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'clone repo with excludes in repo with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ excludes => [ '#{exclude1}', '#{exclude2}' ],
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step 'verify exludes are known to git' do
+ on(host, "cat #{tmpdir}/#{repo_name}/.git/info/exclude") do |res|
+ fail_test('exclude not found') unless res.stdout.include? "#{exclude1}"
+ fail_test('exclude not found') unless res.stdout.include? "#{exclude2}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_repo_with_excludes_not_in_repo.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_repo_with_excludes_not_in_repo.rb
new file mode 100644
index 00000000..ba379309
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_repo_with_excludes_not_in_repo.rb
@@ -0,0 +1,46 @@
+test_name 'C3508 - clone repo with excludes not in repo'
+
+# Globals
+repo_name = 'testrepo_with_excludes_not_in_repo'
+exclude1 = 'worh02o'
+exclude2 ='ho398b'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'clone repo with excludes not in repo with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ excludes => [ '#{exclude1}', '#{exclude2}' ],
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step 'verify exludes are known to git' do
+ on(host, "cat #{tmpdir}/#{repo_name}/.git/info/exclude") do |res|
+ fail_test('exclude not found') unless res.stdout.include? "#{exclude1}"
+ fail_test('exclude not found') unless res.stdout.include? "#{exclude2}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_scp.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_scp.rb
new file mode 100644
index 00000000..59370ebd
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_scp.rb
@@ -0,0 +1,57 @@
+test_name 'C3428 - clone (ssh protocol, scp syntax)'
+
+# Globals
+repo_name = 'testrepo_clone'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+ step 'setup - establish ssh keys' do
+ # create ssh keys
+ on(host, 'yes | ssh-keygen -q -t rsa -f /root/.ssh/id_rsa -N ""')
+
+ # copy public key to authorized_keys
+ on(host, 'cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys')
+ on(host, 'echo -e "Host *\n\tStrictHostKeyChecking no\n" >> /root/.ssh/config')
+ on(host, 'chown -R root:root /root/.ssh')
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ apply_manifest_on(host, "file{'/root/.ssh/id_rsa': ensure => absent, force => true }", :catch_failures => true)
+ apply_manifest_on(host, "file{'/root/.ssh/id_rsa.pub': ensure => absent, force => true }", :catch_failures => true)
+ end
+
+ step 'clone with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "root@#{host}:#{tmpdir}/testrepo.git",
+ provider => git,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify checkout is on the master branch" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "cat #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('master not found') unless res.stdout.include? "ref: refs/heads/master"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_ssh.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_ssh.rb
new file mode 100644
index 00000000..5bc06ec8
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/clone_ssh.rb
@@ -0,0 +1,57 @@
+test_name 'C3429 - clone (ssh protocol)'
+
+# Globals
+repo_name = 'testrepo_clone'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+ step 'setup - establish ssh keys' do
+ # create ssh keys
+ on(host, 'yes | ssh-keygen -q -t rsa -f /root/.ssh/id_rsa -N ""')
+
+ # copy public key to authorized_keys
+ on(host, 'cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys')
+ on(host, 'echo -e "Host *\n\tStrictHostKeyChecking no\n" >> /root/.ssh/config')
+ on(host, 'chown -R root:root /root/.ssh')
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ apply_manifest_on(host, "file{'/root/.ssh/id_rsa': ensure => absent, force => true }", :catch_failures => true)
+ apply_manifest_on(host, "file{'/root/.ssh/id_rsa.pub': ensure => absent, force => true }", :catch_failures => true)
+ end
+
+ step 'clone with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "ssh://root@#{host}#{tmpdir}/testrepo.git",
+ provider => git,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify checkout is on the master branch" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "cat #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('master not found') unless res.stdout.include? "ref: refs/heads/master"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/negative/clone_over_different_exiting_repo.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/negative/clone_over_different_exiting_repo.rb
new file mode 100644
index 00000000..1e3b4bb5
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/negative/clone_over_different_exiting_repo.rb
@@ -0,0 +1,47 @@
+test_name 'C3482 - clone over an existing repo'
+
+# Globals
+repo_name = 'testrepo_already_exists'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ on(host, "mkdir #{tmpdir}/#{repo_name}")
+ on(host, "cd #{tmpdir}/#{repo_name} && git init")
+ on(host, "cd #{tmpdir}/#{repo_name} && touch a && git add a && git commit -m 'a'")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'clone over existing repo using puppet' do
+ on(host, "cd #{tmpdir}/#{repo_name} && git log --pretty=format:\"%h\"") do |res|
+ @existing_sha = res.stdout
+ end
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :expect_failures => true)
+ end
+
+ step 'verify original repo was not replaced' do
+ on(host, "cd #{tmpdir}/#{repo_name} && git log --pretty=format:\"%h\"") do |res|
+ fail_test('original repo was replaced without force') unless res.stdout.include? "#{@existing_sha}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/negative/clone_repo_with_exec_excludes.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/negative/clone_repo_with_exec_excludes.rb
new file mode 100644
index 00000000..98053555
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/clone/negative/clone_repo_with_exec_excludes.rb
@@ -0,0 +1,45 @@
+test_name 'C3509 - clone repo with excludes not in repo'
+skip_test 'expectations not defined'
+
+# Globals
+repo_name = 'testrepo_with_excludes_not_in_repo'
+exclude1 = "`exec \"rm -rf /tmp\"`"
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'clone repo with excludes not in repo with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ excludes => [ '#{exclude1}' ],
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step 'verify excludes are known to git' do
+ on(host, "cat #{tmpdir}/#{repo_name}/.git/info/exclude") do |res|
+ fail_test('exclude not found') unless res.stdout.include? "#{exclude1}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/compression_0_checkout.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/compression_0_checkout.rb
new file mode 100644
index 00000000..7ac4c4a0
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/compression_0_checkout.rb
@@ -0,0 +1,43 @@
+test_name 'C3495 - checkout with compression 0'
+
+# Globals
+repo_name = 'testrepo_checkout'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'checkout with compression 0 with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ compression => 0,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step 'verify git repo was checked out' do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/compression_1_checkout.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/compression_1_checkout.rb
new file mode 100644
index 00000000..8b7455d8
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/compression_1_checkout.rb
@@ -0,0 +1,43 @@
+test_name 'C3496 - checkout with compression 1'
+
+# Globals
+repo_name = 'testrepo_checkout'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'checkout with compression 1 with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ compression => 1,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step 'verify git repo was checked out' do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/compression_2_checkout.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/compression_2_checkout.rb
new file mode 100644
index 00000000..81d32c3f
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/compression_2_checkout.rb
@@ -0,0 +1,43 @@
+test_name 'C3497 - checkout with compression 2'
+
+# Globals
+repo_name = 'testrepo_checkout'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'checkout with compression 2 with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ compression => 2,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step 'verify git repo was checked out' do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/compression_3_checkout.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/compression_3_checkout.rb
new file mode 100644
index 00000000..12b60a37
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/compression_3_checkout.rb
@@ -0,0 +1,43 @@
+test_name 'C3498 - checkout with compression 3'
+
+# Globals
+repo_name = 'testrepo_checkout'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'checkout with compression 3 with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ compression => 3,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step 'verify git repo was checked out' do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/compression_4_checkout.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/compression_4_checkout.rb
new file mode 100644
index 00000000..66d2d5eb
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/compression_4_checkout.rb
@@ -0,0 +1,43 @@
+test_name 'C3499 - checkout with compression 4'
+
+# Globals
+repo_name = 'testrepo_checkout'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'checkout with compression 4 with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ compression => 4,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step 'verify git repo was checked out' do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/compression_5_checkout.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/compression_5_checkout.rb
new file mode 100644
index 00000000..b60a9f7f
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/compression_5_checkout.rb
@@ -0,0 +1,43 @@
+test_name 'C3500 - checkout with compression 5'
+
+# Globals
+repo_name = 'testrepo_checkout'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'checkout with compression 5 with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ compression => 5,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step 'verify git repo was checked out' do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/compression_6_checkout.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/compression_6_checkout.rb
new file mode 100644
index 00000000..2f6b075a
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/compression_6_checkout.rb
@@ -0,0 +1,43 @@
+test_name 'C3501 - checkout with compression 6'
+
+# Globals
+repo_name = 'testrepo_checkout'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'checkout with compression 6 with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ compression => 6,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step 'verify git repo was checked out' do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/negative/compression_7_checkout.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/negative/compression_7_checkout.rb
new file mode 100644
index 00000000..e74cca92
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/negative/compression_7_checkout.rb
@@ -0,0 +1,43 @@
+test_name 'C3503 - checkout with compression 7'
+
+# Globals
+repo_name = 'testrepo_checkout'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'checkout with compression 7 with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ compression => 7,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step 'verify git repo was checked out' do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/negative/compression_alpha_checkout.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/negative/compression_alpha_checkout.rb
new file mode 100644
index 00000000..59aaf219
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/negative/compression_alpha_checkout.rb
@@ -0,0 +1,43 @@
+test_name 'C3505 - checkout with compression alpha'
+
+# Globals
+repo_name = 'testrepo_checkout'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'checkout with compression alpha with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ compression => abcde,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step 'verify git repo was checked out' do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/negative/compression_eval_checkout.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/negative/compression_eval_checkout.rb
new file mode 100644
index 00000000..b989e586
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/negative/compression_eval_checkout.rb
@@ -0,0 +1,43 @@
+test_name 'C3504 - checkout with compression 10-5'
+
+# Globals
+repo_name = 'testrepo_checkout'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'checkout with compression 10-5 with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ compression => 10-5,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step 'verify git repo was checked out' do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/negative/compression_exec_checkout.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/negative/compression_exec_checkout.rb
new file mode 100644
index 00000000..e1373afb
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/negative/compression_exec_checkout.rb
@@ -0,0 +1,43 @@
+test_name 'C3506 - checkout with compression exec'
+
+# Globals
+repo_name = 'testrepo_checkout'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'checkout with compression exec with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ compression => "exec 'rm -rf /tmp'",
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step 'verify git repo was checked out' do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/negative/compression_negative_checkout.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/negative/compression_negative_checkout.rb
new file mode 100644
index 00000000..1253db1d
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/compression/negative/compression_negative_checkout.rb
@@ -0,0 +1,43 @@
+test_name 'C3502 - checkout with compression -1'
+
+# Globals
+repo_name = 'testrepo_checkout'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'checkout with compression -1 with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ compression => -1,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step 'verify git repo was checked out' do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/create/create_bare_repo_that_already_exists.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/create/create_bare_repo_that_already_exists.rb
new file mode 100644
index 00000000..ccb8a707
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/create/create_bare_repo_that_already_exists.rb
@@ -0,0 +1,40 @@
+test_name 'C3472 - create bare repo that already exists'
+
+# Globals
+repo_name = 'testrepo_bare_repo_already_exists.git'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create bare repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ on(host, "mkdir #{tmpdir}/#{repo_name}")
+ on(host, "cd #{tmpdir}/#{repo_name} && git --bare init")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'create bare repo that already exists using puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => bare,
+ provider => git,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step 'verify repo does not contain .git directory' do
+ on(host, "ls -al #{tmpdir}/#{repo_name}") do |res|
+ fail_test "found .git for #{repo_name}" if res.stdout.include? ".git"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/create/create_repo_that_already_exists.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/create/create_repo_that_already_exists.rb
new file mode 100644
index 00000000..8fb85435
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/create/create_repo_that_already_exists.rb
@@ -0,0 +1,42 @@
+test_name 'C3470 - create repo that already exists'
+
+# Globals
+repo_name = 'testrepo_already_exists'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ on(host, "cd #{tmpdir} && git clone file://#{tmpdir}/testrepo.git #{repo_name}")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'create repo that already exists using puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ provider => git,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step 'verify repo is on master branch' do
+ on(host, "cat #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ assert_match(/ref: refs\/heads\/master/, stdout, "Git checkout not on master on #{host}")
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/create/negative/create_bare_repo_specifying_revision.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/create/negative/create_bare_repo_specifying_revision.rb
new file mode 100644
index 00000000..5b789df1
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/create/negative/create_bare_repo_specifying_revision.rb
@@ -0,0 +1,38 @@
+test_name 'C3473 - create bare repo specifying revision'
+
+# Globals
+repo_name = 'testrepo_bare.git'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'create bare repo specifying revision using puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => bare,
+ revision => master,
+ provider => git,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :expect_failures => true)
+ end
+
+ step 'verify repo does not contain .git directory' do
+ on(host, "ls -al #{tmpdir}") do |res|
+ fail_test "found repo for #{repo_name}" if res.stdout.include? repo_name
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/group_checkout_file.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/group_checkout_file.rb
new file mode 100644
index 00000000..beea7b80
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/group_checkout_file.rb
@@ -0,0 +1,53 @@
+test_name 'C3487 - checkout as a group (file protocol)'
+
+# Globals
+repo_name = 'testrepo_group_checkout'
+group = 'mygroup'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ step 'setup - create group' do
+ apply_manifest_on(host, "group { '#{group}': ensure => present, }", :catch_failures => true)
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ apply_manifest_on(host, "group { '#{group}': ensure => absent, }", :catch_failures => true)
+ end
+
+ step 'checkout as a group with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ group => '#{group}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify git checkout is own by group #{group}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "stat --format '%U:%G' #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('checkout not owned by group') unless res.stdout.include? ":#{group}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/group_checkout_file_path.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/group_checkout_file_path.rb
new file mode 100644
index 00000000..319a8e74
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/group_checkout_file_path.rb
@@ -0,0 +1,53 @@
+test_name 'C3486 - checkout as a group (file path)'
+
+# Globals
+repo_name = 'testrepo_group_checkout'
+group = 'mygroup'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ step 'setup - create group' do
+ apply_manifest_on(host, "group { '#{group}': ensure => present, }", :catch_failures => true)
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ apply_manifest_on(host, "group { '#{group}': ensure => absent, }", :catch_failures => true)
+ end
+
+ step 'checkout a group with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "#{tmpdir}/testrepo.git",
+ provider => git,
+ group => '#{group}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify git checkout is own by group #{group}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "stat --format '%U:%G' #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('checkout not owned by group') unless res.stdout.include? ":#{group}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/group_checkout_git.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/group_checkout_git.rb
new file mode 100644
index 00000000..e5b9cf29
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/group_checkout_git.rb
@@ -0,0 +1,58 @@
+test_name 'C3485 - checkout as a group (git protocol)'
+
+# Globals
+repo_name = 'testrepo_group_checkout'
+group = 'mygroup'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+ step 'setup - start git daemon' do
+ install_package(host, 'git-daemon') unless host['platform'] =~ /debian|ubuntu/
+ on(host, "git daemon --base-path=#{tmpdir} --export-all --reuseaddr --verbose --detach")
+ end
+
+ step 'setup - create group' do
+ apply_manifest_on(host, "group { '#{group}': ensure => present, }", :catch_failures => true)
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ on(host, 'pkill -9 git-daemon ; sleep 1')
+ apply_manifest_on(host, "group { '#{group}': ensure => absent, }", :catch_failures => true)
+ end
+
+ step 'checkout a group with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "git://#{host}/testrepo.git",
+ provider => git,
+ group => '#{group}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify git checkout is own by group #{group}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "stat --format '%U:%G' #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('checkout not owned by group') unless res.stdout.include? ":#{group}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/group_checkout_http.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/group_checkout_http.rb
new file mode 100644
index 00000000..bf86f2eb
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/group_checkout_http.rb
@@ -0,0 +1,66 @@
+test_name 'C3490 - checkout as a group (http protocol)'
+
+# Globals
+repo_name = 'testrepo_group_checkout'
+group = 'mygroup'
+
+hosts.each do |host|
+ ruby = (host.is_pe? && '/opt/puppet/bin/ruby') || 'ruby'
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ step 'setup - start http server' do
+ http_daemon =<<-EOF
+ require 'webrick'
+ server = WEBrick::HTTPServer.new(:Port => 8000, :DocumentRoot => "#{tmpdir}")
+ WEBrick::Daemon.start
+ server.start
+ EOF
+ create_remote_file(host, '/tmp/http_daemon.rb', http_daemon)
+ on(host, "#{ruby} /tmp/http_daemon.rb")
+ end
+
+ step 'setup - create group' do
+ apply_manifest_on(host, "group { '#{group}': ensure => present, }", :catch_failures => true)
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ on(host, "ps ax | grep '#{ruby} /tmp/http_daemon.rb' | grep -v grep | awk '{print \"kill -9 \" $1}' | sh ; sleep 1")
+ apply_manifest_on(host, "group { '#{group}': ensure => absent, }", :catch_failures => true)
+ end
+
+ step 'checkout a group with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "http://#{host}:8000/testrepo.git",
+ provider => git,
+ group => '#{group}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify git checkout is own by group #{group}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "stat --format '%U:%G' #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('checkout not owned by group') unless res.stdout.include? ":#{group}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/group_checkout_https.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/group_checkout_https.rb
new file mode 100644
index 00000000..c4c645f9
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/group_checkout_https.rb
@@ -0,0 +1,73 @@
+test_name 'C3491 - checkout as a group (https protocol)'
+
+# Globals
+repo_name = 'testrepo_group_checkout'
+group = 'mygroup'
+
+hosts.each do |host|
+ ruby = (host.is_pe? && '/opt/puppet/bin/ruby') || 'ruby'
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+ step 'setup - start https server' do
+ https_daemon =<<-EOF
+ require 'webrick'
+ require 'webrick/https'
+ server = WEBrick::HTTPServer.new(
+ :Port => 8443,
+ :DocumentRoot => "#{tmpdir}",
+ :SSLEnable => true,
+ :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE,
+ :SSLCertificate => OpenSSL::X509::Certificate.new( File.open("#{tmpdir}/server.crt").read),
+ :SSLPrivateKey => OpenSSL::PKey::RSA.new( File.open("#{tmpdir}/server.key").read),
+ :SSLCertName => [ [ "CN",WEBrick::Utils::getservername ] ])
+ WEBrick::Daemon.start
+ server.start
+ EOF
+ create_remote_file(host, '/tmp/https_daemon.rb', https_daemon)
+ #on(host, "#{ruby} /tmp/https_daemon.rb")
+ end
+
+ step 'setup - create group' do
+ apply_manifest_on(host, "group { '#{group}': ensure => present, }", :catch_failures => true)
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ on(host, "ps ax | grep '#{ruby} /tmp/https_daemon.rb' | grep -v grep | awk '{print \"kill -9 \" $1}' | sh ; sleep 1")
+ apply_manifest_on(host, "group { '#{group}': ensure => absent, }", :catch_failures => true)
+ end
+
+ step 'checkout as a group with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "https://github.com/johnduarte/testrepo.git",
+ provider => git,
+ group => '#{group}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify git checkout is own by group #{group}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "stat --format '%U:%G' #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('checkout not owned by group') unless res.stdout.include? ":#{group}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/group_checkout_scp.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/group_checkout_scp.rb
new file mode 100644
index 00000000..c65acc43
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/group_checkout_scp.rb
@@ -0,0 +1,64 @@
+test_name 'C3488 - checkout as a group (ssh protocol, scp syntax)'
+
+# Globals
+repo_name = 'testrepo_group_checkout'
+group = 'mygroup'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+ step 'setup - establish ssh keys' do
+ # create ssh keys
+ on(host, 'yes | ssh-keygen -q -t rsa -f /root/.ssh/id_rsa -N ""')
+
+ # copy public key to authorized_keys
+ on(host, 'cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys')
+ on(host, 'echo -e "Host *\n\tStrictHostKeyChecking no\n" >> /root/.ssh/config')
+ on(host, 'chown -R root:root /root/.ssh')
+ end
+
+ step 'setup - create group' do
+ apply_manifest_on(host, "group { '#{group}': ensure => present, }", :catch_failures => true)
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ apply_manifest_on(host, "file{'/root/.ssh/id_rsa': ensure => absent, force => true }", :catch_failures => true)
+ apply_manifest_on(host, "file{'/root/.ssh/id_rsa.pub': ensure => absent, force => true }", :catch_failures => true)
+ apply_manifest_on(host, "group { '#{group}': ensure => absent, }", :catch_failures => true)
+ end
+
+ step 'checkout as a group with puppet (scp syntax)' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "root@#{host}:#{tmpdir}/testrepo.git",
+ provider => git,
+ group => '#{group}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify git checkout is own by group #{group}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "stat --format '%U:%G' #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('checkout not owned by group') unless res.stdout.include? ":#{group}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/group_checkout_ssh.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/group_checkout_ssh.rb
new file mode 100644
index 00000000..cccad19c
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/group_checkout_ssh.rb
@@ -0,0 +1,64 @@
+test_name 'C3489 - checkout as a group (ssh protocol)'
+
+# Globals
+repo_name = 'testrepo_group_checkout'
+group = 'mygroup'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+ step 'setup - establish ssh keys' do
+ # create ssh keys
+ on(host, 'yes | ssh-keygen -q -t rsa -f /root/.ssh/id_rsa -N ""')
+
+ # copy public key to authorized_keys
+ on(host, 'cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys')
+ on(host, 'echo -e "Host *\n\tStrictHostKeyChecking no\n" >> /root/.ssh/config')
+ on(host, 'chown -R root:root /root/.ssh')
+ end
+
+ step 'setup - create group' do
+ apply_manifest_on(host, "group { '#{group}': ensure => present, }", :catch_failures => true)
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ apply_manifest_on(host, "file{'/root/.ssh/id_rsa': ensure => absent, force => true }", :catch_failures => true)
+ apply_manifest_on(host, "file{'/root/.ssh/id_rsa.pub': ensure => absent, force => true }", :catch_failures => true)
+ apply_manifest_on(host, "group { '#{group}': ensure => absent, }", :catch_failures => true)
+ end
+
+ step 'checkout as a group with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "ssh://root@#{host}#{tmpdir}/testrepo.git",
+ provider => git,
+ group => '#{group}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify git checkout is own by group #{group}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "stat --format '%U:%G' #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('checkout not owned by group') unless res.stdout.include? ":#{group}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/negative/group_checkout_file_non_existent_group.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/negative/group_checkout_file_non_existent_group.rb
new file mode 100644
index 00000000..081642d9
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/group_checkout/negative/group_checkout_file_non_existent_group.rb
@@ -0,0 +1,51 @@
+test_name 'C3484 - checkout as a group that is not on system'
+
+# Globals
+repo_name = 'testrepo_group_checkout'
+group = 'mygroup'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ step 'setup - delete group' do
+ apply_manifest_on(host, "group { '#{group}': ensure => absent, }", :catch_failures => true)
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'checkout as a group with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ group => '#{group}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :expect_failures => true)
+ end
+
+ step "verify git checkout is NOT owned by group #{group}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "stat --format '%U:%G' #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('checkout not owned by group') if res.stdout.include? ":#{group}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/negative/revision_checkout_not_exists.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/negative/revision_checkout_not_exists.rb
new file mode 100644
index 00000000..85f1fcc0
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/negative/revision_checkout_not_exists.rb
@@ -0,0 +1,46 @@
+test_name 'C3614 - checkout a revision that does not exist'
+
+# Globals
+repo_name = 'testrepo_revision_checkout'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'checkout revision that does not exist with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ revision => '11111111111111111',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :expect_failures => true)
+ end
+
+ step 'verify that master revision is checked out' do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "cat #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('revision not found') unless res.stdout.include? "ref: refs/heads/master"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/revision_checkout_file.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/revision_checkout_file.rb
new file mode 100644
index 00000000..b17dc73d
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/revision_checkout_file.rb
@@ -0,0 +1,53 @@
+test_name 'C3452 - checkout a revision (file protocol)'
+
+# Globals
+repo_name = 'testrepo_revision_checkout'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'get revision sha from repo' do
+ on(host, "git --git-dir=#{tmpdir}/testrepo.git rev-list HEAD | tail -1") do |res|
+ @sha = res.stdout.chomp
+ end
+ end
+
+ step 'checkout a revision with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ revision => '#{@sha}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify repo is checked out to revision #{@sha}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "cat #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('revision not found') unless res.stdout.include? "#{@sha}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/revision_checkout_file_path.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/revision_checkout_file_path.rb
new file mode 100644
index 00000000..c80eb81b
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/revision_checkout_file_path.rb
@@ -0,0 +1,53 @@
+test_name 'C3451 - checkout a revision (file path)'
+
+# Globals
+repo_name = 'testrepo_revision_checkout'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'get revision sha from repo' do
+ on(host, "git --git-dir=#{tmpdir}/testrepo.git rev-list HEAD | tail -1") do |res|
+ @sha = res.stdout.chomp
+ end
+ end
+
+ step 'checkout a revision with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "#{tmpdir}/testrepo.git",
+ provider => git,
+ revision => '#{@sha}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify repo is checked out to revision #{@sha}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "cat #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('revision not found') unless res.stdout.include? "#{@sha}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/revision_checkout_git.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/revision_checkout_git.rb
new file mode 100644
index 00000000..69a7fe22
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/revision_checkout_git.rb
@@ -0,0 +1,58 @@
+test_name 'C3450 - checkout a revision (git protocol)'
+
+# Globals
+repo_name = 'testrepo_revision_checkout'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+ step 'setup - start git daemon' do
+ install_package(host, 'git-daemon') unless host['platform'] =~ /debian|ubuntu/
+ on(host, "git daemon --base-path=#{tmpdir} --export-all --reuseaddr --verbose --detach")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ on(host, 'pkill -9 git-daemon ; sleep 1')
+ end
+
+ step 'get revision sha from repo' do
+ on(host, "git --git-dir=#{tmpdir}/testrepo.git rev-list HEAD | tail -1") do |res|
+ @sha = res.stdout.chomp
+ end
+ end
+
+ step 'checkout a revision with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "git://#{host}/testrepo.git",
+ provider => git,
+ revision => '#{@sha}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify checkout is set to revision #{@sha}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "cat #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('revision not found') unless res.stdout.include? "#{@sha}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/revision_checkout_http.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/revision_checkout_http.rb
new file mode 100644
index 00000000..7cac163d
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/revision_checkout_http.rb
@@ -0,0 +1,66 @@
+test_name 'C3455 - checkout a revision (http protocol)'
+
+# Globals
+repo_name = 'testrepo_revision_checkout'
+
+hosts.each do |host|
+ ruby = (host.is_pe? && '/opt/puppet/bin/ruby') || 'ruby'
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ step 'setup - start http server' do
+ http_daemon =<<-EOF
+ require 'webrick'
+ server = WEBrick::HTTPServer.new(:Port => 8000, :DocumentRoot => "#{tmpdir}")
+ WEBrick::Daemon.start
+ server.start
+ EOF
+ create_remote_file(host, '/tmp/http_daemon.rb', http_daemon)
+ on(host, "#{ruby} /tmp/http_daemon.rb")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ on(host, "ps ax | grep '#{ruby} /tmp/http_daemon.rb' | grep -v grep | awk '{print \"kill -9 \" $1}' | sh ; sleep 1")
+ end
+
+ step 'get revision sha from repo' do
+ on(host, "git --git-dir=#{tmpdir}/testrepo.git rev-list HEAD | tail -1") do |res|
+ @sha = res.stdout.chomp
+ end
+ end
+
+ step 'checkout a revision with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "http://#{host}:8000/testrepo.git",
+ provider => git,
+ revision => '#{@sha}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify checkout is set to revision #{@sha}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "cat #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('revision not found') unless res.stdout.include? "#{@sha}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/revision_checkout_https.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/revision_checkout_https.rb
new file mode 100644
index 00000000..1c705a5e
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/revision_checkout_https.rb
@@ -0,0 +1,74 @@
+test_name 'C3456 - checkout a revision (https protocol)'
+
+# Globals
+repo_name = 'testrepo_revision_checkout'
+
+hosts.each do |host|
+ ruby = (host.is_pe? && '/opt/puppet/bin/ruby') || 'ruby'
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+ step 'setup - start https server' do
+ https_daemon =<<-EOF
+ require 'webrick'
+ require 'webrick/https'
+ server = WEBrick::HTTPServer.new(
+ :Port => 8443,
+ :DocumentRoot => "#{tmpdir}",
+ :SSLEnable => true,
+ :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE,
+ :SSLCertificate => OpenSSL::X509::Certificate.new( File.open("#{tmpdir}/server.crt").read),
+ :SSLPrivateKey => OpenSSL::PKey::RSA.new( File.open("#{tmpdir}/server.key").read),
+ :SSLCertName => [ [ "CN",WEBrick::Utils::getservername ] ])
+ WEBrick::Daemon.start
+ server.start
+ EOF
+ create_remote_file(host, '/tmp/https_daemon.rb', https_daemon)
+ #on(host, "#{ruby} /tmp/https_daemon.rb")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ on(host, "ps ax | grep '#{ruby} /tmp/https_daemon.rb' | grep -v grep | awk '{print \"kill -9 \" $1}' | sh ; sleep 1")
+ end
+
+ step 'get revision sha from repo' do
+ on(host, "git clone https://github.com/johnduarte/testrepo.git #{tmpdir}/foo")
+ on(host, "git --git-dir=#{tmpdir}/foo/.git rev-list HEAD | tail -1") do |res|
+ @sha = res.stdout.chomp
+ end
+ end
+
+ step 'checkout a revision with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "https://github.com/johnduarte/testrepo.git",
+ provider => git,
+ revision => '#{@sha}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify checkout is set to revision #{@sha}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "cat #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('revision not found') unless res.stdout.include? "#{@sha}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/revision_checkout_scp.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/revision_checkout_scp.rb
new file mode 100644
index 00000000..b5dbd244
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/revision_checkout_scp.rb
@@ -0,0 +1,64 @@
+test_name 'C3453 - checkout a revision (ssh protocol, scp syntax)'
+
+# Globals
+repo_name = 'testrepo_revision_checkout'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+ step 'setup - establish ssh keys' do
+ # create ssh keys
+ on(host, 'yes | ssh-keygen -q -t rsa -f /root/.ssh/id_rsa -N ""')
+
+ # copy public key to authorized_keys
+ on(host, 'cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys')
+ on(host, 'echo -e "Host *\n\tStrictHostKeyChecking no\n" >> /root/.ssh/config')
+ on(host, 'chown -R root:root /root/.ssh')
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ apply_manifest_on(host, "file{'/root/.ssh/id_rsa': ensure => absent, force => true }", :catch_failures => true)
+ apply_manifest_on(host, "file{'/root/.ssh/id_rsa.pub': ensure => absent, force => true }", :catch_failures => true)
+ end
+
+ step 'get revision sha from repo' do
+ on(host, "git --git-dir=#{tmpdir}/testrepo.git rev-list HEAD | tail -1") do |res|
+ @sha = res.stdout.chomp
+ end
+ end
+
+ step 'checkout a revision with puppet (scp syntax)' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "root@#{host}:#{tmpdir}/testrepo.git",
+ provider => git,
+ revision => '#{@sha}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify checkout is set to revision #{@sha}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "cat #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('revision not found') unless res.stdout.include? "#{@sha}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/revision_checkout_ssh.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/revision_checkout_ssh.rb
new file mode 100644
index 00000000..222653e4
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/revision_checkout/revision_checkout_ssh.rb
@@ -0,0 +1,64 @@
+test_name 'C3454 - checkout a revision (ssh protocol)'
+
+# Globals
+repo_name = 'testrepo_revision_checkout'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+ step 'setup - establish ssh keys' do
+ # create ssh keys
+ on(host, 'yes | ssh-keygen -q -t rsa -f /root/.ssh/id_rsa -N ""')
+
+ # copy public key to authorized_keys
+ on(host, 'cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys')
+ on(host, 'echo -e "Host *\n\tStrictHostKeyChecking no\n" >> /root/.ssh/config')
+ on(host, 'chown -R root:root /root/.ssh')
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ apply_manifest_on(host, "file{'/root/.ssh/id_rsa': ensure => absent, force => true }", :catch_failures => true)
+ apply_manifest_on(host, "file{'/root/.ssh/id_rsa.pub': ensure => absent, force => true }", :catch_failures => true)
+ end
+
+ step 'get revision sha from repo' do
+ on(host, "git --git-dir=#{tmpdir}/testrepo.git rev-list HEAD | tail -1") do |res|
+ @sha = res.stdout.chomp
+ end
+ end
+
+ step 'checkout a revision with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "ssh://root@#{host}#{tmpdir}/testrepo.git",
+ provider => git,
+ revision => '#{@sha}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify checkout is set to revision #{@sha}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "cat #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('revision not found') unless res.stdout.include? "#{@sha}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/negative/shallow_clone_exec_depth.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/negative/shallow_clone_exec_depth.rb
new file mode 100644
index 00000000..f01a488e
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/negative/shallow_clone_exec_depth.rb
@@ -0,0 +1,43 @@
+test_name 'C3608 - shallow clone repo depth hostile input'
+
+# Globals
+repo_name = 'testrepo_shallow_clone'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'shallow clone repo with puppet (bad input ignored, full clone checkedout)' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ depth => "exec 'rm -rf /tmp'",
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step 'verify checkout is NOT shallow' do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('shallow not found') if res.stdout.include? "shallow"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/negative/shallow_clone_file_path.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/negative/shallow_clone_file_path.rb
new file mode 100644
index 00000000..47fb338b
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/negative/shallow_clone_file_path.rb
@@ -0,0 +1,44 @@
+test_name 'C3475 - shallow clone repo minimal depth = 1 (file path protocol)'
+skip_test 'Not currently supported. See FM-1285'
+
+# Globals
+repo_name = 'testrepo_shallow_clone'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'shallow clone repo with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "#{tmpdir}/testrepo.git",
+ provider => git,
+ depth => 1,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step 'git does not support shallow clone via file path: verify checkout is NOT created' do
+ on(host, "ls #{tmpdir}") do |res|
+ fail_test('checkout found') if res.stdout.include? "#{repo_name}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/negative/shallow_clone_http.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/negative/shallow_clone_http.rb
new file mode 100644
index 00000000..723a0b62
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/negative/shallow_clone_http.rb
@@ -0,0 +1,55 @@
+test_name 'C3479 - shallow clone repo minimal depth = 1 (http protocol)'
+
+# Globals
+repo_name = 'testrepo_shallow_clone'
+
+hosts.each do |host|
+ ruby = (host.is_pe? && '/opt/puppet/bin/ruby') || 'ruby'
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ step 'setup - start http server' do
+ http_daemon =<<-EOF
+ require 'webrick'
+ server = WEBrick::HTTPServer.new(:Port => 8000, :DocumentRoot => "#{tmpdir}")
+ WEBrick::Daemon.start
+ server.start
+ EOF
+ create_remote_file(host, '/tmp/http_daemon.rb', http_daemon)
+ on(host, "#{ruby} /tmp/http_daemon.rb")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ on(host, 'ps ax | grep "#{ruby} /tmp/http_daemon.rb" | grep -v grep | awk \'{print "kill -9 " $1}\' | sh ; sleep 1')
+ end
+
+ step 'shallow clone repo with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "http://#{host}:8000/testrepo.git",
+ provider => git,
+ depth => 1,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :expect_failures => true)
+ end
+
+ step 'git does not support shallow clone via HTTP: verify checkout is NOT created' do
+ on(host, "ls #{tmpdir}") do |res|
+ fail_test('checkout found') if res.stdout.include? "#{repo_name}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/negative/shallow_clone_negative_depth.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/negative/shallow_clone_negative_depth.rb
new file mode 100644
index 00000000..869620d2
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/negative/shallow_clone_negative_depth.rb
@@ -0,0 +1,43 @@
+test_name 'C3607 - shallow clone repo depth = -1'
+
+# Globals
+repo_name = 'testrepo_shallow_clone'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'shallow clone repo with puppet (bad input ignored, full clone checkedout)' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ depth => -1,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step 'verify checkout is NOT shallow' do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('shallow not found') if res.stdout.include? "shallow"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/negative/shallow_clone_overflow_depth.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/negative/shallow_clone_overflow_depth.rb
new file mode 100644
index 00000000..5da9fd7e
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/negative/shallow_clone_overflow_depth.rb
@@ -0,0 +1,45 @@
+test_name 'C3606 - shallow clone repo depth overflow 64bit integer'
+
+# Globals
+repo_name = 'testrepo_shallow_clone'
+
+pending_test("The overflow can't be handled on some git versions")
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'shallow clone repo with puppet (bad input ignored, full clone checkedout)' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ depth => 18446744073709551616,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step 'verify checkout is NOT shallow' do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('shallow not found') if res.stdout.include? "shallow"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/shallow_clone_file.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/shallow_clone_file.rb
new file mode 100644
index 00000000..9e2abe28
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/shallow_clone_file.rb
@@ -0,0 +1,47 @@
+test_name 'C3476 - shallow clone repo minimal depth = 1 (file protocol)'
+
+# Globals
+repo_name = 'testrepo_shallow_clone'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'shallow clone repo with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ depth => 1,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step 'verify checkout is shallow and of the correct depth' do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('shallow not found') unless res.stdout.include? "shallow"
+ end
+
+ on(host, "wc -l #{tmpdir}/#{repo_name}/.git/shallow") do |res|
+ fail_test('shallow not found') unless res.stdout.include? "1 #{tmpdir}/#{repo_name}/.git/shallow"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/shallow_clone_git.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/shallow_clone_git.rb
new file mode 100644
index 00000000..49683d24
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/shallow_clone_git.rb
@@ -0,0 +1,52 @@
+test_name 'C3474 - shallow clone repo minimal depth = 1 (git protocol)'
+
+# Globals
+repo_name = 'testrepo_shallow_clone'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+ step 'setup - start git daemon' do
+ install_package(host, 'git-daemon') unless host['platform'] =~ /debian|ubuntu/
+ on(host, "git daemon --base-path=#{tmpdir} --export-all --reuseaddr --verbose --detach")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ on(host, 'pkill -9 git-daemon ; sleep 1')
+ end
+
+ step 'shallow clone repo with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "git://#{host}/testrepo.git",
+ provider => git,
+ depth => 1,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step 'verify checkout is shallow and of the correct depth' do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('shallow not found') unless res.stdout.include? "shallow"
+ end
+
+ on(host, "wc -l #{tmpdir}/#{repo_name}/.git/shallow") do |res|
+ fail_test('shallow not found') unless res.stdout.include? "1 #{tmpdir}/#{repo_name}/.git/shallow"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/shallow_clone_https.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/shallow_clone_https.rb
new file mode 100644
index 00000000..23927287
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/shallow_clone_https.rb
@@ -0,0 +1,68 @@
+test_name 'C3480 - shallow clone repo minimal depth = 1 (https protocol)'
+skip_test 'Not currently supported. See FM-1286'
+
+# Globals
+repo_name = 'testrepo_shallow_clone'
+
+hosts.each do |host|
+ ruby = (host.is_pe? && '/opt/puppet/bin/ruby') || 'ruby'
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+ step 'setup - start https server' do
+ https_daemon =<<-EOF
+ require 'webrick'
+ require 'webrick/https'
+ server = WEBrick::HTTPServer.new(
+ :Port => 8443,
+ :DocumentRoot => "#{tmpdir}",
+ :SSLEnable => true,
+ :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE,
+ :SSLCertificate => OpenSSL::X509::Certificate.new( File.open("#{tmpdir}/server.crt").read),
+ :SSLPrivateKey => OpenSSL::PKey::RSA.new( File.open("#{tmpdir}/server.key").read),
+ :SSLCertName => [ [ "CN",WEBrick::Utils::getservername ] ])
+ WEBrick::Daemon.start
+ server.start
+ EOF
+ create_remote_file(host, '/tmp/https_daemon.rb', https_daemon)
+ #on(host, "#{ruby} /tmp/https_daemon.rb")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ on(host, 'ps ax | grep "#{ruby} /tmp/https_daemon.rb" | grep -v grep | awk \'{print "kill -9 " $1}\' | sh ; sleep 1')
+ end
+
+ step 'shallow clone repo with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "https://github.com/johnduarte/testrepo.git",
+ provider => git,
+ depth => 1,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step 'verify checkout is shallow and of the correct depth' do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('shallow not found') unless res.stdout.include? "shallow"
+ end
+
+ on(host, "wc -l #{tmpdir}/#{repo_name}/.git/shallow") do |res|
+ fail_test('shallow not found') unless res.stdout.include? "1 #{tmpdir}/#{repo_name}/.git/shallow"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/shallow_clone_scp.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/shallow_clone_scp.rb
new file mode 100644
index 00000000..1d5b35a1
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/shallow_clone_scp.rb
@@ -0,0 +1,58 @@
+test_name 'C3478 - shallow clone repo minimal depth = 1 (ssh protocol, scp syntax)'
+
+# Globals
+repo_name = 'testrepo_shallow_clone'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+ step 'setup - establish ssh keys' do
+ # create ssh keys
+ on(host, 'yes | ssh-keygen -q -t rsa -f /root/.ssh/id_rsa -N ""')
+
+ # copy public key to authorized_keys
+ on(host, 'cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys')
+ on(host, 'echo -e "Host *\n\tStrictHostKeyChecking no\n" >> /root/.ssh/config')
+ on(host, 'chown -R root:root /root/.ssh')
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ apply_manifest_on(host, "file{'/root/.ssh/id_rsa': ensure => absent, force => true }", :catch_failures => true)
+ apply_manifest_on(host, "file{'/root/.ssh/id_rsa.pub': ensure => absent, force => true }", :catch_failures => true)
+ end
+
+ step 'shallow clone repo with puppet (scp syntax)' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "root@#{host}:#{tmpdir}/testrepo.git",
+ provider => git,
+ depth => 1,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step 'verify checkout is shallow and of the correct depth' do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('shallow not found') unless res.stdout.include? "shallow"
+ end
+
+ on(host, "wc -l #{tmpdir}/#{repo_name}/.git/shallow") do |res|
+ fail_test('shallow not found') unless res.stdout.include? "1 #{tmpdir}/#{repo_name}/.git/shallow"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/shallow_clone_ssh.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/shallow_clone_ssh.rb
new file mode 100644
index 00000000..0f00b30e
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/shallow_clone_ssh.rb
@@ -0,0 +1,58 @@
+test_name 'C3477 - shallow clone repo minimal depth = 1 (ssh protocol)'
+
+# Globals
+repo_name = 'testrepo_shallow_clone'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+ step 'setup - establish ssh keys' do
+ # create ssh keys
+ on(host, 'yes | ssh-keygen -q -t rsa -f /root/.ssh/id_rsa -N ""')
+
+ # copy public key to authorized_keys
+ on(host, 'cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys')
+ on(host, 'echo -e "Host *\n\tStrictHostKeyChecking no\n" >> /root/.ssh/config')
+ on(host, 'chown -R root:root /root/.ssh')
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ apply_manifest_on(host, "file{'/root/.ssh/id_rsa': ensure => absent, force => true }", :catch_failures => true)
+ apply_manifest_on(host, "file{'/root/.ssh/id_rsa.pub': ensure => absent, force => true }", :catch_failures => true)
+ end
+
+ step 'shallow clone repo with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "ssh://root@#{host}#{tmpdir}/testrepo.git",
+ provider => git,
+ depth => 1,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step 'verify checkout is shallow and of the correct depth' do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('shallow not found') unless res.stdout.include? "shallow"
+ end
+
+ on(host, "wc -l #{tmpdir}/#{repo_name}/.git/shallow") do |res|
+ fail_test('shallow not found') unless res.stdout.include? "1 #{tmpdir}/#{repo_name}/.git/shallow"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/shallow_clone_zero_depth.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/shallow_clone_zero_depth.rb
new file mode 100644
index 00000000..34c624f7
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/shallow_clone/shallow_clone_zero_depth.rb
@@ -0,0 +1,43 @@
+test_name 'C3404 - shallow clone repo depth = 0 non shallow'
+
+# Globals
+repo_name = 'testrepo_shallow_clone'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'shallow clone repo with puppet (zero depth means not shallow)' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ depth => 0,
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step 'verify checkout is NOT shallow' do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('shallow found') if res.stdout.include? "shallow"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/negative/tag_checkout_not_exists.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/negative/tag_checkout_not_exists.rb
new file mode 100644
index 00000000..1849f029
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/negative/tag_checkout_not_exists.rb
@@ -0,0 +1,47 @@
+test_name 'C3612 - checkout a tag that does not exist'
+
+# Globals
+repo_name = 'testrepo_tag_checkout'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'checkout tag that does not exist with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ tag => '11111111111111111',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step 'verify that master tag is checked out' do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "cat #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('tag not found') unless res.stdout.include? "ref: refs/heads/master"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/tag_checkout_file.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/tag_checkout_file.rb
new file mode 100644
index 00000000..9c744855
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/tag_checkout_file.rb
@@ -0,0 +1,48 @@
+test_name 'C3445 - checkout a tag (file protocol)'
+
+# Globals
+repo_name = 'testrepo_tag_checkout'
+tag = '0.0.2'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'checkout a tag with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ revision => '#{tag}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify checkout out tag is #{tag}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host,"git --git-dir=#{tmpdir}/#{repo_name}/.git name-rev HEAD") do |res|
+ fail_test('tag not found') unless res.stdout.include? "#{tag}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/tag_checkout_file_path.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/tag_checkout_file_path.rb
new file mode 100644
index 00000000..01f319cb
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/tag_checkout_file_path.rb
@@ -0,0 +1,48 @@
+test_name 'C3444 - checkout a tag (file path)'
+
+# Globals
+repo_name = 'testrepo_tag_checkout'
+tag = '0.0.2'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'checkout a tag with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "#{tmpdir}/testrepo.git",
+ provider => git,
+ revision => '#{tag}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify checkout out tag is #{tag}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host,"git --git-dir=#{tmpdir}/#{repo_name}/.git name-rev HEAD") do |res|
+ fail_test('tag not found') unless res.stdout.include? "#{tag}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/tag_checkout_git.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/tag_checkout_git.rb
new file mode 100644
index 00000000..42e689c8
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/tag_checkout_git.rb
@@ -0,0 +1,59 @@
+test_name 'C3443 - checkout a tag (git protocol)'
+
+# Globals
+repo_name = 'testrepo_tag_checkout'
+tag = '0.0.2'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+ step 'setup - start git daemon' do
+ install_package(host, 'git-daemon') unless host['platform'] =~ /debian|ubuntu/
+ on(host, "git daemon --base-path=#{tmpdir} --export-all --reuseaddr --verbose --detach")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ on(host, 'pkill -9 git-daemon ; sleep 1')
+ end
+
+ step 'get tag sha from repo' do
+ on(host, "git --git-dir=#{tmpdir}/testrepo.git rev-list HEAD | tail -1") do |res|
+ @sha = res.stdout.chomp
+ end
+ end
+
+ step 'checkout a tag with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "git://#{host}/testrepo.git",
+ provider => git,
+ revision => '#{tag}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify checkout out tag is #{tag}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host,"git --git-dir=#{tmpdir}/#{repo_name}/.git name-rev HEAD") do |res|
+ fail_test('tag not found') unless res.stdout.include? "#{tag}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/tag_checkout_http.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/tag_checkout_http.rb
new file mode 100644
index 00000000..3ea363c4
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/tag_checkout_http.rb
@@ -0,0 +1,67 @@
+test_name 'C3448 - checkout a tag (http protocol)'
+
+# Globals
+repo_name = 'testrepo_tag_checkout'
+tag = '0.0.2'
+
+hosts.each do |host|
+ ruby = (host.is_pe? && '/opt/puppet/bin/ruby') || 'ruby'
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ step 'setup - start http server' do
+ http_daemon =<<-EOF
+ require 'webrick'
+ server = WEBrick::HTTPServer.new(:Port => 8000, :DocumentRoot => "#{tmpdir}")
+ WEBrick::Daemon.start
+ server.start
+ EOF
+ create_remote_file(host, '/tmp/http_daemon.rb', http_daemon)
+ on(host, "#{ruby} /tmp/http_daemon.rb")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ on(host, "ps ax | grep '#{ruby} /tmp/http_daemon.rb' | grep -v grep | awk '{print \"kill -9 \" $1}' | sh ; sleep 1")
+ end
+
+ step 'get tag sha from repo' do
+ on(host, "git --git-dir=#{tmpdir}/testrepo.git rev-list HEAD | tail -1") do |res|
+ @sha = res.stdout.chomp
+ end
+ end
+
+ step 'checkout a tag with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "http://#{host}:8000/testrepo.git",
+ provider => git,
+ revision => '#{tag}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify checkout out tag is #{tag}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host,"git --git-dir=#{tmpdir}/#{repo_name}/.git name-rev HEAD") do |res|
+ fail_test('tag not found') unless res.stdout.include? "#{tag}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/tag_checkout_https.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/tag_checkout_https.rb
new file mode 100644
index 00000000..d508c436
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/tag_checkout_https.rb
@@ -0,0 +1,74 @@
+test_name 'C3449 - checkout a tag (https protocol)'
+
+# Globals
+repo_name = 'testrepo_tag_checkout'
+tag = '0.0.2'
+
+hosts.each do |host|
+ ruby = (host.is_pe? && '/opt/puppet/bin/ruby') || 'ruby'
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+ step 'setup - start https server' do
+ https_daemon =<<-EOF
+ require 'webrick'
+ require 'webrick/https'
+ server = WEBrick::HTTPServer.new(
+ :Port => 8443,
+ :DocumentRoot => "#{tmpdir}",
+ :SSLEnable => true,
+ :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE,
+ :SSLCertificate => OpenSSL::X509::Certificate.new( File.open("#{tmpdir}/server.crt").read),
+ :SSLPrivateKey => OpenSSL::PKey::RSA.new( File.open("#{tmpdir}/server.key").read),
+ :SSLCertName => [ [ "CN",WEBrick::Utils::getservername ] ])
+ WEBrick::Daemon.start
+ server.start
+ EOF
+ create_remote_file(host, '/tmp/https_daemon.rb', https_daemon)
+ #on(host, "#{ruby} /tmp/https_daemon.rb")
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ on(host, "ps ax | grep '#{ruby} /tmp/https_daemon.rb' | grep -v grep | awk '{print \"kill -9 \" $1}' | sh ; sleep 1")
+ end
+
+ step 'get tag sha from repo' do
+ on(host, "git --git-dir=#{tmpdir}/testrepo.git rev-list HEAD | tail -1") do |res|
+ @sha = res.stdout.chomp
+ end
+ end
+
+ step 'checkout a tag with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "https://github.com/johnduarte/testrepo.git",
+ provider => git,
+ revision => '#{tag}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify checkout out tag is #{tag}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host,"git --git-dir=#{tmpdir}/#{repo_name}/.git name-rev HEAD") do |res|
+ fail_test('tag not found') unless res.stdout.include? "#{tag}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/tag_checkout_scp.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/tag_checkout_scp.rb
new file mode 100644
index 00000000..cb96b4e2
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/tag_checkout_scp.rb
@@ -0,0 +1,65 @@
+test_name 'C3446 - checkout a tag (ssh protocol, scp syntax)'
+
+# Globals
+repo_name = 'testrepo_tag_checkout'
+tag = '0.0.2'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+ step 'setup - establish ssh keys' do
+ # create ssh keys
+ on(host, 'yes | ssh-keygen -q -t rsa -f /root/.ssh/id_rsa -N ""')
+
+ # copy public key to authorized_keys
+ on(host, 'cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys')
+ on(host, 'echo -e "Host *\n\tStrictHostKeyChecking no\n" >> /root/.ssh/config')
+ on(host, 'chown -R root:root /root/.ssh')
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ apply_manifest_on(host, "file{'/root/.ssh/id_rsa': ensure => absent, force => true }", :catch_failures => true)
+ apply_manifest_on(host, "file{'/root/.ssh/id_rsa.pub': ensure => absent, force => true }", :catch_failures => true)
+ end
+
+ step 'get tag sha from repo' do
+ on(host, "git --git-dir=#{tmpdir}/testrepo.git rev-list HEAD | tail -1") do |res|
+ @sha = res.stdout.chomp
+ end
+ end
+
+ step 'checkout a tag with puppet (scp syntax)' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "root@#{host}:#{tmpdir}/testrepo.git",
+ provider => git,
+ revision => '#{tag}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify checkout out tag is #{tag}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host,"git --git-dir=#{tmpdir}/#{repo_name}/.git name-rev HEAD") do |res|
+ fail_test('tag not found') unless res.stdout.include? "#{tag}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/tag_checkout_ssh.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/tag_checkout_ssh.rb
new file mode 100644
index 00000000..bc416e8e
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/tag_checkout/tag_checkout_ssh.rb
@@ -0,0 +1,65 @@
+test_name 'C3447 - checkout a tag (ssh protocol)'
+
+# Globals
+repo_name = 'testrepo_tag_checkout'
+tag = '0.0.2'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+ step 'setup - establish ssh keys' do
+ # create ssh keys
+ on(host, 'yes | ssh-keygen -q -t rsa -f /root/.ssh/id_rsa -N ""')
+
+ # copy public key to authorized_keys
+ on(host, 'cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys')
+ on(host, 'echo -e "Host *\n\tStrictHostKeyChecking no\n" >> /root/.ssh/config')
+ on(host, 'chown -R root:root /root/.ssh')
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ apply_manifest_on(host, "file{'/root/.ssh/id_rsa': ensure => absent, force => true }", :catch_failures => true)
+ apply_manifest_on(host, "file{'/root/.ssh/id_rsa.pub': ensure => absent, force => true }", :catch_failures => true)
+ end
+
+ step 'get tag sha from repo' do
+ on(host, "git --git-dir=#{tmpdir}/testrepo.git rev-list HEAD | tail -1") do |res|
+ @sha = res.stdout.chomp
+ end
+ end
+
+ step 'checkout a tag with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "ssh://root@#{host}#{tmpdir}/testrepo.git",
+ provider => git,
+ revision => '#{tag}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify checkout out tag is #{tag}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host,"git --git-dir=#{tmpdir}/#{repo_name}/.git name-rev HEAD") do |res|
+ fail_test('tag not found') unless res.stdout.include? "#{tag}"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/negative/user_checkout_file_non_existent_user.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/negative/user_checkout_file_non_existent_user.rb
new file mode 100644
index 00000000..245e1751
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/negative/user_checkout_file_non_existent_user.rb
@@ -0,0 +1,51 @@
+test_name 'C3483 - checkout as a user that is not on system'
+
+# Globals
+repo_name = 'testrepo_user_checkout'
+user = 'myuser'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ step 'setup - delete user' do
+ apply_manifest_on(host, "user { '#{user}': ensure => absent, }", :catch_failures => true)
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ end
+
+ step 'checkout as a user with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ owner => '#{user}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :expect_failures => true)
+ end
+
+ step "verify git checkout is NOT owned by user #{user}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "stat --format '%U:%G' #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('checkout not owned by user') if res.stdout.include? "#{user}:"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/user_checkout_file.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/user_checkout_file.rb
new file mode 100644
index 00000000..ccd9ad44
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/user_checkout_file.rb
@@ -0,0 +1,53 @@
+test_name 'C3459 - checkout as a user (file protocol)'
+
+# Globals
+repo_name = 'testrepo_user_checkout'
+user = 'myuser'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ step 'setup - create user' do
+ apply_manifest_on(host, "user { '#{user}': ensure => present, }", :catch_failures => true)
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ apply_manifest_on(host, "user { '#{user}': ensure => absent, }", :catch_failures => true)
+ end
+
+ step 'checkout as a user with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ owner => '#{user}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify git checkout is owned by user #{user}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "stat --format '%U:%G' #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('checkout not owned by user') unless res.stdout.include? "#{user}:"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/user_checkout_file_path.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/user_checkout_file_path.rb
new file mode 100644
index 00000000..602769de
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/user_checkout_file_path.rb
@@ -0,0 +1,53 @@
+test_name 'C3458 - checkout as a user (file path)'
+
+# Globals
+repo_name = 'testrepo_user_checkout'
+user = 'myuser'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ step 'setup - create user' do
+ apply_manifest_on(host, "user { '#{user}': ensure => present, }", :catch_failures => true)
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ apply_manifest_on(host, "user { '#{user}': ensure => absent, }", :catch_failures => true)
+ end
+
+ step 'checkout a user with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "#{tmpdir}/testrepo.git",
+ provider => git,
+ owner => '#{user}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify git checkout is owned by user #{user}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "stat --format '%U:%G' #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('checkout not owned by user') unless res.stdout.include? "#{user}:"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/user_checkout_git.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/user_checkout_git.rb
new file mode 100644
index 00000000..af2ffb71
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/user_checkout_git.rb
@@ -0,0 +1,58 @@
+test_name 'C3457 - checkout as a user (git protocol)'
+
+# Globals
+repo_name = 'testrepo_user_checkout'
+user = 'myuser'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+ step 'setup - start git daemon' do
+ install_package(host, 'git-daemon') unless host['platform'] =~ /debian|ubuntu/
+ on(host, "git daemon --base-path=#{tmpdir} --export-all --reuseaddr --verbose --detach")
+ end
+
+ step 'setup - create user' do
+ apply_manifest_on(host, "user { '#{user}': ensure => present, }", :catch_failures => true)
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ on(host, 'pkill -9 git-daemon ; sleep 1')
+ apply_manifest_on(host, "user { '#{user}': ensure => absent, }", :catch_failures => true)
+ end
+
+ step 'checkout a user with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "git://#{host}/testrepo.git",
+ provider => git,
+ owner => '#{user}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify git checkout is owned by user #{user}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "stat --format '%U:%G' #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('checkout not owned by user') unless res.stdout.include? "#{user}:"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/user_checkout_http.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/user_checkout_http.rb
new file mode 100644
index 00000000..e8713e5b
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/user_checkout_http.rb
@@ -0,0 +1,66 @@
+test_name 'C3462 - checkout as a user (http protocol)'
+
+# Globals
+repo_name = 'testrepo_user_checkout'
+user = 'myuser'
+
+hosts.each do |host|
+ ruby = (host.is_pe? && '/opt/puppet/bin/ruby') || 'ruby'
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ step 'setup - start http server' do
+ http_daemon =<<-EOF
+ require 'webrick'
+ server = WEBrick::HTTPServer.new(:Port => 8000, :DocumentRoot => "#{tmpdir}")
+ WEBrick::Daemon.start
+ server.start
+ EOF
+ create_remote_file(host, '/tmp/http_daemon.rb', http_daemon)
+ on(host, "#{ruby} /tmp/http_daemon.rb")
+ end
+
+ step 'setup - create user' do
+ apply_manifest_on(host, "user { '#{user}': ensure => present, }", :catch_failures => true)
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ on(host, "ps ax | grep '#{ruby} /tmp/http_daemon.rb' | grep -v grep | awk '{print \"kill -9 \" $1}' | sh ; sleep 1")
+ apply_manifest_on(host, "user { '#{user}': ensure => absent, }", :catch_failures => true)
+ end
+
+ step 'checkout a user with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "http://#{host}:8000/testrepo.git",
+ provider => git,
+ owner => '#{user}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify git checkout is owned by user #{user}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "stat --format '%U:%G' #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('checkout not owned by user') unless res.stdout.include? "#{user}:"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/user_checkout_https.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/user_checkout_https.rb
new file mode 100644
index 00000000..4e633d78
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/user_checkout_https.rb
@@ -0,0 +1,73 @@
+test_name 'C3463 - checkout as a user (https protocol)'
+
+# Globals
+repo_name = 'testrepo_user_checkout'
+user = 'myuser'
+
+hosts.each do |host|
+ ruby = (host.is_pe? && '/opt/puppet/bin/ruby') || 'ruby'
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+ step 'setup - start https server' do
+ https_daemon =<<-EOF
+ require 'webrick'
+ require 'webrick/https'
+ server = WEBrick::HTTPServer.new(
+ :Port => 8443,
+ :DocumentRoot => "#{tmpdir}",
+ :SSLEnable => true,
+ :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE,
+ :SSLCertificate => OpenSSL::X509::Certificate.new( File.open("#{tmpdir}/server.crt").read),
+ :SSLPrivateKey => OpenSSL::PKey::RSA.new( File.open("#{tmpdir}/server.key").read),
+ :SSLCertName => [ [ "CN",WEBrick::Utils::getservername ] ])
+ WEBrick::Daemon.start
+ server.start
+ EOF
+ create_remote_file(host, '/tmp/https_daemon.rb', https_daemon)
+ #on(host, "#{ruby} /tmp/https_daemon.rb")
+ end
+
+ step 'setup - create user' do
+ apply_manifest_on(host, "user { '#{user}': ensure => present, }", :catch_failures => true)
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ on(host, "ps ax | grep '#{ruby} /tmp/https_daemon.rb' | grep -v grep | awk '{print \"kill -9 \" $1}' | sh ; sleep 1")
+ apply_manifest_on(host, "user { '#{user}': ensure => absent, }", :catch_failures => true)
+ end
+
+ step 'checkout as a user with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "https://github.com/johnduarte/testrepo.git",
+ provider => git,
+ owner => '#{user}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify git checkout is owned by user #{user}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "stat --format '%U:%G' #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('checkout not owned by user') unless res.stdout.include? "#{user}:"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/user_checkout_scp.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/user_checkout_scp.rb
new file mode 100644
index 00000000..98efb462
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/user_checkout_scp.rb
@@ -0,0 +1,64 @@
+test_name 'C3460 - checkout as a user (ssh protocol, scp syntax)'
+
+# Globals
+repo_name = 'testrepo_user_checkout'
+user = 'myuser'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+ step 'setup - establish ssh keys' do
+ # create ssh keys
+ on(host, 'yes | ssh-keygen -q -t rsa -f /root/.ssh/id_rsa -N ""')
+
+ # copy public key to authorized_keys
+ on(host, 'cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys')
+ on(host, 'echo -e "Host *\n\tStrictHostKeyChecking no\n" >> /root/.ssh/config')
+ on(host, 'chown -R root:root /root/.ssh')
+ end
+
+ step 'setup - create user' do
+ apply_manifest_on(host, "user { '#{user}': ensure => present, }", :catch_failures => true)
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ apply_manifest_on(host, "file{'/root/.ssh/id_rsa': ensure => absent, force => true }", :catch_failures => true)
+ apply_manifest_on(host, "file{'/root/.ssh/id_rsa.pub': ensure => absent, force => true }", :catch_failures => true)
+ apply_manifest_on(host, "user { '#{user}': ensure => absent, }", :catch_failures => true)
+ end
+
+ step 'checkout as a user with puppet (scp syntax)' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "root@#{host}:#{tmpdir}/testrepo.git",
+ provider => git,
+ owner => '#{user}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify git checkout is owned by user #{user}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "stat --format '%U:%G' #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('checkout not owned by user') unless res.stdout.include? "#{user}:"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/user_checkout_ssh.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/user_checkout_ssh.rb
new file mode 100644
index 00000000..cfd521ec
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker/git/user_checkout/user_checkout_ssh.rb
@@ -0,0 +1,64 @@
+test_name 'C3461 - checkout as a user (ssh protocol)'
+
+# Globals
+repo_name = 'testrepo_user_checkout'
+user = 'myuser'
+
+hosts.each do |host|
+ tmpdir = host.tmpdir('vcsrepo')
+ step 'setup - create repo' do
+ git_pkg = 'git'
+ if host['platform'] =~ /ubuntu-10/
+ git_pkg = 'git-core'
+ end
+ install_package(host, git_pkg)
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../..'))
+ scp_to(host, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ on(host, "cd #{tmpdir} && ./create_git_repo.sh")
+ end
+ step 'setup - establish ssh keys' do
+ # create ssh keys
+ on(host, 'yes | ssh-keygen -q -t rsa -f /root/.ssh/id_rsa -N ""')
+
+ # copy public key to authorized_keys
+ on(host, 'cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys')
+ on(host, 'echo -e "Host *\n\tStrictHostKeyChecking no\n" >> /root/.ssh/config')
+ on(host, 'chown -R root:root /root/.ssh')
+ end
+
+ step 'setup - create user' do
+ apply_manifest_on(host, "user { '#{user}': ensure => present, }", :catch_failures => true)
+ end
+
+ teardown do
+ on(host, "rm -fr #{tmpdir}")
+ apply_manifest_on(host, "file{'/root/.ssh/id_rsa': ensure => absent, force => true }", :catch_failures => true)
+ apply_manifest_on(host, "file{'/root/.ssh/id_rsa.pub': ensure => absent, force => true }", :catch_failures => true)
+ apply_manifest_on(host, "user { '#{user}': ensure => absent, }", :catch_failures => true)
+ end
+
+ step 'checkout as a user with puppet' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/#{repo_name}":
+ ensure => present,
+ source => "ssh://root@#{host}#{tmpdir}/testrepo.git",
+ provider => git,
+ owner => '#{user}',
+ }
+ EOS
+
+ apply_manifest_on(host, pp, :catch_failures => true)
+ apply_manifest_on(host, pp, :catch_changes => true)
+ end
+
+ step "verify git checkout is owned by user #{user}" do
+ on(host, "ls #{tmpdir}/#{repo_name}/.git/") do |res|
+ fail_test('checkout not found') unless res.stdout.include? "HEAD"
+ end
+
+ on(host, "stat --format '%U:%G' #{tmpdir}/#{repo_name}/.git/HEAD") do |res|
+ fail_test('checkout not owned by user') unless res.stdout.include? "#{user}:"
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/beaker_helper.rb b/puppet/modules/vcsrepo/spec/acceptance/beaker_helper.rb
new file mode 100644
index 00000000..4d232047
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/beaker_helper.rb
@@ -0,0 +1,51 @@
+test_name "Installing Puppet and vcsrepo module" do
+ step 'install puppet' do
+ if @options[:provision]
+ # This will fail if puppet is already installed, ie --no-provision
+ if hosts.first.is_pe?
+ install_pe
+ else
+ install_puppet
+ on hosts, "mkdir -p #{hosts.first['distmoduledir']}"
+ end
+ end
+ end
+
+ step 'Ensure we can install our module' do
+ hosts.each do |host|
+ # We ask the host to interpolate it's distmoduledir because we don't
+ # actually know it on Windows until we've let it redirect us (depending
+ # on whether we're running as a 32/64 bit process on 32/64 bit Windows
+ moduledir = on(host, "echo #{host['distmoduledir']}").stdout.chomp
+ on host, "mkdir -p #{moduledir}"
+ end
+ end
+
+ step 'install module' do
+ hosts.each do |host|
+ proj_root = File.expand_path(File.join(File.dirname(__FILE__),'..','..'))
+
+ # This require beaker 1.15
+ copy_module_to(host, :source => proj_root, :module_name => 'vcsrepo')
+
+ case fact_on(host, 'osfamily')
+ when 'RedHat'
+ install_package(host, 'git')
+ when 'Debian'
+ install_package(host, 'git-core')
+ else
+ if !check_for_package(host, 'git')
+ puts "Git package is required for this module"
+ exit
+ end
+ end
+
+ gitconfig = <<-EOS
+[user]
+ email = root@localhost
+ name = root
+EOS
+ create_remote_file(host, "/root/.gitconfig", gitconfig)
+ end
+ end
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/clone_repo_spec.rb b/puppet/modules/vcsrepo/spec/acceptance/clone_repo_spec.rb
new file mode 100644
index 00000000..c2345502
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/clone_repo_spec.rb
@@ -0,0 +1,534 @@
+require 'spec_helper_acceptance'
+
+tmpdir = default.tmpdir('vcsrepo')
+
+describe 'clones a remote repo' do
+ before(:all) do
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
+ shell("mkdir -p #{tmpdir}") # win test
+ scp_to(default, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ shell("cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ after(:all) do
+ shell("rm -rf #{tmpdir}/testrepo.git")
+ end
+
+ context 'get the current master HEAD' do
+ it 'clones a repo' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/testrepo":
+ ensure => present,
+ provider => git,
+ source => "file://#{tmpdir}/testrepo.git",
+ }
+ EOS
+
+ # Run it twice and test for idempotency
+ apply_manifest(pp, :catch_failures => true)
+ apply_manifest(pp, :catch_changes => true)
+ end
+
+ describe file("#{tmpdir}/testrepo/.git") do
+ it { is_expected.to be_directory }
+ end
+
+ describe file("#{tmpdir}/testrepo/.git/HEAD") do
+ it { is_expected.to contain 'ref: refs/heads/master' }
+ end
+ end
+
+ context 'using a https source on github' do
+ it 'clones a repo' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/httpstestrepo":
+ ensure => present,
+ provider => git,
+ source => "https://github.com/puppetlabs/puppetlabs-vcsrepo.git",
+ }
+ EOS
+
+ # Run it twice and test for idempotency
+ apply_manifest(pp, :catch_failures => true)
+ apply_manifest(pp, :catch_changes => true)
+ end
+
+ describe file("#{tmpdir}/httpstestrepo/.git") do
+ it { is_expected.to be_directory }
+ end
+
+ describe file("#{tmpdir}/httpstestrepo/.git/HEAD") do
+ it { is_expected.to contain 'ref: refs/heads/master' }
+ end
+ end
+
+ context 'using a commit SHA' do
+ let (:sha) do
+ shell("git --git-dir=#{tmpdir}/testrepo.git rev-list HEAD | tail -1").stdout.chomp
+ end
+
+ after(:all) do
+ shell("rm -rf #{tmpdir}/testrepo_sha")
+ end
+
+ it 'clones a repo' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/testrepo_sha":
+ ensure => present,
+ provider => git,
+ source => "file://#{tmpdir}/testrepo.git",
+ revision => "#{sha}",
+ }
+ EOS
+
+ # Run it twice and test for idempotency
+ apply_manifest(pp, :catch_failures => true)
+ apply_manifest(pp, :catch_changes => true)
+ end
+
+ describe file("#{tmpdir}/testrepo_sha/.git") do
+ it { is_expected.to be_directory }
+ end
+
+ describe file("#{tmpdir}/testrepo_sha/.git/HEAD") do
+ it { is_expected.to contain sha }
+ end
+ end
+
+ context 'using a tag' do
+ it 'clones a repo' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/testrepo_tag":
+ ensure => present,
+ provider => git,
+ source => "file://#{tmpdir}/testrepo.git",
+ revision => '0.0.2',
+ }
+ EOS
+
+ # Run it twice and test for idempotency
+ apply_manifest(pp, :catch_failures => true)
+ apply_manifest(pp, :catch_changes => true)
+ end
+
+ describe file("#{tmpdir}/testrepo_tag/.git") do
+ it { is_expected.to be_directory }
+ end
+
+ it 'should have the tag as the HEAD' do
+ shell("git --git-dir=#{tmpdir}/testrepo_tag/.git name-rev HEAD | grep '0.0.2'")
+ end
+ end
+
+ context 'using a branch name' do
+ it 'clones a repo' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/testrepo_branch":
+ ensure => present,
+ provider => git,
+ source => "file://#{tmpdir}/testrepo.git",
+ revision => 'a_branch',
+ }
+ EOS
+
+ # Run it twice and test for idempotency
+ apply_manifest(pp, :catch_failures => true)
+ apply_manifest(pp, :catch_changes => true)
+ end
+
+ describe file("#{tmpdir}/testrepo_branch/.git") do
+ it { is_expected.to be_directory }
+ end
+
+ describe file("#{tmpdir}/testrepo_branch/.git/HEAD") do
+ it { is_expected.to contain 'ref: refs/heads/a_branch' }
+ end
+ end
+
+ context 'ensure latest with branch specified' do
+ it 'clones a repo' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/testrepo_latest":
+ ensure => latest,
+ provider => git,
+ source => "file://#{tmpdir}/testrepo.git",
+ revision => 'a_branch',
+ }
+ EOS
+
+ # Run it twice and test for idempotency
+ apply_manifest(pp, :catch_failures => true)
+ apply_manifest(pp, :catch_changes => true)
+ end
+
+ it 'verifies the HEAD commit SHA on remote and local match' do
+ remote_commit = shell("git ls-remote file://#{tmpdir}/testrepo_latest HEAD | head -1").stdout
+ local_commit = shell("git --git-dir=#{tmpdir}/testrepo_latest/.git rev-parse HEAD").stdout.chomp
+ expect(remote_commit).to include(local_commit)
+ end
+ end
+
+ context 'ensure latest with branch unspecified' do
+ it 'clones a repo' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/testrepo_latest":
+ ensure => latest,
+ provider => git,
+ source => "file://#{tmpdir}/testrepo.git",
+ }
+ EOS
+
+ # Run it twice and test for idempotency
+ apply_manifest(pp, :catch_failures => true)
+ apply_manifest(pp, :catch_changes => true)
+ end
+
+ it 'verifies the HEAD commit SHA on remote and local match' do
+ remote_commit = shell("git ls-remote file://#{tmpdir}/testrepo_latest HEAD | head -1").stdout
+ local_commit = shell("git --git-dir=#{tmpdir}/testrepo_latest/.git rev-parse HEAD").stdout.chomp
+ expect(remote_commit).to include(local_commit)
+ end
+ end
+
+ context 'with shallow clone' do
+ it 'does a shallow clone' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/testrepo_shallow":
+ ensure => present,
+ provider => git,
+ source => "file://#{tmpdir}/testrepo.git",
+ depth => '1',
+ }
+ EOS
+
+ # Run it twice and test for idempotency
+ apply_manifest(pp, :catch_failures => true)
+ apply_manifest(pp, :catch_changes => true)
+ end
+
+ describe file("#{tmpdir}/testrepo_shallow/.git/shallow") do
+ it { is_expected.to be_file }
+ end
+ end
+
+ context 'path is not empty and not a repository' do
+ before(:all) do
+ shell("mkdir #{tmpdir}/not_a_repo", :acceptable_exit_codes => [0,1])
+ shell("touch #{tmpdir}/not_a_repo/file1.txt", :acceptable_exit_codes => [0,1])
+ end
+
+ it 'should raise an exception' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/not_a_repo":
+ ensure => present,
+ provider => git
+ source => "file://#{tmpdir}/testrepo.git",
+ }
+ EOS
+ apply_manifest(pp, :expect_failures => true)
+ end
+ end
+
+ context 'with an owner' do
+ pp = <<-EOS
+ user { 'vagrant':
+ ensure => present,
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ it 'clones a repo' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/testrepo_owner":
+ ensure => present,
+ provider => git,
+ source => "file://#{tmpdir}/testrepo.git",
+ owner => 'vagrant',
+ }
+ EOS
+
+ # Run it twice and test for idempotency
+ apply_manifest(pp, :catch_failures => true)
+ apply_manifest(pp, :catch_changes => true)
+ end
+
+ describe file("#{tmpdir}/testrepo_owner") do
+ it { is_expected.to be_directory }
+ it { is_expected.to be_owned_by 'vagrant' }
+ end
+ end
+
+ context 'with a group' do
+ pp = <<-EOS
+ group { 'vagrant':
+ ensure => present,
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+
+ it 'clones a repo' do
+ pp = <<-EOS
+ vcsrepo { "/#{tmpdir}/testrepo_group":
+ ensure => present,
+ provider => git,
+ source => "file://#{tmpdir}/testrepo.git",
+ group => 'vagrant',
+ }
+ EOS
+
+ # Run it twice and test for idempotency
+ apply_manifest(pp, :catch_failures => true)
+ apply_manifest(pp, :catch_changes => true)
+ end
+
+ describe file("#{tmpdir}/testrepo_group") do
+ it { is_expected.to be_directory }
+ it { is_expected.to be_grouped_into 'vagrant' }
+ end
+ end
+
+ context 'with excludes' do
+ it 'clones a repo' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/testrepo_excludes":
+ ensure => present,
+ provider => git,
+ source => "file://#{tmpdir}/testrepo.git",
+ excludes => ['exclude1.txt', 'exclude2.txt'],
+ }
+ EOS
+
+ # Run it twice and test for idempotency
+ apply_manifest(pp, :catch_failures => true)
+ apply_manifest(pp, :catch_changes => true)
+ end
+
+ describe file("#{tmpdir}/testrepo_excludes/.git/info/exclude") do
+ describe '#content' do
+ subject { super().content }
+ it { is_expected.to match /exclude1.txt/ }
+ end
+
+ describe '#content' do
+ subject { super().content }
+ it { is_expected.to match /exclude2.txt/ }
+ end
+ end
+ end
+
+ context 'with force' do
+ before(:all) do
+ shell("mkdir -p #{tmpdir}/testrepo_force/folder")
+ shell("touch #{tmpdir}/testrepo_force/temp.txt")
+ end
+
+ it 'applies the manifest' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/testrepo_force":
+ ensure => present,
+ provider => git,
+ source => "file://#{tmpdir}/testrepo.git",
+ force => true,
+ }
+ EOS
+
+ # Run it twice and test for idempotency
+ apply_manifest(pp, :catch_failures => true)
+ apply_manifest(pp, :catch_changes => true)
+ end
+
+ describe file("#{tmpdir}/testrepo_force/folder") do
+ it { is_expected.not_to be_directory }
+ end
+
+ describe file("#{tmpdir}/testrepo_force/temp.txt") do
+ it { is_expected.not_to be_file }
+ end
+
+ describe file("#{tmpdir}/testrepo_force/.git") do
+ it { is_expected.to be_directory }
+ end
+
+ context 'and noop' do
+ before(:all) do
+ shell("mkdir #{tmpdir}/testrepo_already_exists")
+ shell("cd #{tmpdir}/testrepo_already_exists && git init")
+ shell("cd #{tmpdir}/testrepo_already_exists && touch a && git add a && git commit -m 'a'")
+ end
+ after(:all) do
+ shell("rm -rf #{tmpdir}/testrepo_already_exists")
+ end
+
+ it 'applies the manifest' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/testrepo_already_exists":
+ ensure => present,
+ source => "file://#{tmpdir}/testrepo.git",
+ provider => git,
+ force => true,
+ noop => true,
+ }
+ EOS
+
+ apply_manifest(pp, :catch_changes => true)
+ end
+ end
+ end
+
+ context 'as a user' do
+ before(:all) do
+ shell("chmod 707 #{tmpdir}")
+ pp = <<-EOS
+ group { 'testuser':
+ ensure => present,
+ }
+ user { 'testuser':
+ ensure => present,
+ groups => 'testuser',
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+
+ it 'applies the manifest' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/testrepo_user":
+ ensure => present,
+ provider => git,
+ source => "file://#{tmpdir}/testrepo.git",
+ user => 'testuser',
+ }
+ EOS
+
+ # Run it twice and test for idempotency
+ apply_manifest(pp, :catch_failures => true)
+ apply_manifest(pp, :catch_changes => true)
+ end
+
+ describe file("#{tmpdir}/testrepo_user") do
+ it { is_expected.to be_directory }
+ it { is_expected.to be_owned_by 'testuser' }
+ end
+
+ describe file("#{tmpdir}/testrepo_user") do
+ it { is_expected.to be_directory }
+ it { is_expected.to be_grouped_into 'testuser' }
+ end
+ end
+
+ context 'non-origin remote name' do
+ it 'applies the manifest' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/testrepo_remote":
+ ensure => present,
+ provider => git,
+ source => "file://#{tmpdir}/testrepo.git",
+ remote => 'testorigin',
+ }
+ EOS
+
+ # Run it twice and test for idempotency
+ apply_manifest(pp, :catch_failures => true)
+ apply_manifest(pp, :catch_changes => true)
+ end
+
+ it 'remote name is "testorigin"' do
+ shell("git --git-dir=#{tmpdir}/testrepo_remote/.git remote | grep 'testorigin'")
+ end
+
+ after(:all) do
+ pp = 'user { "testuser": ensure => absent }'
+ apply_manifest(pp, :catch_failures => true)
+ end
+ end
+
+ context 'as a user with ssh' do
+ before(:all) do
+ # create user
+ pp = <<-EOS
+ group { 'testuser-ssh':
+ ensure => present,
+ }
+ user { 'testuser-ssh':
+ ensure => present,
+ groups => 'testuser-ssh',
+ managehome => true,
+ }
+ EOS
+ apply_manifest(pp, :catch_failures => true)
+
+ # create ssh keys
+ shell('mkdir -p /home/testuser-ssh/.ssh')
+ shell('ssh-keygen -q -t rsa -f /home/testuser-ssh/.ssh/id_rsa -N ""')
+
+ # copy public key to authorized_keys
+ shell('cat /home/testuser-ssh/.ssh/id_rsa.pub > /home/testuser-ssh/.ssh/authorized_keys')
+ shell('echo -e "Host localhost\n\tStrictHostKeyChecking no\n" > /home/testuser-ssh/.ssh/config')
+ shell('chown -R testuser-ssh:testuser-ssh /home/testuser-ssh/.ssh')
+ end
+
+ it 'applies the manifest' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/testrepo_user_ssh":
+ ensure => present,
+ provider => git,
+ source => "testuser-ssh@localhost:#{tmpdir}/testrepo.git",
+ user => 'testuser-ssh',
+ }
+ EOS
+
+ # Run it twice and test for idempotency
+ apply_manifest(pp, :catch_failures => true)
+ apply_manifest(pp, :catch_changes => true)
+ end
+
+ after(:all) do
+ pp = <<-EOS
+ user { 'testuser-ssh':
+ ensure => absent,
+ managehome => true,
+ }
+ EOS
+ apply_manifest(pp, :catch_failures => true)
+ end
+ end
+
+ context 'using an identity file' do
+ before(:all) do
+ # create user
+ pp = <<-EOS
+ user { 'testuser-ssh':
+ ensure => present,
+ managehome => true,
+ }
+ EOS
+ apply_manifest(pp, :catch_failures => true)
+
+ # create ssh keys
+ shell('mkdir -p /home/testuser-ssh/.ssh')
+ shell('ssh-keygen -q -t rsa -f /home/testuser-ssh/.ssh/id_rsa -N ""')
+
+ # copy public key to authorized_keys
+ shell('cat /home/testuser-ssh/.ssh/id_rsa.pub > /home/testuser-ssh/.ssh/authorized_keys')
+ shell('echo -e "Host localhost\n\tStrictHostKeyChecking no\n" > /home/testuser-ssh/.ssh/config')
+ shell('chown -R testuser-ssh:testuser-ssh /home/testuser-ssh/.ssh')
+ end
+
+ it 'applies the manifest' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/testrepo_user_ssh_id":
+ ensure => present,
+ provider => git,
+ source => "testuser-ssh@localhost:#{tmpdir}/testrepo.git",
+ identity => '/home/testuser-ssh/.ssh/id_rsa',
+ }
+ EOS
+
+ # Run it twice and test for idempotency
+ apply_manifest(pp, :catch_failures => true)
+ apply_manifest(pp, :catch_changes => true)
+ end
+ end
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/create_repo_spec.rb b/puppet/modules/vcsrepo/spec/acceptance/create_repo_spec.rb
new file mode 100644
index 00000000..53a93c97
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/create_repo_spec.rb
@@ -0,0 +1,89 @@
+require 'spec_helper_acceptance'
+
+tmpdir = default.tmpdir('vcsrepo')
+
+describe 'create a repo' do
+ context 'without a source' do
+ it 'creates a blank repo' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/testrepo_blank_repo":
+ ensure => present,
+ provider => git,
+ }
+ EOS
+
+ # Run it twice and test for idempotency
+ apply_manifest(pp, :catch_failures => true)
+ apply_manifest(pp, :catch_changes => true)
+ end
+
+ describe file("#{tmpdir}/testrepo_blank_repo/") do
+ it 'should have zero files' do
+ shell("ls -1 #{tmpdir}/testrepo_blank_repo | wc -l") do |r|
+ expect(r.stdout).to match(/^0\n$/)
+ end
+ end
+ end
+
+ describe file("#{tmpdir}/testrepo_blank_repo/.git") do
+ it { is_expected.to be_directory }
+ end
+ end
+
+ context 'no source but revision provided' do
+ it 'should not fail (MODULES-2125)' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/testrepo_blank_with_revision_repo":
+ ensure => present,
+ provider => git,
+ revision => 'master'
+ }
+ EOS
+
+ # Run it twice and test for idempotency
+ apply_manifest(pp, :catch_failures => true)
+ apply_manifest(pp, :catch_changes => true)
+ end
+ end
+
+ context 'bare repo' do
+ it 'creates a bare repo' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/testrepo_bare_repo":
+ ensure => bare,
+ provider => git,
+ }
+ EOS
+
+ # Run it twice and test for idempotency
+ apply_manifest(pp, :catch_failures => true)
+ apply_manifest(pp, :catch_changes => true)
+ end
+
+ describe file("#{tmpdir}/testrepo_bare_repo/config") do
+ it { is_expected.to contain 'bare = true' }
+ end
+
+ describe file("#{tmpdir}/testrepo_bare_repo/.git") do
+ it { is_expected.not_to be_directory }
+ end
+ end
+
+ context 'bare repo with a revision' do
+ it 'does not create a bare repo when a revision is defined' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/testrepo_bare_repo_rev":
+ ensure => bare,
+ provider => git,
+ revision => 'master',
+ }
+ EOS
+
+ apply_manifest(pp, :expect_failures => true)
+ end
+
+ describe file("#{tmpdir}/testrepo_bare_repo_rev") do
+ it { is_expected.not_to be_directory }
+ end
+ end
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/files/create_git_repo.sh b/puppet/modules/vcsrepo/spec/acceptance/files/create_git_repo.sh
new file mode 100755
index 00000000..b5e930ca
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/files/create_git_repo.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+mkdir testrepo
+cd testrepo
+
+touch file1.txt file2.txt file3.txt
+git init
+echo 'change 1' > file1.txt
+git add file1.txt
+git commit -m 'add file1'
+git tag 0.0.1
+echo 'change 2' > file2.txt
+git add file2.txt
+git commit -m 'add file2'
+git tag 0.0.2
+echo 'change 3' > file3.txt
+git add file3.txt
+git commit -m 'add file3'
+git tag 0.0.3
+
+git checkout -b a_branch
+echo 'change 4' > file4.txt
+git add file4.txt
+git commit -m 'add file4'
+echo 'change 5' > file5.txt
+git add file5.txt
+git commit -m 'add file5'
+echo 'change 6' > file6.txt
+git add file6.txt
+git commit -m 'add file6'
+
+git checkout master
+cd ..
+
+git --git-dir=testrepo/.git config core.bare true
+cp -r testrepo/.git testrepo.git
+rm -rf testrepo
+cd testrepo.git
+touch git-daemon-export-ok
+git update-server-info
diff --git a/puppet/modules/vcsrepo/spec/acceptance/files/server.crt b/puppet/modules/vcsrepo/spec/acceptance/files/server.crt
new file mode 100644
index 00000000..270f65c0
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/files/server.crt
@@ -0,0 +1,13 @@
+-----BEGIN CERTIFICATE-----
+MIICATCCAWoCCQDRobnOvvkStDANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJB
+VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
+cyBQdHkgTHRkMB4XDTE1MDQwODE3MjM1NVoXDTI1MDQwNTE3MjM1NVowRTELMAkG
+A1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0
+IFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAyRTv
+uX6328aQ5Auc8PI+xNaCiE0UZNYcs+xq3AEkR/Tnz0HGXdx3+PnFG7MIRSS65hXA
+VGenZk3wP4vNIe9gu+G9jtOFTJOgoOBUnJ/Hcs79Zgcmz3cAWQpqww+CZpyngUDS
+msZ5HoEbNS+qaIron3IrYCgPsy1BHFs5ze7JrtcCAwEAATANBgkqhkiG9w0BAQUF
+AAOBgQCaYVv8WbFbrnLMOcyjE7GjSmVh68fEN+AqntZa1Z5GOv6OQIN9mVSoNxWo
+lb/9xmldfMQThgKckHHvB5Q9kf923nMQZOi8yxyaoeYWrkglkFFU/sdF6yuFBdUU
+D+rXmHnS754FLTGDzESmlRVUCYuwVgrRdm+P+wu2+lZT3x85VA==
+-----END CERTIFICATE-----
diff --git a/puppet/modules/vcsrepo/spec/acceptance/files/server.key b/puppet/modules/vcsrepo/spec/acceptance/files/server.key
new file mode 100644
index 00000000..b594f13e
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/files/server.key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDJFO+5frfbxpDkC5zw8j7E1oKITRRk1hyz7GrcASRH9OfPQcZd
+3Hf4+cUbswhFJLrmFcBUZ6dmTfA/i80h72C74b2O04VMk6Cg4FScn8dyzv1mBybP
+dwBZCmrDD4JmnKeBQNKaxnkegRs1L6poiuifcitgKA+zLUEcWznN7smu1wIDAQAB
+AoGAQPnD8OOyk5DZVuctwmn0wHQ0X8jQczkAs18MtKSlzZ6knUM6zy+jkM9c0vOK
+E5Wn0xtqN5v66sL6g/4vvex1DA5Q6YsXvZ48VpVliZXXK/1pdTv0qwMyHdlBhmgJ
+MhnZbyNy61QHdOTsWDR1YrELpDyFMJ9cZZD0NOnsuhd2DbECQQDq7W/zlJBZPWNR
+ab2dP+HLpm/PiEBT13SuEEskh3GEEfZlwz/cGu0Z8DHA4E3Z60KFjwgnc92GNFMg
+m0t3hHtpAkEA2x5PsDxBk9sWwdIvu57vjQLdotvAfyb+W9puIaZS1JRSVLTsUVEj
+Y0KxgsPHtcjrVoN//zGymn4ePxWOzlrQPwJBAN5thEuZY7o6dyiD9zVFYKGSqdZS
+aKV5H04Wuy6Q1pd28lWTMYlSLR8b3d+B//PN3SPbMps4BoukSvhaUG+OjdECQFzF
+KZIBAPa7pJftCH6UHPIDy5ifF5H+DWUQRt6CT8FnBrCMZR1MkAH/g65Me6pwZYsc
+Y73E6cxVJzMoSmz9r/sCQQCOhPflFCxZ23ocsuRBo9O/mMUDaLoHZXWuJ2DqAUN2
+mS6UUR/lpyc7Cmy0VOyhS8783D7MUfji5ddfVxb5tWgm
+-----END RSA PRIVATE KEY-----
diff --git a/puppet/modules/vcsrepo/spec/acceptance/modules_1596_spec.rb b/puppet/modules/vcsrepo/spec/acceptance/modules_1596_spec.rb
new file mode 100644
index 00000000..fa36285a
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/modules_1596_spec.rb
@@ -0,0 +1,72 @@
+require 'spec_helper_acceptance'
+
+tmpdir = default.tmpdir('vcsrepo')
+
+describe 'clones a remote repo' do
+ before(:all) do
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
+ shell("mkdir -p #{tmpdir}") # win test
+ end
+
+ after(:all) do
+ shell("rm -rf #{tmpdir}/vcsrepo")
+ end
+
+ context 'force with a remote' do
+ it 'clones from remote' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/vcsrepo":
+ ensure => present,
+ provider => git,
+ source => 'https://github.com/puppetlabs/puppetlabs-vcsrepo',
+ force => true,
+ }
+ EOS
+
+ # Run it twice to test for idempotency
+ apply_manifest(pp, :catch_failures => true)
+ # need to create a file to make sure we aren't destroying the repo
+ # because fun fact, if you call destroy/create in 'retrieve' puppet won't
+ # register that any changes happen, because that method isn't supposed to
+ # be making any changes.
+ shell("touch #{tmpdir}/vcsrepo/foo")
+ apply_manifest(pp, :catch_changes => true)
+ end
+
+ describe file("#{tmpdir}/vcsrepo/foo") do
+ it { is_expected.to be_file }
+ end
+ end
+
+ context 'force over an existing repo' do
+ it 'clones from remote' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/vcsrepo":
+ ensure => present,
+ provider => git,
+ source => 'https://github.com/puppetlabs/puppetlabs-vcsrepo',
+ force => true,
+ }
+ EOS
+
+ pp2 = <<-EOS
+ vcsrepo { "#{tmpdir}/vcsrepo":
+ ensure => present,
+ provider => git,
+ source => 'https://github.com/puppetlabs/puppetlabs-stdlib',
+ force => true,
+ }
+ EOS
+
+
+ apply_manifest(pp, :catch_failures => true)
+ # create a file to make sure we're destroying the repo
+ shell("touch #{tmpdir}/vcsrepo/foo")
+ apply_manifest(pp2, :catch_failures => true)
+ end
+
+ describe file("#{tmpdir}/vcsrepo/foo") do
+ it { is_expected.to_not be_file }
+ end
+ end
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/modules_1800_spec.rb b/puppet/modules/vcsrepo/spec/acceptance/modules_1800_spec.rb
new file mode 100644
index 00000000..12415e80
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/modules_1800_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper_acceptance'
+
+tmpdir = default.tmpdir('vcsrepo')
+
+describe 'clones a remote repo' do
+ before(:all) do
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
+ shell("mkdir -p #{tmpdir}") # win test
+ end
+
+ after(:all) do
+ shell("rm -rf #{tmpdir}/vcsrepo")
+ end
+
+ context 'ensure latest with no revision' do
+ it 'clones from default remote' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/vcsrepo":
+ ensure => present,
+ provider => git,
+ source => "https://github.com/puppetlabs/puppetlabs-vcsrepo.git",
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ shell("cd #{tmpdir}/vcsrepo; /usr/bin/git reset --hard HEAD~2")
+ end
+
+ it 'updates' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/vcsrepo":
+ ensure => latest,
+ provider => git,
+ source => "https://github.com/puppetlabs/puppetlabs-vcsrepo.git",
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+ end
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/modules_2326_spec.rb b/puppet/modules/vcsrepo/spec/acceptance/modules_2326_spec.rb
new file mode 100644
index 00000000..601c6ff6
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/modules_2326_spec.rb
@@ -0,0 +1,69 @@
+require 'spec_helper_acceptance'
+
+tmpdir = default.tmpdir('vcsrepo')
+
+describe 'clones with special characters' do
+
+ before(:all) do
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
+ shell("mkdir -p #{tmpdir}") # win test
+ scp_to(default, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ shell("cd #{tmpdir} && ./create_git_repo.sh")
+ end
+
+ after(:all) do
+ shell("rm -rf #{tmpdir}/testrepo.git")
+ end
+
+ context 'as a user with ssh' do
+ before(:all) do
+ # create user
+ pp = <<-EOS
+ group { 'testuser-ssh':
+ ensure => present,
+ }
+ user { 'testuser-ssh':
+ ensure => present,
+ groups => 'testuser-ssh',
+ managehome => true,
+ }
+ EOS
+ apply_manifest(pp, :catch_failures => true)
+
+ # create ssh keys
+ shell('mkdir -p /home/testuser-ssh/.ssh')
+ shell('echo -e \'y\n\'|ssh-keygen -q -t rsa -f /home/testuser-ssh/.ssh/id_rsa -N ""')
+
+ # copy public key to authorized_keys
+ shell('cat /home/testuser-ssh/.ssh/id_rsa.pub > /home/testuser-ssh/.ssh/authorized_keys')
+ shell('echo -e "Host localhost\n\tStrictHostKeyChecking no\n" > /home/testuser-ssh/.ssh/config')
+ shell('chown -R testuser-ssh:testuser-ssh /home/testuser-ssh/.ssh')
+ shell("chown testuser-ssh:testuser-ssh #{tmpdir}")
+ end
+
+ it 'applies the manifest' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/testrepo_user_ssh":
+ ensure => present,
+ provider => git,
+ source => "git+ssh://testuser-ssh@localhost#{tmpdir}/testrepo.git",
+ user => 'testuser-ssh',
+ }
+ EOS
+
+ # Run it twice and test for idempotency
+ apply_manifest(pp, :catch_failures => true)
+ apply_manifest(pp, :catch_changes => true)
+ end
+
+ after(:all) do
+ pp = <<-EOS
+ user { 'testuser-ssh':
+ ensure => absent,
+ managehome => true,
+ }
+ EOS
+ apply_manifest(pp, :catch_failures => true)
+ end
+ end
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/modules_660_spec.rb b/puppet/modules/vcsrepo/spec/acceptance/modules_660_spec.rb
new file mode 100644
index 00000000..c45aa28b
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/modules_660_spec.rb
@@ -0,0 +1,89 @@
+require 'spec_helper_acceptance'
+
+tmpdir = default.tmpdir('vcsrepo')
+
+describe 'MODULES-660' do
+ before(:all) do
+ # Create testrepo.git
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
+ shell("mkdir -p #{tmpdir}") # win test
+ scp_to(default, "#{my_root}/acceptance/files/create_git_repo.sh", tmpdir)
+ shell("cd #{tmpdir} && ./create_git_repo.sh")
+
+ # Configure testrepo.git as upstream of testrepo
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/testrepo":
+ ensure => present,
+ provider => git,
+ revision => 'a_branch',
+ source => "file://#{tmpdir}/testrepo.git",
+ }
+ EOS
+ apply_manifest(pp, :catch_failures => true)
+ end
+
+ after(:all) do
+ shell("rm -rf #{tmpdir}/testrepo.git")
+ end
+
+ shared_examples 'switch to branch/tag/sha' do
+ it 'pulls the new branch commits' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/testrepo":
+ ensure => latest,
+ provider => git,
+ revision => 'a_branch',
+ source => "file://#{tmpdir}/testrepo.git",
+ }
+ EOS
+ apply_manifest(pp, :expect_changes => true)
+ apply_manifest(pp, :catch_changes => true)
+ end
+ it 'checks out the tag' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/testrepo":
+ ensure => latest,
+ provider => git,
+ revision => '0.0.3',
+ source => "file://#{tmpdir}/testrepo.git",
+ }
+ EOS
+ apply_manifest(pp, :expect_changes => true)
+ apply_manifest(pp, :catch_changes => true)
+ end
+ it 'checks out the sha' do
+ sha = shell("cd #{tmpdir}/testrepo && git rev-parse origin/master").stdout.chomp
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/testrepo":
+ ensure => latest,
+ provider => git,
+ revision => '#{sha}',
+ source => "file://#{tmpdir}/testrepo.git",
+ }
+ EOS
+ apply_manifest(pp, :expect_changes => true)
+ apply_manifest(pp, :catch_changes => true)
+ end
+ end
+
+ context 'on branch' do
+ before :each do
+ shell("cd #{tmpdir}/testrepo && git checkout a_branch")
+ shell("cd #{tmpdir}/testrepo && git reset --hard 0.0.2")
+ end
+ it_behaves_like 'switch to branch/tag/sha'
+ end
+ context 'on tag' do
+ before :each do
+ shell("cd #{tmpdir}/testrepo && git checkout 0.0.1")
+ end
+ it_behaves_like 'switch to branch/tag/sha'
+ end
+ context 'on detached head' do
+ before :each do
+ shell("cd #{tmpdir}/testrepo && git checkout 0.0.2")
+ shell("cd #{tmpdir}/testrepo && git checkout HEAD~1")
+ end
+ it_behaves_like 'switch to branch/tag/sha'
+ end
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/modules_753_spec.rb b/puppet/modules/vcsrepo/spec/acceptance/modules_753_spec.rb
new file mode 100644
index 00000000..e4e332bf
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/modules_753_spec.rb
@@ -0,0 +1,68 @@
+require 'spec_helper_acceptance'
+
+tmpdir = default.tmpdir('vcsrepo')
+
+describe 'clones a remote repo' do
+ before(:all) do
+ my_root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
+ shell("mkdir -p #{tmpdir}") # win test
+ end
+
+ after(:all) do
+ shell("rm -rf #{tmpdir}/vcsrepo")
+ end
+
+ context 'clone with single remote' do
+ it 'clones from default remote' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/vcsrepo":
+ ensure => present,
+ provider => git,
+ source => "https://github.com/puppetlabs/puppetlabs-vcsrepo.git",
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+
+ end
+
+ it "git config output should contain the remote" do
+ shell("/usr/bin/git config -l -f #{tmpdir}/vcsrepo/.git/config") do |r|
+ expect(r.stdout).to match(/remote.origin.url=https:\/\/github.com\/puppetlabs\/puppetlabs-vcsrepo.git/)
+ end
+ end
+
+ after(:all) do
+ shell("rm -rf #{tmpdir}/vcsrepo")
+ end
+
+ end
+
+ context 'clone with multiple remotes' do
+ it 'clones from default remote and adds 2 remotes to config file' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/vcsrepo":
+ ensure => present,
+ provider => git,
+ source => {"origin" => "https://github.com/puppetlabs/puppetlabs-vcsrepo.git", "test1" => "https://github.com/puppetlabs/puppetlabs-vcsrepo.git"},
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+
+ end
+
+ it "git config output should contain the remotes" do
+ shell("/usr/bin/git config -l -f #{tmpdir}/vcsrepo/.git/config") do |r|
+ expect(r.stdout).to match(/remote.origin.url=https:\/\/github.com\/puppetlabs\/puppetlabs-vcsrepo.git/)
+ expect(r.stdout).to match(/remote.test1.url=https:\/\/github.com\/puppetlabs\/puppetlabs-vcsrepo.git/)
+ end
+ end
+
+ after(:all) do
+ shell("rm -rf #{tmpdir}/vcsrepo")
+ end
+
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/nodesets/centos-59-x64.yml b/puppet/modules/vcsrepo/spec/acceptance/nodesets/centos-59-x64.yml
new file mode 100644
index 00000000..2ad90b86
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/nodesets/centos-59-x64.yml
@@ -0,0 +1,10 @@
+HOSTS:
+ centos-59-x64:
+ roles:
+ - master
+ platform: el-5-x86_64
+ box : centos-59-x64-vbox4210-nocm
+ box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-59-x64-vbox4210-nocm.box
+ hypervisor : vagrant
+CONFIG:
+ type: git
diff --git a/puppet/modules/vcsrepo/spec/acceptance/nodesets/centos-64-x64-pe.yml b/puppet/modules/vcsrepo/spec/acceptance/nodesets/centos-64-x64-pe.yml
new file mode 100644
index 00000000..7d9242f1
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/nodesets/centos-64-x64-pe.yml
@@ -0,0 +1,12 @@
+HOSTS:
+ centos-64-x64:
+ roles:
+ - master
+ - database
+ - dashboard
+ platform: el-6-x86_64
+ box : centos-64-x64-vbox4210-nocm
+ box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-64-x64-vbox4210-nocm.box
+ hypervisor : vagrant
+CONFIG:
+ type: pe
diff --git a/puppet/modules/vcsrepo/spec/acceptance/nodesets/centos-64-x64.yml b/puppet/modules/vcsrepo/spec/acceptance/nodesets/centos-64-x64.yml
new file mode 100644
index 00000000..05540ed8
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/nodesets/centos-64-x64.yml
@@ -0,0 +1,10 @@
+HOSTS:
+ centos-64-x64:
+ roles:
+ - master
+ platform: el-6-x86_64
+ box : centos-64-x64-vbox4210-nocm
+ box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-64-x64-vbox4210-nocm.box
+ hypervisor : vagrant
+CONFIG:
+ type: foss
diff --git a/puppet/modules/vcsrepo/spec/acceptance/nodesets/centos-65-x64.yml b/puppet/modules/vcsrepo/spec/acceptance/nodesets/centos-65-x64.yml
new file mode 100644
index 00000000..4e2cb809
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/nodesets/centos-65-x64.yml
@@ -0,0 +1,10 @@
+HOSTS:
+ centos-65-x64:
+ roles:
+ - master
+ platform: el-6-x86_64
+ box : centos-65-x64-vbox436-nocm
+ box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-65-x64-virtualbox-nocm.box
+ hypervisor : vagrant
+CONFIG:
+ type: foss
diff --git a/puppet/modules/vcsrepo/spec/acceptance/nodesets/debian-607-x64.yml b/puppet/modules/vcsrepo/spec/acceptance/nodesets/debian-607-x64.yml
new file mode 100644
index 00000000..43df6a57
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/nodesets/debian-607-x64.yml
@@ -0,0 +1,10 @@
+HOSTS:
+ debian-607-x64:
+ roles:
+ - master
+ platform: debian-6-amd64
+ box : debian-607-x64-vbox4210-nocm
+ box_url : http://puppet-vagrant-boxes.puppetlabs.com/debian-607-x64-vbox4210-nocm.box
+ hypervisor : vagrant
+CONFIG:
+ type: foss
diff --git a/puppet/modules/vcsrepo/spec/acceptance/nodesets/debian-73-x64.yml b/puppet/modules/vcsrepo/spec/acceptance/nodesets/debian-73-x64.yml
new file mode 100644
index 00000000..5b87870a
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/nodesets/debian-73-x64.yml
@@ -0,0 +1,10 @@
+HOSTS:
+ debian-73-x64:
+ roles:
+ - master
+ platform: debian-7-amd64
+ box : debian-73-x64-virtualbox-nocm
+ box_url : http://puppet-vagrant-boxes.puppetlabs.com/debian-73-x64-virtualbox-nocm.box
+ hypervisor : vagrant
+CONFIG:
+ type: foss
diff --git a/puppet/modules/vcsrepo/spec/acceptance/nodesets/default.yml b/puppet/modules/vcsrepo/spec/acceptance/nodesets/default.yml
new file mode 100644
index 00000000..05540ed8
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/nodesets/default.yml
@@ -0,0 +1,10 @@
+HOSTS:
+ centos-64-x64:
+ roles:
+ - master
+ platform: el-6-x86_64
+ box : centos-64-x64-vbox4210-nocm
+ box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-64-x64-vbox4210-nocm.box
+ hypervisor : vagrant
+CONFIG:
+ type: foss
diff --git a/puppet/modules/vcsrepo/spec/acceptance/nodesets/ubuntu-server-10044-x64.yml b/puppet/modules/vcsrepo/spec/acceptance/nodesets/ubuntu-server-10044-x64.yml
new file mode 100644
index 00000000..5ca1514e
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/nodesets/ubuntu-server-10044-x64.yml
@@ -0,0 +1,10 @@
+HOSTS:
+ ubuntu-server-10044-x64:
+ roles:
+ - master
+ platform: ubuntu-10.04-amd64
+ box : ubuntu-server-10044-x64-vbox4210-nocm
+ box_url : http://puppet-vagrant-boxes.puppetlabs.com/ubuntu-server-10044-x64-vbox4210-nocm.box
+ hypervisor : vagrant
+CONFIG:
+ type: foss
diff --git a/puppet/modules/vcsrepo/spec/acceptance/nodesets/ubuntu-server-12042-x64.yml b/puppet/modules/vcsrepo/spec/acceptance/nodesets/ubuntu-server-12042-x64.yml
new file mode 100644
index 00000000..d065b304
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/nodesets/ubuntu-server-12042-x64.yml
@@ -0,0 +1,10 @@
+HOSTS:
+ ubuntu-server-12042-x64:
+ roles:
+ - master
+ platform: ubuntu-12.04-amd64
+ box : ubuntu-server-12042-x64-vbox4210-nocm
+ box_url : http://puppet-vagrant-boxes.puppetlabs.com/ubuntu-server-12042-x64-vbox4210-nocm.box
+ hypervisor : vagrant
+CONFIG:
+ type: foss
diff --git a/puppet/modules/vcsrepo/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml b/puppet/modules/vcsrepo/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml
new file mode 100644
index 00000000..cba1cd04
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml
@@ -0,0 +1,11 @@
+HOSTS:
+ ubuntu-server-1404-x64:
+ roles:
+ - master
+ platform: ubuntu-14.04-amd64
+ box : puppetlabs/ubuntu-14.04-64-nocm
+ box_url : https://vagrantcloud.com/puppetlabs/ubuntu-14.04-64-nocm
+ hypervisor : vagrant
+CONFIG:
+ log_level : debug
+ type: git
diff --git a/puppet/modules/vcsrepo/spec/acceptance/remove_repo_spec.rb b/puppet/modules/vcsrepo/spec/acceptance/remove_repo_spec.rb
new file mode 100644
index 00000000..d5646b34
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/remove_repo_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper_acceptance'
+
+tmpdir = default.tmpdir('vcsrepo')
+
+describe 'remove a repo' do
+ it 'creates a blank repo' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/testrepo_deleted":
+ ensure => present,
+ provider => git,
+ }
+ EOS
+ apply_manifest(pp, :catch_failures => true)
+ end
+
+ it 'removes a repo' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/testrepo_deleted":
+ ensure => absent,
+ provider => git,
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ end
+
+ describe file("#{tmpdir}/testrepo_deleted") do
+ it { is_expected.not_to be_directory }
+ end
+end
diff --git a/puppet/modules/vcsrepo/spec/acceptance/remove_repo_spec_noop.rb b/puppet/modules/vcsrepo/spec/acceptance/remove_repo_spec_noop.rb
new file mode 100644
index 00000000..f6bd86e9
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/acceptance/remove_repo_spec_noop.rb
@@ -0,0 +1,31 @@
+require 'spec_helper_acceptance'
+
+tmpdir = default.tmpdir('vcsrepo')
+
+describe 'does not remove a repo if noop' do
+ it 'creates a blank repo' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/testrepo_noop_deleted":
+ ensure => present,
+ provider => git,
+ }
+ EOS
+ apply_manifest(pp, :catch_failures => true)
+ end
+
+ it 'does not remove a repo if noop' do
+ pp = <<-EOS
+ vcsrepo { "#{tmpdir}/testrepo_noop_deleted":
+ ensure => absent,
+ provider => git,
+ force => true,
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true, :noop => true, :verbose => false)
+ end
+
+ describe file("#{tmpdir}/testrepo_noop_deleted") do
+ it { is_expected.to be_directory }
+ end
+end
diff --git a/puppet/modules/vcsrepo/spec/fixtures/bzr_version_info.txt b/puppet/modules/vcsrepo/spec/fixtures/bzr_version_info.txt
new file mode 100644
index 00000000..88a56a1c
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/fixtures/bzr_version_info.txt
@@ -0,0 +1,5 @@
+revision-id: menesis@pov.lt-20100309191856-4wmfqzc803fj300x
+date: 2010-03-09 21:18:56 +0200
+build-date: 2010-03-14 00:42:43 -0800
+revno: 2634
+branch-nick: mytest
diff --git a/puppet/modules/vcsrepo/spec/fixtures/git_branch_a.txt b/puppet/modules/vcsrepo/spec/fixtures/git_branch_a.txt
new file mode 100644
index 00000000..2c99829d
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/fixtures/git_branch_a.txt
@@ -0,0 +1,14 @@
+ feature/foo
+ feature/bar
+ feature/baz
+ feature/quux
+ only/local
+* master
+ refactor/foo
+ origin/HEAD
+ origin/feature/foo
+ origin/feature/bar
+ origin/feature/baz
+ origin/feature/quux
+ origin/only/remote
+ origin/master
diff --git a/puppet/modules/vcsrepo/spec/fixtures/git_branch_feature_bar.txt b/puppet/modules/vcsrepo/spec/fixtures/git_branch_feature_bar.txt
new file mode 100644
index 00000000..72d5e200
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/fixtures/git_branch_feature_bar.txt
@@ -0,0 +1,14 @@
+ feature/foo
+* feature/bar
+ feature/baz
+ feature/quux
+ only/local
+ master
+ refactor/foo
+ origin/HEAD
+ origin/feature/foo
+ origin/feature/bar
+ origin/feature/baz
+ origin/feature/quux
+ origin/only/remote
+ origin/master
diff --git a/puppet/modules/vcsrepo/spec/fixtures/git_branch_none.txt b/puppet/modules/vcsrepo/spec/fixtures/git_branch_none.txt
new file mode 100644
index 00000000..7207c379
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/fixtures/git_branch_none.txt
@@ -0,0 +1,15 @@
+ feature/foo
+ feature/bar
+ feature/baz
+ feature/quux
+ only/local
+ master
+* (no branch)
+ refactor/foo
+ origin/HEAD
+ origin/feature/foo
+ origin/feature/bar
+ origin/feature/baz
+ origin/feature/quux
+ origin/only/remote
+ origin/master
diff --git a/puppet/modules/vcsrepo/spec/fixtures/hg_parents.txt b/puppet/modules/vcsrepo/spec/fixtures/hg_parents.txt
new file mode 100644
index 00000000..46173df4
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/fixtures/hg_parents.txt
@@ -0,0 +1,6 @@
+changeset: 3:34e6012c783a
+parent: 2:21ea4598c962
+parent: 1:9d0ff0028458
+user: Test User <test.user@example.com>
+date: Fri Aug 07 13:13:02 2009 -0400
+summary: merge
diff --git a/puppet/modules/vcsrepo/spec/fixtures/hg_tags.txt b/puppet/modules/vcsrepo/spec/fixtures/hg_tags.txt
new file mode 100644
index 00000000..53792e5a
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/fixtures/hg_tags.txt
@@ -0,0 +1,18 @@
+tip 1019:bca3f20b249b
+0.9.1 1017:76ce7cca95d8
+0.9 1001:dbaa6f4ec585
+0.8 839:65b66ac0fc83
+0.7.1 702:e1357f00129f
+0.7 561:7b2af3b4c968
+0.6.3 486:e38077f4e4aa
+0.6.2 405:07bb099b7b10
+0.6.1 389:93750f3fbbe2
+0.6 369:34e6012c783a
+0.5.3 321:5ffa6ae7e699
+0.5.2 318:fdc2c2e4cebe
+0.5.1 315:33a5ea0cbe7a
+0.5 313:47490716f4c9
+0.4 240:47fa3a14cc63
+0.3.1 132:bc231db18e1c
+0.3 130:661615e510dd
+0.2 81:f98d13b442f6
diff --git a/puppet/modules/vcsrepo/spec/fixtures/svn_info.txt b/puppet/modules/vcsrepo/spec/fixtures/svn_info.txt
new file mode 100644
index 00000000..d2a975b2
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/fixtures/svn_info.txt
@@ -0,0 +1,10 @@
+Path: .
+URL: http://example.com/svn/trunk
+Repository Root: http://example.com/svn
+Repository UUID: 75246ace-e253-0410-96dd-a7613ca8dc81
+Revision: 4
+Node Kind: directory
+Schedule: normal
+Last Changed Author: jon
+Last Changed Rev: 3
+Last Changed Date: 2008-08-07 11:34:25 -0700 (Thu, 07 Aug 2008)
diff --git a/puppet/modules/vcsrepo/spec/spec.opts b/puppet/modules/vcsrepo/spec/spec.opts
new file mode 100644
index 00000000..91cd6427
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/spec.opts
@@ -0,0 +1,6 @@
+--format
+s
+--colour
+--loadby
+mtime
+--backtrace
diff --git a/puppet/modules/vcsrepo/spec/spec_helper.rb b/puppet/modules/vcsrepo/spec/spec_helper.rb
new file mode 100644
index 00000000..22d5d689
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/spec_helper.rb
@@ -0,0 +1,8 @@
+#This file is generated by ModuleSync, do not edit.
+require 'puppetlabs_spec_helper/module_spec_helper'
+
+# put local configuration and setup into spec_helper_local
+begin
+ require 'spec_helper_local'
+rescue LoadError
+end
diff --git a/puppet/modules/vcsrepo/spec/spec_helper_acceptance.rb b/puppet/modules/vcsrepo/spec/spec_helper_acceptance.rb
new file mode 100644
index 00000000..97c43e8c
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/spec_helper_acceptance.rb
@@ -0,0 +1,46 @@
+require 'beaker-rspec'
+require 'beaker/puppet_install_helper'
+
+run_puppet_install_helper
+
+RSpec.configure do |c|
+ # Project root
+ proj_root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
+
+ # Readable test descriptions
+ c.formatter = :documentation
+
+ # Configure all nodes in nodeset
+ c.before :suite do
+
+ # ensure test dependencies are available on all hosts
+ hosts.each do |host|
+ copy_module_to(host, :source => proj_root, :module_name => 'vcsrepo')
+ case fact_on(host, 'osfamily')
+ when 'RedHat'
+ if fact_on(host, 'operatingsystemmajrelease') == '5'
+ will_install_git = on(host, 'which git', :acceptable_exit_codes => [0,1]).exit_code == 1
+
+ if will_install_git
+ on host, puppet('module install stahnma-epel')
+ apply_manifest_on( host, 'include epel' )
+ end
+
+ end
+
+ install_package(host, 'git')
+
+ when 'Debian'
+ install_package(host, 'git-core')
+
+ else
+ if !check_for_package(host, 'git')
+ puts "Git package is required for this module"
+ exit
+ end
+ end
+ on host, 'git config --global user.email "root@localhost"'
+ on host, 'git config --global user.name "root"'
+ end
+ end
+end
diff --git a/puppet/modules/vcsrepo/spec/spec_helper_local.rb b/puppet/modules/vcsrepo/spec/spec_helper_local.rb
new file mode 100644
index 00000000..c7d27b52
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/spec_helper_local.rb
@@ -0,0 +1,7 @@
+require 'support/filesystem_helpers'
+require 'support/fixture_helpers'
+
+RSpec.configure do |c|
+ c.include FilesystemHelpers
+ c.include FixtureHelpers
+end
diff --git a/puppet/modules/vcsrepo/spec/support/filesystem_helpers.rb b/puppet/modules/vcsrepo/spec/support/filesystem_helpers.rb
new file mode 100644
index 00000000..15e2ca75
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/support/filesystem_helpers.rb
@@ -0,0 +1,18 @@
+module FilesystemHelpers
+
+ def expects_chdir(path = resource.value(:path))
+ Dir.expects(:chdir).with(path).at_least_once.yields
+ end
+
+ def expects_mkdir(path = resource.value(:path))
+ Dir.expects(:mkdir).with(path).at_least_once
+ end
+
+ def expects_rm_rf(path = resource.value(:path))
+ FileUtils.expects(:rm_rf).with(path)
+ end
+
+ def expects_directory?(returns = true, path = resource.value(:path))
+ File.expects(:directory?).with(path).returns(returns)
+ end
+end
diff --git a/puppet/modules/vcsrepo/spec/support/fixture_helpers.rb b/puppet/modules/vcsrepo/spec/support/fixture_helpers.rb
new file mode 100644
index 00000000..8a0e0a0b
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/support/fixture_helpers.rb
@@ -0,0 +1,7 @@
+module FixtureHelpers
+
+ def fixture(name, ext = '.txt')
+ File.read(File.join(File.dirname(__FILE__), '..', 'fixtures', name.to_s + ext))
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/unit/puppet/provider/vcsrepo/bzr_spec.rb b/puppet/modules/vcsrepo/spec/unit/puppet/provider/vcsrepo/bzr_spec.rb
new file mode 100644
index 00000000..b5e2f731
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/unit/puppet/provider/vcsrepo/bzr_spec.rb
@@ -0,0 +1,109 @@
+require 'spec_helper'
+
+describe Puppet::Type.type(:vcsrepo).provider(:bzr_provider) do
+
+ let(:resource) { Puppet::Type.type(:vcsrepo).new({
+ :name => 'test',
+ :ensure => :present,
+ :provider => :bzr,
+ :revision => '2634',
+ :source => 'lp:do',
+ :path => '/tmp/test',
+ })}
+
+ let(:provider) { resource.provider }
+
+ before :each do
+ Puppet::Util.stubs(:which).with('bzr').returns('/usr/bin/bzr')
+ end
+
+ describe 'creating' do
+ context 'with defaults' do
+ it "should execute 'bzr clone -r' with the revision" do
+ provider.expects(:bzr).with('branch', '-r', resource.value(:revision), resource.value(:source), resource.value(:path))
+ provider.create
+ end
+ end
+
+ context 'without revision' do
+ it "should just execute 'bzr clone' without a revision" do
+ resource.delete(:revision)
+ provider.expects(:bzr).with('branch', resource.value(:source), resource.value(:path))
+ provider.create
+ end
+ end
+
+ context 'without source' do
+ it "should execute 'bzr init'" do
+ resource.delete(:source)
+ provider.expects(:bzr).with('init', resource.value(:path))
+ provider.create
+ end
+ end
+ end
+
+ describe 'destroying' do
+ it "it should remove the directory" do
+ provider.destroy
+ end
+ end
+
+ describe "checking existence" do
+ it "should check for the directory" do
+ File.expects(:directory?).with(File.join(resource.value(:path), '.bzr')).returns(true)
+ provider.exists?
+ end
+ end
+
+ describe "checking the revision property" do
+ before do
+ expects_chdir
+ provider.expects(:bzr).with('version-info').returns(File.read(fixtures('bzr_version_info.txt')))
+ @current_revid = 'menesis@pov.lt-20100309191856-4wmfqzc803fj300x'
+ end
+
+ context "when given a non-revid as the resource revision" do
+ context "when its revid is not different than the current revid" do
+ it "should return the ref" do
+ resource[:revision] = '2634'
+ provider.expects(:bzr).with('revision-info', '2634').returns("2634 menesis@pov.lt-20100309191856-4wmfqzc803fj300x\n")
+ expect(provider.revision).to eq(resource.value(:revision))
+ end
+ end
+ context "when its revid is different than the current revid" do
+ it "should return the current revid" do
+ resource[:revision] = '2636'
+ provider.expects(:bzr).with('revision-info', resource.value(:revision)).returns("2635 foo\n")
+ expect(provider.revision).to eq(@current_revid)
+ end
+ end
+ end
+
+ context "when given a revid as the resource revision" do
+ context "when it is the same as the current revid" do
+ it "should return it" do
+ resource[:revision] = 'menesis@pov.lt-20100309191856-4wmfqzc803fj300x'
+ provider.expects(:bzr).with('revision-info', resource.value(:revision)).returns("1234 #{resource.value(:revision)}\n")
+ expect(provider.revision).to eq(resource.value(:revision))
+ end
+ end
+ context "when it is not the same as the current revid" do
+ it "should return the current revid" do
+ resource[:revision] = 'menesis@pov.lt-20100309191856-4wmfqzc803fj300y'
+ provider.expects(:bzr).with('revision-info', resource.value(:revision)).returns("2636 foo\n")
+ expect(provider.revision).to eq(@current_revid)
+ end
+ end
+
+ end
+ end
+
+ describe "setting the revision property" do
+ it "should use 'bzr update -r' with the revision" do
+ Dir.expects(:chdir).with('/tmp/test').at_least_once.yields
+ provider.expects(:bzr).with('update', '-r', 'somerev')
+ provider.revision = 'somerev'
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/unit/puppet/provider/vcsrepo/cvs_spec.rb b/puppet/modules/vcsrepo/spec/unit/puppet/provider/vcsrepo/cvs_spec.rb
new file mode 100644
index 00000000..2e18149a
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/unit/puppet/provider/vcsrepo/cvs_spec.rb
@@ -0,0 +1,124 @@
+require 'spec_helper'
+
+describe Puppet::Type.type(:vcsrepo).provider(:cvs_provider) do
+
+ let(:resource) { Puppet::Type.type(:vcsrepo).new({
+ :name => 'test',
+ :ensure => :present,
+ :provider => :cvs,
+ :revision => '2634',
+ :source => 'lp:do',
+ :path => '/tmp/test',
+ })}
+
+ let(:provider) { resource.provider }
+
+ before :each do
+ Puppet::Util.stubs(:which).with('cvs').returns('/usr/bin/cvs')
+ end
+
+ describe 'creating' do
+ context "with a source" do
+ it "should execute 'cvs checkout'" do
+ resource[:source] = ':ext:source@example.com:/foo/bar'
+ resource[:revision] = 'an-unimportant-value'
+ expects_chdir('/tmp')
+ Puppet::Util::Execution.expects(:execute).with([:cvs, '-d', resource.value(:source), 'checkout', '-r', 'an-unimportant-value', '-d', 'test', 'bar'], :custom_environment => {})
+ provider.create
+ end
+
+ it "should execute 'cvs checkout' as user 'muppet'" do
+ resource[:source] = ':ext:source@example.com:/foo/bar'
+ resource[:revision] = 'an-unimportant-value'
+ resource[:user] = 'muppet'
+ expects_chdir('/tmp')
+ Puppet::Util::Execution.expects(:execute).with([:cvs, '-d', resource.value(:source), 'checkout', '-r', 'an-unimportant-value', '-d', 'test', 'bar'], :uid => 'muppet', :custom_environment => {})
+ provider.create
+ end
+
+ it "should just execute 'cvs checkout' without a revision" do
+ resource[:source] = ':ext:source@example.com:/foo/bar'
+ resource.delete(:revision)
+ Puppet::Util::Execution.expects(:execute).with([:cvs, '-d', resource.value(:source), 'checkout', '-d', File.basename(resource.value(:path)), File.basename(resource.value(:source))], :custom_environment => {})
+ provider.create
+ end
+
+ context "with a compression" do
+ it "should just execute 'cvs checkout' without a revision" do
+ resource[:source] = ':ext:source@example.com:/foo/bar'
+ resource[:compression] = '3'
+ resource.delete(:revision)
+ Puppet::Util::Execution.expects(:execute).with([:cvs, '-d', resource.value(:source), '-z', '3', 'checkout', '-d', File.basename(resource.value(:path)), File.basename(resource.value(:source))], :custom_environment => {})
+ provider.create
+ end
+ end
+ end
+
+ context "when a source is not given" do
+ it "should execute 'cvs init'" do
+ resource.delete(:source)
+ Puppet::Util::Execution.expects(:execute).with([:cvs, '-d', resource.value(:path), 'init'], :custom_environment => {})
+ provider.create
+ end
+ end
+ end
+
+ describe 'destroying' do
+ it "it should remove the directory" do
+ provider.destroy
+ end
+ end
+
+ describe "checking existence" do
+ it "should check for the CVS directory with source" do
+ resource[:source] = ':ext:source@example.com:/foo/bar'
+ File.expects(:directory?).with(File.join(resource.value(:path), 'CVS'))
+ provider.exists?
+ end
+
+ it "should check for the CVSROOT directory without source" do
+ resource.delete(:source)
+ File.expects(:directory?).with(File.join(resource.value(:path), 'CVSROOT'))
+ provider.exists?
+ end
+ end
+
+ describe "checking the revision property" do
+ before do
+ @tag_file = File.join(resource.value(:path), 'CVS', 'Tag')
+ end
+
+ context "when CVS/Tag exists" do
+ before do
+ @tag = 'TAG'
+ File.expects(:exist?).with(@tag_file).returns(true)
+ end
+ it "should read CVS/Tag" do
+ File.expects(:read).with(@tag_file).returns("T#{@tag}")
+ expect(provider.revision).to eq(@tag)
+ end
+ end
+
+ context "when CVS/Tag does not exist" do
+ before do
+ File.expects(:exist?).with(@tag_file).returns(false)
+ end
+ it "assumes HEAD" do
+ expect(provider.revision).to eq('HEAD')
+ end
+ end
+ end
+
+ describe "when setting the revision property" do
+ before do
+ @tag = 'SOMETAG'
+ end
+
+ it "should use 'cvs update -dr'" do
+ expects_chdir
+ Puppet::Util::Execution.expects(:execute).with([:cvs, 'update', '-dr', @tag, '.'], :custom_environment => {})
+ provider.revision = @tag
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/unit/puppet/provider/vcsrepo/git_spec.rb b/puppet/modules/vcsrepo/spec/unit/puppet/provider/vcsrepo/git_spec.rb
new file mode 100644
index 00000000..6a8f58f8
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/unit/puppet/provider/vcsrepo/git_spec.rb
@@ -0,0 +1,401 @@
+require 'spec_helper'
+
+describe Puppet::Type.type(:vcsrepo).provider(:git_provider) do
+ def branch_a_list(include_branch = nil?)
+ <<branches
+end
+#{"* master" unless include_branch.nil?}
+#{"* " + include_branch unless !include_branch}
+ remote/origin/master
+ remote/origin/foo
+
+branches
+ end
+ let(:resource) { Puppet::Type.type(:vcsrepo).new({
+ :name => 'test',
+ :ensure => :present,
+ :provider => :git,
+ :revision => '2634',
+ :source => 'git@repo',
+ :path => '/tmp/test',
+ :force => false
+ })}
+
+ let(:provider) { resource.provider }
+
+ before :each do
+ Puppet::Util.stubs(:which).with('git').returns('/usr/bin/git')
+ end
+
+ context 'creating' do
+ context "with a revision that is a remote branch" do
+ it "should execute 'git clone' and 'git checkout -b'" do
+ resource[:revision] = 'only/remote'
+ Dir.expects(:chdir).with('/').at_least_once.yields
+ Dir.expects(:chdir).with('/tmp/test').at_least_once.yields
+ provider.expects(:git).with('clone', resource.value(:source), resource.value(:path))
+ provider.expects(:update_submodules)
+ provider.expects(:update_remote_url).with("origin", resource.value(:source)).returns false
+ provider.expects(:git).with('branch', '-a').returns(branch_a_list(resource.value(:revision)))
+ provider.expects(:git).with('checkout', '--force', resource.value(:revision))
+ provider.create
+ end
+ end
+
+ context "with a remote not named 'origin'" do
+ it "should execute 'git clone --origin not_origin" do
+ resource[:remote] = 'not_origin'
+ Dir.expects(:chdir).with('/').at_least_once.yields
+ Dir.expects(:chdir).with('/tmp/test').at_least_once.yields
+ provider.expects(:git).with('clone', '--origin', 'not_origin', resource.value(:source), resource.value(:path))
+ provider.expects(:update_submodules)
+ provider.expects(:update_remote_url).with("not_origin", resource.value(:source)).returns false
+ provider.expects(:git).with('branch', '-a').returns(branch_a_list(resource.value(:revision)))
+ provider.expects(:git).with('checkout', '--force', resource.value(:revision))
+ provider.create
+ end
+ end
+
+ context "with shallow clone enable" do
+ it "should execute 'git clone --depth 1'" do
+ resource[:revision] = 'only/remote'
+ resource[:depth] = 1
+ Dir.expects(:chdir).with('/').at_least_once.yields
+ Dir.expects(:chdir).with('/tmp/test').at_least_once.yields
+ provider.expects(:git).with('clone', '--depth', '1', '--branch', resource.value(:revision),resource.value(:source), resource.value(:path))
+ provider.expects(:update_submodules)
+ provider.expects(:update_remote_url).with("origin", resource.value(:source)).returns false
+ provider.expects(:git).with('branch', '-a').returns(branch_a_list(resource.value(:revision)))
+ provider.expects(:git).with('checkout', '--force', resource.value(:revision))
+ provider.create
+ end
+ end
+
+ context "with a revision that is not a remote branch" do
+ it "should execute 'git clone' and 'git reset --hard'" do
+ resource[:revision] = 'a-commit-or-tag'
+ Dir.expects(:chdir).with('/').at_least_once.yields
+ Dir.expects(:chdir).with('/tmp/test').at_least_once.yields
+ provider.expects(:git).with('clone', resource.value(:source), resource.value(:path))
+ provider.expects(:update_submodules)
+ provider.expects(:update_remote_url).with("origin", resource.value(:source)).returns false
+ provider.expects(:git).with('branch', '-a').returns(branch_a_list(resource.value(:revision)))
+ provider.expects(:git).with('checkout', '--force', resource.value(:revision))
+ provider.create
+ end
+
+ it "should execute 'git clone' and submodule commands" do
+ resource.delete(:revision)
+ provider.expects(:git).with('clone', resource.value(:source), resource.value(:path))
+ provider.expects(:update_submodules)
+ provider.expects(:update_remotes)
+ provider.create
+ end
+ end
+
+ context "with an ensure of bare" do
+ context "with revision" do
+ it "should raise an error" do
+ resource[:ensure] = :bare
+ expect { provider.create }.to raise_error Puppet::Error, /cannot set a revision.+bare/i
+ end
+ end
+ context "without revision" do
+ it "should just execute 'git clone --bare'" do
+ resource[:ensure] = :bare
+ resource.delete(:revision)
+ provider.expects(:git).with('clone', '--bare', resource.value(:source), resource.value(:path))
+ provider.expects(:update_remotes)
+ provider.create
+ end
+ end
+ end
+
+ context "with an ensure of mirror" do
+ context "with revision" do
+ it "should raise an error" do
+ resource[:ensure] = :mirror
+ expect { provider.create }.to raise_error Puppet::Error, /cannot set a revision.+bare/i
+ end
+ end
+ context "without revision" do
+ it "should just execute 'git clone --mirror'" do
+ resource[:ensure] = :mirror
+ resource.delete(:revision)
+ provider.expects(:git).with('clone', '--mirror', resource.value(:source), resource.value(:path))
+ provider.expects(:update_remotes)
+ provider.create
+ end
+ end
+ end
+
+ context "when a source is not given" do
+ context "when the path does not exist" do
+ it "should execute 'git init'" do
+ resource[:ensure] = :present
+ resource.delete(:source)
+ expects_mkdir
+ expects_chdir
+ expects_directory?(false)
+
+ provider.expects(:bare_exists?).returns(false)
+ provider.expects(:git).with('init')
+ provider.create
+ end
+ end
+
+ context "when the path is a bare repository" do
+ it "should convert it to a working copy" do
+ resource[:ensure] = :present
+ resource.delete(:source)
+ provider.expects(:bare_exists?).returns(true)
+ provider.expects(:convert_bare_to_working_copy)
+ provider.create
+ end
+ end
+
+ context "when the path is not empty and not a repository" do
+ it "should raise an exception" do
+ provider.expects(:path_exists?).returns(true)
+ provider.expects(:path_empty?).returns(false)
+ expect { provider.create }.to raise_error(Puppet::Error)
+ end
+ end
+ end
+
+ context "when the path does not exist" do
+ it "should execute 'git init --bare'" do
+ resource[:ensure] = :bare
+ resource.delete(:source)
+ resource.delete(:revision)
+ expects_chdir
+ expects_mkdir
+ expects_directory?(false)
+ provider.expects(:working_copy_exists?).returns(false)
+ provider.expects(:git).with('init', '--bare')
+ provider.create
+ end
+
+ it "should raise an exeption" do
+ resource[:ensure] = :mirror
+ resource.delete(:source)
+ resource.delete(:revision)
+
+ expect { provider.create }.to raise_error Puppet::Error, /cannot init repository with mirror.+try bare/i
+ end
+ end
+
+ context "when the path is a working copy repository" do
+ it "should convert it to a bare repository" do
+ resource[:ensure] = :bare
+ resource.delete(:source)
+ resource.delete(:revision)
+ provider.expects(:working_copy_exists?).returns(true)
+ provider.expects(:convert_working_copy_to_bare)
+ provider.create
+ end
+ it "should clone overtop it using force" do
+ resource[:force] = true
+ Dir.expects(:chdir).with('/').at_least_once.yields
+ Dir.expects(:chdir).with('/tmp/test').at_least_once.yields
+ provider.expects(:path_exists?).returns(true)
+ provider.expects(:path_empty?).returns(false)
+ provider.destroy
+ provider.expects(:git).with('clone',resource.value(:source), resource.value(:path))
+ provider.expects(:update_submodules)
+ provider.expects(:update_remote_url).with("origin", resource.value(:source)).returns false
+ provider.expects(:git).with('branch', '-a').returns(branch_a_list(resource.value(:revision)))
+ provider.expects(:git).with('checkout', '--force', resource.value(:revision))
+ provider.create
+ end
+ end
+
+ context "when the path is not empty and not a repository" do
+ it "should raise an exception" do
+ provider.expects(:path_exists?).returns(true)
+ provider.expects(:path_empty?).returns(false)
+ provider.expects(:working_copy_exists?).returns(false)
+ expect { provider.create }.to raise_error(Puppet::Error)
+ end
+ end
+ end
+
+
+ context 'destroying' do
+ it "it should remove the directory" do
+ #expects_rm_rf
+ provider.destroy
+ end
+ end
+
+ context "checking the revision property" do
+ before do
+ expects_chdir('/tmp/test')
+ resource[:revision] = 'currentsha'
+ resource[:source] = 'http://example.com'
+ provider.stubs(:git).with('config', 'remote.origin.url').returns('')
+ provider.stubs(:git).with('fetch', 'origin') # FIXME
+ provider.stubs(:git).with('fetch', '--tags', 'origin')
+ provider.stubs(:git).with('rev-parse', 'HEAD').returns('currentsha')
+ provider.stubs(:git).with('branch', '-a').returns(branch_a_list(resource.value(:revision)))
+ provider.stubs(:git).with('tag', '-l').returns("Hello")
+ end
+
+ context "when its SHA is not different than the current SHA" do
+ it "should return the ref" do
+ provider.expects(:git).with('rev-parse', resource.value(:revision)).returns('currentsha')
+ provider.expects(:update_remotes)
+ expect(provider.revision).to eq(resource.value(:revision))
+ end
+ end
+
+ context "when its SHA is different than the current SHA" do
+ it "should return the current SHA" do
+ provider.expects(:git).with('rev-parse', resource.value(:revision)).returns('othersha')
+ provider.expects(:update_remotes)
+ expect(provider.revision).to eq(resource.value(:revision))
+ end
+ end
+
+ context "when its a ref to a remote head" do
+ it "should return the revision" do
+ provider.stubs(:git).with('branch', '-a').returns(" remotes/origin/#{resource.value(:revision)}")
+ provider.expects(:git).with('rev-parse', "origin/#{resource.value(:revision)}").returns("newsha")
+ provider.expects(:update_remotes)
+ expect(provider.revision).to eq(resource.value(:revision))
+ end
+ end
+
+ context "when its a ref to non existant remote head" do
+ it "should fail" do
+ provider.expects(:git).with('branch', '-a').returns(branch_a_list)
+ provider.expects(:git).with('rev-parse', '--revs-only', resource.value(:revision)).returns('')
+ provider.expects(:update_remotes)
+ expect { provider.revision }.to raise_error(Puppet::Error, /not a local or remote ref$/)
+ end
+ end
+
+ context "when the source is modified" do
+ it "should update the origin url" do
+ resource[:source] = 'git://git@foo.com/bar.git'
+ provider.expects(:git).with('config', '-l').returns("remote.origin.url=git://git@foo.com/foo.git\n")
+ provider.expects(:git).with('remote', 'set-url', 'origin', 'git://git@foo.com/bar.git')
+ provider.expects(:git).with('remote','update')
+ provider.expects(:git).with('rev-parse', resource.value(:revision)).returns('currentsha')
+ expect(provider.revision).to eq(resource.value(:revision))
+ end
+ end
+
+ context "when multiple sources are modified" do
+ it "should update the urls" do
+ resource[:source] = {"origin" => "git://git@foo.com/bar.git", "new_remote" => "git://git@foo.com/baz.git"}
+ provider.expects(:git).at_least_once.with('config', '-l').returns("remote.origin.url=git://git@foo.com/bar.git\n", "remote.origin.url=git://git@foo.com/foo.git\n")
+ provider.expects(:git).with('remote', 'set-url', 'origin', 'git://git@foo.com/bar.git')
+ provider.expects(:git).with('remote', 'add', 'new_remote', 'git://git@foo.com/baz.git')
+ provider.expects(:git).with('remote','update')
+ provider.expects(:git).with('rev-parse', resource.value(:revision)).returns('currentsha')
+ expect(provider.revision).to eq(resource.value(:revision))
+ end
+ end
+
+ context "when there's no source" do
+ it 'should return the revision' do
+ resource.delete(:source)
+ provider.expects(:git).with('status')
+ provider.expects(:git).with('rev-parse', resource.value(:revision)).returns('currentsha')
+ expect(provider.revision).to eq(resource.value(:revision))
+ end
+ end
+ end
+
+ context "setting the revision property" do
+ before do
+ expects_chdir
+ end
+ context "when it's an existing local branch" do
+ it "should use 'git fetch' and 'git reset'" do
+ resource[:revision] = 'feature/foo'
+ provider.expects(:update_submodules)
+ provider.expects(:git).with('branch', '-a').at_least_once.returns(branch_a_list(resource.value(:revision)))
+ provider.expects(:git).with('checkout', '--force', resource.value(:revision))
+ provider.expects(:git).with('reset', '--hard', "origin/#{resource.value(:revision)}")
+ provider.revision = resource.value(:revision)
+ end
+ end
+ context "when it's a remote branch" do
+ it "should use 'git fetch' and 'git reset'" do
+ resource[:revision] = 'only/remote'
+ provider.expects(:update_submodules)
+ provider.expects(:git).with('branch', '-a').at_least_once.returns(resource.value(:revision))
+ provider.expects(:git).with('checkout', '--force', resource.value(:revision))
+ provider.expects(:git).with('reset', '--hard', "origin/#{resource.value(:revision)}")
+ provider.revision = resource.value(:revision)
+ end
+ end
+ context "when it's a commit or tag" do
+ it "should use 'git fetch' and 'git reset'" do
+ resource[:revision] = 'a-commit-or-tag'
+ provider.expects(:git).with('branch', '-a').at_least_once.returns(fixture(:git_branch_a))
+ provider.expects(:git).with('checkout', '--force', resource.value(:revision))
+ provider.expects(:git).with('branch', '-a').returns(fixture(:git_branch_a))
+ provider.expects(:git).with('branch', '-a').returns(fixture(:git_branch_a))
+ provider.expects(:git).with('submodule', 'update', '--init', '--recursive')
+ provider.revision = resource.value(:revision)
+ end
+ end
+ end
+
+ context "updating references" do
+ it "should use 'git fetch --tags'" do
+ resource.delete(:source)
+ expects_chdir
+ provider.expects(:git).with('config', '-l').returns("remote.origin.url=git://git@foo.com/foo.git\n")
+ provider.expects(:git).with('fetch', 'origin')
+ provider.expects(:git).with('fetch', '--tags', 'origin')
+ provider.update_references
+ end
+ end
+
+ describe 'latest?' do
+ context 'when true' do
+ it do
+ provider.expects(:revision).returns('testrev')
+ provider.expects(:latest_revision).returns('testrev')
+ expect(provider.latest?).to be_truthy
+ end
+ end
+ context 'when false' do
+ it do
+ provider.expects(:revision).returns('master')
+ provider.expects(:latest_revision).returns('testrev')
+ expect(provider.latest?).to be_falsey
+ end
+ end
+ end
+
+ describe 'convert_working_copy_to_bare' do
+ it do
+ FileUtils.expects(:mv).returns(true)
+ FileUtils.expects(:rm_rf).returns(true)
+ FileUtils.expects(:mv).returns(true)
+
+ provider.instance_eval { convert_working_copy_to_bare }
+ end
+ end
+
+ describe 'convert_bare_to_working_copy' do
+ it do
+ FileUtils.expects(:mv).returns(true)
+ FileUtils.expects(:mkdir).returns(true)
+ FileUtils.expects(:mv).returns(true)
+ provider.expects(:commits_in?).returns(true)
+ # If you forget to stub these out you lose 3 hours of rspec work.
+ provider.expects(:reset).with('HEAD').returns(true)
+ provider.expects(:git_with_identity).returns(true)
+ provider.expects(:update_owner_and_excludes).returns(true)
+
+ provider.instance_eval { convert_bare_to_working_copy }
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/unit/puppet/provider/vcsrepo/hg_spec.rb b/puppet/modules/vcsrepo/spec/unit/puppet/provider/vcsrepo/hg_spec.rb
new file mode 100644
index 00000000..65d820d9
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/unit/puppet/provider/vcsrepo/hg_spec.rb
@@ -0,0 +1,138 @@
+require 'spec_helper'
+
+describe Puppet::Type.type(:vcsrepo).provider(:hg) do
+
+ let(:resource) { Puppet::Type.type(:vcsrepo).new({
+ :name => 'test',
+ :ensure => :present,
+ :provider => :hg,
+ :path => '/tmp/vcsrepo',
+ })}
+
+ let(:provider) { resource.provider }
+
+ before :each do
+ Puppet::Util.stubs(:which).with('hg').returns('/usr/bin/hg')
+ end
+
+ describe 'creating' do
+ context 'with source and revision' do
+ it "should execute 'hg clone -u' with the revision" do
+ resource[:source] = 'something'
+ resource[:revision] = '1'
+ provider.expects(:hg).with('clone', '-u',
+ resource.value(:revision),
+ resource.value(:source),
+ resource.value(:path))
+ provider.create
+ end
+ end
+
+ context 'without revision' do
+ it "should just execute 'hg clone' without a revision" do
+ resource[:source] = 'something'
+ provider.expects(:hg).with('clone', resource.value(:source), resource.value(:path))
+ provider.create
+ end
+ end
+
+ context "when a source is not given" do
+ it "should execute 'hg init'" do
+ provider.expects(:hg).with('init', resource.value(:path))
+ provider.create
+ end
+ end
+
+ context "when basic auth is used" do
+ it "should execute 'hg clone'" do
+ resource[:source] = 'something'
+ resource[:basic_auth_username] = 'user'
+ resource[:basic_auth_password] = 'pass'
+ provider.expects(:hg).with('clone',
+ resource.value(:source),
+ resource.value(:path),
+ "--config","\"auth.x.prefix=" + resource.value(:source) + "\"",
+ "--config","\"auth.x.username=" + resource.value(:basic_auth_username) + "\"",
+ "--config","\"auth.x.password=" + resource.value(:basic_auth_password) + "\"",
+ "--config","\"auth.x.schemes=http https" + "\"")
+ provider.create
+ end
+ end
+ end
+
+ describe 'destroying' do
+ it "it should remove the directory" do
+ expects_rm_rf
+ provider.destroy
+ end
+ end
+
+ describe "checking existence" do
+ it "should check for the directory" do
+ expects_directory?(true, File.join(resource.value(:path), '.hg'))
+ provider.exists?
+ end
+ end
+
+ describe "checking the revision property" do
+ before do
+ expects_chdir
+ end
+
+ context "when given a non-SHA as the resource revision" do
+ before do
+ provider.expects(:hg).with('parents').returns(fixture(:hg_parents))
+ provider.expects(:hg).with('tags').returns(fixture(:hg_tags))
+ end
+
+ context "when its SHA is not different than the current SHA" do
+ it "should return the ref" do
+ resource[:revision] = '0.6'
+ expect(provider.revision).to eq('0.6')
+ end
+ end
+
+ context "when its SHA is different than the current SHA" do
+ it "should return the current SHA" do
+ resource[:revision] = '0.5.3'
+ expect(provider.revision).to eq('34e6012c783a')
+ end
+ end
+ end
+ context "when given a SHA as the resource revision" do
+ before do
+ provider.expects(:hg).with('parents').returns(fixture(:hg_parents))
+ end
+
+ context "when it is the same as the current SHA", :resource => {:revision => '34e6012c783a'} do
+ it "should return it" do
+ resource[:revision] = '34e6012c783a'
+ provider.expects(:hg).with('tags').returns(fixture(:hg_tags))
+ expect(provider.revision).to eq(resource.value(:revision))
+ end
+ end
+
+ context "when it is not the same as the current SHA", :resource => {:revision => 'not-the-same'} do
+ it "should return the current SHA" do
+ resource[:revision] = 'not-the-same'
+ provider.expects(:hg).with('tags').returns(fixture(:hg_tags))
+ expect(provider.revision).to eq('34e6012c783a')
+ end
+ end
+ end
+ end
+
+ describe "setting the revision property" do
+ before do
+ @revision = '6aa99e9b3ab1'
+ end
+ it "should use 'hg update ---clean -r'" do
+ expects_chdir
+ provider.expects(:hg).with('pull')
+ provider.expects(:hg).with('merge')
+ provider.expects(:hg).with('update', '--clean', '-r', @revision)
+ provider.revision = @revision
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/unit/puppet/provider/vcsrepo/p4_spec.rb b/puppet/modules/vcsrepo/spec/unit/puppet/provider/vcsrepo/p4_spec.rb
new file mode 100644
index 00000000..e331cae6
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/unit/puppet/provider/vcsrepo/p4_spec.rb
@@ -0,0 +1,82 @@
+require 'spec_helper'
+
+describe Puppet::Type.type(:vcsrepo).provider(:p4) do
+
+ let(:resource) { Puppet::Type.type(:vcsrepo).new({
+ :name => 'test',
+ :ensure => :present,
+ :provider => :p4,
+ :path => '/tmp/vcsrepo',
+ })}
+
+ let(:provider) { resource.provider }
+
+ before :each do
+ Puppet::Util.stubs(:which).with('p4').returns('/usr/local/bin/p4')
+ end
+
+ spec = {
+ :input => "Description: Generated by Puppet VCSrepo\nRoot: /tmp/vcsrepo\n\nView:\n",
+ :marshal => false
+ }
+
+ describe 'creating' do
+ context 'with source and revision' do
+ it "should execute 'p4 sync' with the revision" do
+ resource[:source] = 'something'
+ resource[:revision] = '1'
+ ENV['P4CLIENT'] = 'client_ws1'
+
+ provider.expects(:p4).with(['client', '-o', 'client_ws1']).returns({})
+ provider.expects(:p4).with(['client', '-i'], spec)
+ provider.expects(:p4).with(['sync', resource.value(:source) + "@" + resource.value(:revision)])
+ provider.create
+ end
+ end
+
+ context 'without revision' do
+ it "should just execute 'p4 sync' without a revision" do
+ resource[:source] = 'something'
+ ENV['P4CLIENT'] = 'client_ws2'
+
+ provider.expects(:p4).with(['client', '-o', 'client_ws2']).returns({})
+ provider.expects(:p4).with(['client', '-i'], spec)
+ provider.expects(:p4).with(['sync', resource.value(:source)])
+ provider.create
+ end
+ end
+
+ context "when a client and source are not given" do
+ it "should execute 'p4 client'" do
+ ENV['P4CLIENT'] = nil
+
+ path = resource.value(:path)
+ host = Facter.value('hostname')
+ default = "puppet-" + Digest::MD5.hexdigest(path + host)
+
+ provider.expects(:p4).with(['client', '-o', default]).returns({})
+ provider.expects(:p4).with(['client', '-i'], spec)
+ provider.create
+ end
+ end
+ end
+
+ describe 'destroying' do
+ it "it should remove the directory" do
+ ENV['P4CLIENT'] = 'test_client'
+
+ provider.expects(:p4).with(['client', '-d', '-f', 'test_client'])
+ expects_rm_rf
+ provider.destroy
+ end
+ end
+
+ describe "checking existence" do
+ it "should check for the directory" do
+ provider.expects(:p4).with(['info'], {:marshal => false}).returns({})
+ provider.expects(:p4).with(['where', resource.value(:path) + "..."], {:raise => false}).returns({})
+ provider.exists?
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/unit/puppet/provider/vcsrepo/svn_spec.rb b/puppet/modules/vcsrepo/spec/unit/puppet/provider/vcsrepo/svn_spec.rb
new file mode 100644
index 00000000..6a37c205
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/unit/puppet/provider/vcsrepo/svn_spec.rb
@@ -0,0 +1,160 @@
+require 'spec_helper'
+
+describe Puppet::Type.type(:vcsrepo).provider(:svn) do
+
+ let(:resource) { Puppet::Type.type(:vcsrepo).new({
+ :name => 'test',
+ :ensure => :present,
+ :provider => :svn,
+ :path => '/tmp/vcsrepo',
+ })}
+
+ let(:provider) { resource.provider }
+
+ before :each do
+ Puppet::Util.stubs(:which).with('git').returns('/usr/bin/git')
+ end
+
+ describe 'creating' do
+ context 'with source and revision' do
+ it "should execute 'svn checkout' with a revision" do
+ resource[:source] = 'exists'
+ resource[:revision] = '1'
+ provider.expects(:svn).with('--non-interactive', 'checkout', '-r',
+ resource.value(:revision),
+ resource.value(:source),
+ resource.value(:path))
+ provider.create
+ end
+ end
+ context 'with source' do
+ it "should just execute 'svn checkout' without a revision" do
+ resource[:source] = 'exists'
+ provider.expects(:svn).with('--non-interactive', 'checkout',
+ resource.value(:source),
+ resource.value(:path))
+ provider.create
+ end
+ end
+
+ context 'with fstype' do
+ it "should execute 'svnadmin create' with an '--fs-type' option" do
+ resource[:fstype] = 'ext4'
+ provider.expects(:svnadmin).with('create', '--fs-type',
+ resource.value(:fstype),
+ resource.value(:path))
+ provider.create
+ end
+ end
+ context 'without fstype' do
+ it "should execute 'svnadmin create' without an '--fs-type' option" do
+ provider.expects(:svnadmin).with('create', resource.value(:path))
+ provider.create
+ end
+ end
+
+ context "with depth" do
+ it "should execute 'svn checkout' with a depth" do
+ resource[:source] = 'exists'
+ resource[:depth] = 'infinity'
+ provider.expects(:svn).with('--non-interactive', 'checkout', '--depth', 'infinity',
+ resource.value(:source),
+ resource.value(:path))
+ provider.create
+ end
+ end
+
+ context "with trust_server_cert" do
+ it "should execute 'svn checkout' without a trust-server-cert" do
+ resource[:source] = 'exists'
+ resource[:trust_server_cert] = :false
+ provider.expects(:svn).with('--non-interactive', 'checkout',
+ resource.value(:source),
+ resource.value(:path))
+ provider.create
+ end
+ it "should execute 'svn checkout' with a trust-server-cert" do
+ resource[:source] = 'exists'
+ resource[:trust_server_cert] = :true
+ provider.expects(:svn).with('--non-interactive', '--trust-server-cert', 'checkout',
+ resource.value(:source),
+ resource.value(:path))
+ provider.create
+ end
+ end
+ end
+
+ describe 'destroying' do
+ it "it should remove the directory" do
+ expects_rm_rf
+ provider.destroy
+ end
+ end
+
+ describe "checking existence" do
+ it "should check for the directory" do
+ expects_directory?(true, resource.value(:path))
+ expects_directory?(true, File.join(resource.value(:path), '.svn'))
+ provider.exists?
+ end
+ end
+
+ describe "checking the revision property" do
+ before do
+ provider.expects(:svn).with('--non-interactive', 'info').returns(fixture(:svn_info))
+ end
+ it "should use 'svn info'" do
+ expects_chdir
+ expect(provider.revision).to eq('4') # From 'Revision', not 'Last Changed Rev'
+ end
+ end
+
+ describe "setting the revision property" do
+ before do
+ @revision = '30'
+ end
+ context 'with conflict' do
+ it "should use 'svn update'" do
+ resource[:conflict] = 'theirs-full'
+ expects_chdir
+ provider.expects(:svn).with('--non-interactive', 'update',
+ '-r', @revision,
+ '--accept', resource.value(:conflict))
+ provider.revision = @revision
+ end
+ end
+ context 'without conflict' do
+ it "should use 'svn update'" do
+ expects_chdir
+ provider.expects(:svn).with('--non-interactive', 'update', '-r', @revision)
+ provider.revision = @revision
+ end
+ end
+ end
+
+ describe "setting the revision property and repo source" do
+ before do
+ @revision = '30'
+ end
+ context 'with conflict' do
+ it "should use 'svn switch'" do
+ resource[:source] = 'an-unimportant-value'
+ resource[:conflict] = 'theirs-full'
+ expects_chdir
+ provider.expects(:svn).with('--non-interactive', 'switch',
+ '-r', @revision, 'an-unimportant-value',
+ '--accept', resource.value(:conflict))
+ provider.revision = @revision
+ end
+ end
+ context 'without conflict' do
+ it "should use 'svn switch'" do
+ resource[:source] = 'an-unimportant-value'
+ expects_chdir
+ provider.expects(:svn).with('--non-interactive', 'switch', '-r', @revision, 'an-unimportant-value')
+ provider.revision = @revision
+ end
+ end
+ end
+
+end
diff --git a/puppet/modules/vcsrepo/spec/unit/puppet/type/README.markdown b/puppet/modules/vcsrepo/spec/unit/puppet/type/README.markdown
new file mode 100644
index 00000000..1ee19ac8
--- /dev/null
+++ b/puppet/modules/vcsrepo/spec/unit/puppet/type/README.markdown
@@ -0,0 +1,4 @@
+Resource Type Specs
+===================
+
+Define specs for your resource types in this directory.
diff --git a/puppet/modules/x509/manifests/base.pp b/puppet/modules/x509/manifests/base.pp
new file mode 100644
index 00000000..b88cce64
--- /dev/null
+++ b/puppet/modules/x509/manifests/base.pp
@@ -0,0 +1,45 @@
+class x509::base {
+ include x509::variables
+
+ package { [ 'ssl-cert', 'ca-certificates' ]:
+ ensure => installed;
+ }
+
+ group { 'ssl-cert':
+ ensure => present,
+ system => true,
+ require => Package['ssl-cert'];
+ }
+
+ file {
+ $x509::variables::root:
+ ensure => directory,
+ mode => '0755',
+ owner => root,
+ group => root;
+
+ $x509::variables::keys:
+ ensure => directory,
+ mode => '0750',
+ owner => root,
+ group => ssl-cert;
+
+ $x509::variables::certs:
+ ensure => directory,
+ mode => '0755',
+ owner => root,
+ group => root;
+
+ $x509::variables::local_CAs:
+ ensure => directory,
+ mode => '2775',
+ owner => root,
+ group => root;
+ }
+
+ exec { 'update-ca-certificates':
+ command => '/usr/sbin/update-ca-certificates',
+ refreshonly => true,
+ subscribe => File[$x509::variables::local_CAs]
+ }
+}
diff --git a/puppet/modules/x509/manifests/ca.pp b/puppet/modules/x509/manifests/ca.pp
new file mode 100644
index 00000000..0e068cd3
--- /dev/null
+++ b/puppet/modules/x509/manifests/ca.pp
@@ -0,0 +1,34 @@
+define x509::ca (
+ $content = 'absent',
+ $source = 'absent'
+) {
+ include x509::variables
+ include x509::base
+
+ file { "${x509::variables::local_CAs}/${name}.crt" :
+ ensure => file,
+ mode => '0444',
+ group => 'ssl-cert',
+ require => Package['ca-certificates'],
+ notify => Exec['update-ca-certificates'],
+ }
+ case $content {
+ 'absent': {
+ $real_source = $source ? {
+ 'absent' => [
+ "puppet:///modules/site_x509/CAs/${::fqdn}/${name}.crt",
+ "puppet:///modules/site_x509/CAs/${name}.crt"
+ ],
+ default => "puppet:///$source",
+ }
+ File["${x509::variables::local_CAs}/${name}.crt"] {
+ source => $real_source
+ }
+ }
+ default: {
+ File["${x509::variables::local_CAs}/${name}.crt"] {
+ content => $content
+ }
+ }
+ }
+}
diff --git a/puppet/modules/x509/manifests/cert.pp b/puppet/modules/x509/manifests/cert.pp
new file mode 100644
index 00000000..0aafb76d
--- /dev/null
+++ b/puppet/modules/x509/manifests/cert.pp
@@ -0,0 +1,34 @@
+define x509::cert (
+ $content = 'absent',
+ $source = 'absent'
+) {
+ include x509::variables
+ include x509::base
+
+ file { "${x509::variables::certs}/${name}.crt":
+ ensure => file,
+ mode => '0444',
+ group => 'ssl-cert',
+ require => Package['ssl-cert']
+ }
+
+ case $content {
+ 'absent': {
+ $real_source = $source ? {
+ 'absent' => [
+ "puppet:///modules/site_x509/certs/${::fqdn}/${name}.crt",
+ "puppet:///modules/site_x509/certs/${name}.crt"
+ ],
+ default => "puppet:///$source",
+ }
+ File["${x509::variables::certs}/${name}.crt"] {
+ source => $real_source
+ }
+ }
+ default: {
+ File["${x509::variables::certs}/${name}.crt"] {
+ content => $content
+ }
+ }
+ }
+}
diff --git a/puppet/modules/x509/manifests/init.pp b/puppet/modules/x509/manifests/init.pp
new file mode 100644
index 00000000..8283e482
--- /dev/null
+++ b/puppet/modules/x509/manifests/init.pp
@@ -0,0 +1,2 @@
+class x509 {
+}
diff --git a/puppet/modules/x509/manifests/key.pp b/puppet/modules/x509/manifests/key.pp
new file mode 100644
index 00000000..fd7e25fd
--- /dev/null
+++ b/puppet/modules/x509/manifests/key.pp
@@ -0,0 +1,37 @@
+define x509::key (
+ $content = 'absent',
+ $source = 'absent',
+ $owner = 'root',
+ $group = 'ssl-cert'
+) {
+ include x509::variables
+ include x509::base
+
+ file { "${x509::variables::keys}/${name}.key":
+ ensure => file,
+ mode => '0640',
+ owner => $owner,
+ group => $group,
+ require => Package['ssl-cert']
+ }
+
+ case $content {
+ 'absent': {
+ $real_source = $source ? {
+ 'absent' => [
+ "puppet:///modules/site_x509/keys/${::fqdn}/${name}.key",
+ "puppet:///modules/site_x509/keys/${name}.key"
+ ],
+ default => "puppet:///$source",
+ }
+ File["${x509::variables::keys}/${name}.key"] {
+ source => $real_source
+ }
+ }
+ default: {
+ File["${x509::variables::keys}/${name}.key"] {
+ content => $content
+ }
+ }
+ }
+}
diff --git a/puppet/modules/x509/manifests/variables.pp b/puppet/modules/x509/manifests/variables.pp
new file mode 100644
index 00000000..e6bd2359
--- /dev/null
+++ b/puppet/modules/x509/manifests/variables.pp
@@ -0,0 +1,7 @@
+class x509::variables {
+ $root = '/etc/x509'
+ $certs = "${root}/certs"
+ $keys = "${root}/keys"
+ $x509_chain = "${root}/certs"
+ $local_CAs = '/usr/local/share/ca-certificates'
+}
diff --git a/tests/README.md b/tests/README.md
new file mode 100644
index 00000000..814c25b1
--- /dev/null
+++ b/tests/README.md
@@ -0,0 +1,25 @@
+Tests
+---------------------------------
+
+tests/white-box/
+
+ These tests are run on the server as superuser. They are for
+ troubleshooting any problems with the internal setup of the server.
+
+tests/black-box/
+
+ These test are run the user's local machine. They are for troubleshooting
+ any external problems with the service exposed by the server.
+
+Additional Files
+---------------------------------
+
+tests/helpers/
+
+ Utility functions made available to all tests.
+
+tests/order.rb
+
+ Configuration file to specify which nodes should be tested in which order.
+
+
diff --git a/tests/helpers/bonafide_helper.rb b/tests/helpers/bonafide_helper.rb
new file mode 100644
index 00000000..5b886228
--- /dev/null
+++ b/tests/helpers/bonafide_helper.rb
@@ -0,0 +1,235 @@
+#
+# helper for the communication with the provider API for creating, authenticating, and deleting accounts.
+#
+
+class LeapTest
+
+ def assert_tmp_user
+ user = assert_create_user
+ assert_authenticate_user(user)
+ yield user if block_given?
+ assert_delete_user(user)
+ rescue StandardError, MiniTest::Assertion => exc
+ begin
+ assert_delete_user(user)
+ rescue
+ end
+ raise exc
+ end
+
+ #
+ # attempts to create a user account via the API,
+ # returning the user object if successful.
+ #
+ def assert_create_user(username=nil, auth=nil)
+ user = SRP::User.new(username)
+ url = api_url("/users.json")
+ params = user.to_params
+ if auth
+ options = api_options(:auth => auth)
+ else
+ options = api_options
+ if property('webapp.invite_required')
+ @invite_code = generate_invite_code
+ params['user[invite_code]'] = @invite_code
+ end
+ end
+
+ assert_post(url, params, options) do |body|
+ assert response = JSON.parse(body), 'response should be JSON'
+ assert response['ok'], "Creating a user should be successful, got #{response.inspect} instead."
+ user.ok = true
+ user.id = response['id']
+ end
+ return user
+ end
+
+ # TODO: use the api for this instead.
+ def generate_invite_code
+ `cd /srv/leap/webapp/ && sudo -u leap-webapp RAILS_ENV=production bundle exec rake generate_invites[1]`.gsub(/\n/, "")
+ end
+
+ #
+ # attempts to authenticate user. if successful,
+ # user object is updated with id and session token.
+ #
+ def assert_authenticate_user(user)
+ url = api_url("/sessions.json")
+ session = SRP::Session.new(user)
+ params = {'login' => user.username, 'A' => session.aa}
+ assert_post(url, params, api_options) do |body, response|
+ cookie = response['Set-Cookie'].split(';').first
+ assert(response = JSON.parse(body), 'response should be JSON')
+ assert(session.bb = response["B"], 'response should include "B"')
+ url = api_url("/sessions/login.json")
+ params = {'client_auth' => session.m, 'A' => session.aa}
+ assert_put(url, params, api_options('Cookie' => cookie)) do |body|
+ assert(response = JSON.parse(body), 'response should be JSON')
+ assert(response['M2'], 'response should include M2')
+ user.session_token = response['token']
+ user.id = response['id']
+ assert(user.session_token, 'response should include token')
+ assert(user.id, 'response should include user id')
+ end
+ end
+ end
+
+ #
+ # attempts to destroy a user account via the API.
+ #
+ def assert_delete_user(user)
+ if user.is_a? String
+ assert_delete_user_by_login(user)
+ elsif user.is_a? SRP::User
+ assert_delete_srp_user(user)
+ end
+ end
+
+ #
+ # returns true if the identity exists, uses monitor token auth
+ #
+ def identity_exists?(address)
+ url = api_url("/identities/#{URI.encode(address)}.json")
+ options = {:ok_codes => [200, 404]}.merge(
+ api_options(:auth => :monitor)
+ )
+ assert_get(url, nil, options) do |body, response|
+ return response.code == "200"
+ end
+ end
+
+ def upload_public_key(user_id, public_key)
+ url = api_url("/users/#{user_id}.json")
+ params = {"user[public_key]" => public_key}
+ assert_put(url, params, api_options(:auth => :monitor))
+ end
+
+ #
+ # return user document as a Hash. uses monitor token auth
+ #
+ def find_user_by_id(user_id)
+ url = api_url("/users/#{user_id}.json")
+ assert_get(url, nil, api_options(:auth => :monitor)) do |body|
+ return JSON.parse(body)
+ end
+ end
+
+ #
+ # return user document as a Hash. uses monitor token auth
+ # NOTE: this relies on deprecated behavior of the API
+ # and will not work when multi-domain support is added.
+ #
+ def find_user_by_login(login)
+ url = api_url("/users/0.json?login=#{login}")
+ options = {:ok_codes => [200, 404]}.merge(
+ api_options(:auth => :monitor)
+ )
+ assert_get(url, nil, options) do |body, response|
+ if response.code == "200"
+ return JSON.parse(body)
+ else
+ return nil
+ end
+ end
+ end
+
+ private
+
+ def api_url(path)
+ unless path =~ /^\//
+ path = '/' + path
+ end
+ if property('testing.api_uri')
+ return property('testing.api_uri') + path
+ elsif property('api')
+ api = property('api')
+ return "https://%{domain}:%{port}/%{version}#{path}" % {
+ :domain => api['domain'],
+ :port => api['port'],
+ :version => api['version'] || 1
+ }
+ else
+ fail 'This node needs to have either testing.api_url or api.{domain,port} configured.'
+ end
+ end
+
+ #
+ # produces an options hash used for api http requests.
+ #
+ # argument options hash gets added to "headers"
+ # of the http request.
+ #
+ # special :auth key in argument will expand to
+ # add api_token_auth header.
+ #
+ # if you want to try manually:
+ #
+ # export API_URI=`grep api_uri /etc/leap/hiera.yaml | cut -d\" -f2`
+ # export TOKEN=`grep monitor_auth_token /etc/leap/hiera.yaml | awk '{print $2}'`
+ # curl -H "Accept: application/json" -H "Token: $TOKEN" $API_URI
+ #
+ def api_options(options={})
+ # note: must be :headers, not "headers"
+ hsh = {
+ :headers => {
+ "Accept" => "application/json"
+ }
+ }
+ if options[:auth]
+ hsh[:headers].merge!(api_token_auth(options.delete(:auth)))
+ end
+ hsh[:headers].merge!(options)
+ return hsh
+ end
+
+ #
+ # add token authentication to a http request.
+ #
+ # returns a hash suitable for adding to the 'headers' option
+ # of an http function.
+ #
+ def api_token_auth(token)
+ if token.is_a?(Symbol) && property('testing')
+ if token == :monitor
+ token_str = property('testing.monitor_auth_token')
+ else
+ raise ArgumentError.new 'no such token'
+ end
+ else
+ token_str = token
+ end
+ {"Authorization" => "Token token=\"#{token_str}\""}
+ end
+
+ #
+ # not actually used in any test, but useful when
+ # writing new tests.
+ #
+ def assert_delete_user_by_login(login_name)
+ user = find_user_by_login(login_name)
+ url = api_url("/users/#{user['id']}.json")
+ params = {:identities => 'destroy'}
+ delete(url, params, api_options(:auth => :monitor)) do |body, response, error|
+ assert error.nil?, "Error deleting user: #{error}"
+ assert response.code.to_i == 200, "Unable to delete user: HTTP response from API should have code 200, was #{response.code} #{error} #{body}"
+ assert(response = JSON.parse(body), 'Delete response should be JSON')
+ assert(response["success"], 'Deleting user should be a success')
+ end
+ end
+
+ def assert_delete_srp_user(user)
+ if user && user.ok && user.id && user.session_token && !user.deleted
+ url = api_url("users/#{user.id}.json")
+ params = {:identities => 'destroy'}
+ user.deleted = true
+ delete(url, params, api_options(:auth => user.session_token)) do |body, response, error|
+ assert error.nil?, "Error deleting user: #{error}"
+ assert response.code.to_i == 200, "Unable to delete user: HTTP response from API should have code 200, was #{response.code} #{error} #{body}"
+ assert(response = JSON.parse(body), 'Delete response should be JSON')
+ assert(response["success"], 'Deleting user should be a success')
+ end
+ end
+ end
+
+
+end
diff --git a/tests/helpers/client_side_db.py b/tests/helpers/client_side_db.py
new file mode 100644
index 00000000..2f8c220f
--- /dev/null
+++ b/tests/helpers/client_side_db.py
@@ -0,0 +1,167 @@
+import logging
+import os
+import tempfile
+import getpass
+import binascii
+import json
+
+try:
+ import requests
+ import srp._pysrp as srp
+except ImportError:
+ pass
+
+from twisted.internet.defer import inlineCallbacks
+
+from leap.soledad.client import Soledad
+
+
+"""
+Helper functions to give access to client-side Soledad database.
+Copied over from soledad/scripts folder.
+"""
+
+# create a logger
+logger = logging.getLogger(__name__)
+
+# DEBUG: enable debug logs
+# LOG_FORMAT = '%(asctime)s %(message)s'
+# logging.basicConfig(format=LOG_FORMAT, level=logging.DEBUG)
+
+
+safe_unhexlify = lambda x: binascii.unhexlify(x) if (
+ len(x) % 2 == 0) else binascii.unhexlify('0' + x)
+
+
+def _fail(reason):
+ logger.error('Fail: ' + reason)
+ exit(2)
+
+
+def get_soledad_instance(uuid, passphrase, basedir, server_url, cert_file,
+ token):
+ # setup soledad info
+ logger.info('UUID is %s' % uuid)
+ logger.info('Server URL is %s' % server_url)
+ secrets_path = os.path.join(
+ basedir, '%s.secret' % uuid)
+ local_db_path = os.path.join(
+ basedir, '%s.db' % uuid)
+ # instantiate soledad
+ return Soledad(
+ uuid,
+ unicode(passphrase),
+ secrets_path=secrets_path,
+ local_db_path=local_db_path,
+ server_url=server_url,
+ cert_file=cert_file,
+ auth_token=token,
+ defer_encryption=True)
+
+
+def _get_api_info(provider):
+ info = requests.get(
+ 'https://'+provider+'/provider.json', verify=False).json()
+ return info['api_uri'], info['api_version']
+
+
+def _login(username, passphrase, provider, api_uri, api_version):
+ usr = srp.User(username, passphrase, srp.SHA256, srp.NG_1024)
+ auth = None
+ try:
+ auth = _authenticate(api_uri, api_version, usr).json()
+ except requests.exceptions.ConnectionError:
+ _fail('Could not connect to server.')
+ if 'errors' in auth:
+ _fail(str(auth['errors']))
+ return api_uri, api_version, auth
+
+
+def _authenticate(api_uri, api_version, usr):
+ api_url = "%s/%s" % (api_uri, api_version)
+ session = requests.session()
+ uname, A = usr.start_authentication()
+ params = {'login': uname, 'A': binascii.hexlify(A)}
+ init = session.post(
+ api_url + '/sessions', data=params, verify=False).json()
+ if 'errors' in init:
+ _fail('test user not found')
+ M = usr.process_challenge(
+ safe_unhexlify(init['salt']), safe_unhexlify(init['B']))
+ return session.put(api_url + '/sessions/' + uname, verify=False,
+ data={'client_auth': binascii.hexlify(M)})
+
+
+def _get_soledad_info(username, provider, passphrase, basedir):
+ api_uri, api_version = _get_api_info(provider)
+ auth = _login(username, passphrase, provider, api_uri, api_version)
+ # get soledad server url
+ service_url = '%s/%s/config/soledad-service.json' % \
+ (api_uri, api_version)
+ soledad_hosts = requests.get(service_url, verify=False).json()['hosts']
+ hostnames = soledad_hosts.keys()
+ # allow for choosing the host
+ host = hostnames[0]
+ if len(hostnames) > 1:
+ i = 1
+ print "There are many available hosts:"
+ for h in hostnames:
+ print " (%d) %s.%s" % (i, h, provider)
+ i += 1
+ choice = raw_input("Choose a host to use (default: 1): ")
+ if choice != '':
+ host = hostnames[int(choice) - 1]
+ server_url = 'https://%s:%d/user-%s' % \
+ (soledad_hosts[host]['hostname'], soledad_hosts[host]['port'],
+ auth[2]['id'])
+ # get provider ca certificate
+ ca_cert = requests.get('https://%s/ca.crt' % provider, verify=False).text
+ cert_file = os.path.join(basedir, 'ca.crt')
+ with open(cert_file, 'w') as f:
+ f.write(ca_cert)
+ return auth[2]['id'], server_url, cert_file, auth[2]['token']
+
+
+def _get_passphrase(args):
+ passphrase = args.passphrase
+ if passphrase is None:
+ passphrase = getpass.getpass(
+ 'Password for %s@%s: ' % (args.username, args.provider))
+ return passphrase
+
+
+def _get_basedir(args):
+ basedir = args.basedir
+ if basedir is None:
+ basedir = tempfile.mkdtemp()
+ elif not os.path.isdir(basedir):
+ os.mkdir(basedir)
+ logger.info('Using %s as base directory.' % basedir)
+ return basedir
+
+
+@inlineCallbacks
+def _export_key(args, km, fname, private=False):
+ address = args.username + "@" + args.provider
+ pkey = yield km.get_key(
+ address, OpenPGPKey, private=private, fetch_remote=False)
+ with open(args.export_private_key, "w") as f:
+ f.write(pkey.key_data)
+
+
+@inlineCallbacks
+def _export_incoming_messages(soledad, directory):
+ yield soledad.create_index("by-incoming", "bool(incoming)")
+ docs = yield soledad.get_from_index("by-incoming", '1')
+ i = 1
+ for doc in docs:
+ with open(os.path.join(directory, "message_%d.gpg" % i), "w") as f:
+ f.write(doc.content["_enc_json"])
+ i += 1
+
+
+@inlineCallbacks
+def _get_all_docs(soledad):
+ _, docs = yield soledad.get_all_docs()
+ for doc in docs:
+ print json.dumps(doc.content, indent=4)
diff --git a/tests/helpers/couchdb_helper.rb b/tests/helpers/couchdb_helper.rb
new file mode 100644
index 00000000..b9085c1e
--- /dev/null
+++ b/tests/helpers/couchdb_helper.rb
@@ -0,0 +1,142 @@
+class LeapTest
+
+ #
+ # generates a couchdb url for when couchdb is running
+ # remotely and is available via stunnel.
+ #
+ # example properties:
+ #
+ # stunnel:
+ # clients:
+ # couch_client:
+ # couch1_5984:
+ # accept_port: 4000
+ # connect: couch1.bitmask.i
+ # connect_port: 15984
+ #
+ def couchdb_urls_via_stunnel(path="", options=nil)
+ path = path.gsub('"', '%22')
+ if options && options[:username] && options[:password]
+ userpart = "%{username}:%{password}@" % options
+ else
+ userpart = ""
+ end
+ assert_property('stunnel.clients.couch_client').values.collect do |stunnel_conf|
+ assert port = stunnel_conf['accept_port'], 'Field `accept_port` must be present in `stunnel` property.'
+ URLString.new("http://#{userpart}localhost:#{port}#{path}").tap {|url|
+ remote_ip_address = TCPSocket.gethostbyname(stunnel_conf['connect']).last
+ url.memo = "(via stunnel to %s:%s, aka %s)" % [stunnel_conf['connect'], stunnel_conf['connect_port'], remote_ip_address]
+ }
+ end
+ end
+
+ #
+ # generates a couchdb url for accessing couchdb via haproxy
+ #
+ # example properties:
+ #
+ # haproxy:
+ # couch:
+ # listen_port: 4096
+ # servers:
+ # panda:
+ # backup: false
+ # host: localhost
+ # port: 4000
+ # weight: 100
+ # writable: true
+ #
+ def couchdb_url_via_haproxy(path="", options=nil)
+ path = path.gsub('"', '%22')
+ if options && options[:username] && options[:password]
+ userpart = "%{username}:%{password}@" % options
+ else
+ userpart = ""
+ end
+ port = assert_property('haproxy.couch.listen_port')
+ return URLString.new("http://#{userpart}localhost:#{port}#{path}").tap { |url|
+ url.memo = '(via haproxy)'
+ }
+ end
+
+ #
+ # generates a couchdb url for when couchdb is running locally.
+ #
+ # example properties:
+ #
+ # couch:
+ # port: 5984
+ #
+ def couchdb_url_via_localhost(path="", options=nil)
+ path = path.gsub('"', '%22')
+ port = (options && options[:port]) || assert_property('couch.port')
+ if options && options[:username]
+ password = property("couch.users.%{username}.password" % options)
+ userpart = "%s:%s@" % [options[:username], password]
+ else
+ userpart = ""
+ end
+ return URLString.new("http://#{userpart}localhost:#{port}#{path}").tap { |url|
+ url.memo = '(via direct localhost connection)'
+ }
+ end
+
+ #
+ # returns a single url for accessing couchdb
+ #
+ def couchdb_url(path="", options=nil)
+ if property('couch.port')
+ couchdb_url_via_localhost(path, options)
+ elsif property('stunnel.clients.couch_client')
+ couchdb_urls_via_stunnel(path, options).first
+ end
+ end
+
+ #
+ # returns an array of urls for accessing couchdb
+ #
+ def couchdb_urls(path="", options=nil)
+ if property('couch.port')
+ [couchdb_url_via_localhost(path, options)]
+ elsif property('stunnel.clients.couch_client')
+ couchdb_urls_via_stunnel(path, options)
+ end
+ end
+
+ def assert_destroy_user_db(user_id, options=nil)
+ db_name = "user-#{user_id}"
+ url = couchdb_url("/#{db_name}", options)
+ http_options = {:ok_codes => [200, 404]} # ignore missing dbs
+ assert_delete(url, nil, http_options)
+ end
+
+ def assert_create_user_db(user_id, options=nil)
+ db_name = "user-#{user_id}"
+ url = couchdb_url("/#{db_name}", options)
+ http_options = {:ok_codes => [200, 404]} # ignore missing dbs
+ assert_put(url, nil, :format => :json) do |body|
+ assert response = JSON.parse(body), "PUT response should be JSON"
+ assert response["ok"], "PUT response should be OK"
+ end
+ end
+
+ #
+ # returns true if the per-user db created by soledad-server exists.
+ #
+ def user_db_exists?(user_id, options=nil)
+ db_name = "user-#{user_id}"
+ url = couchdb_url("/#{db_name}", options)
+ get(url) do |body, response, error|
+ if response.nil?
+ fail "could not query couchdb #{url}: #{error}\n#{body}"
+ elsif response.code.to_i == 200
+ return true
+ elsif response.code.to_i == 404
+ return false
+ else
+ fail ["could not query couchdb #{url}: expected response code 200 or 404, but got #{response.code}.", error, body].compact.join("\n")
+ end
+ end
+ end
+
+end \ No newline at end of file
diff --git a/tests/helpers/files_helper.rb b/tests/helpers/files_helper.rb
new file mode 100644
index 00000000..d6795889
--- /dev/null
+++ b/tests/helpers/files_helper.rb
@@ -0,0 +1,54 @@
+class LeapTest
+
+ #
+ # Matches the regexp in the file, and returns the first matched string (or fails if no match).
+ #
+ def file_match(filename, regexp)
+ if match = File.read(filename).match(regexp)
+ match.captures.first
+ else
+ fail "Regexp #{regexp.inspect} not found in file #{filename.inspect}."
+ end
+ end
+
+ #
+ # Matches the regexp in the file, and returns array of matched strings (or fails if no match).
+ #
+ def file_matches(filename, regexp)
+ if match = File.read(filename).match(regexp)
+ match.captures
+ else
+ fail "Regexp #{regexp.inspect} not found in file #{filename.inspect}."
+ end
+ end
+
+ #
+ # checks to make sure the given property path exists in $node (e.g. hiera.yaml)
+ # and returns the value
+ #
+ def assert_property(property)
+ latest = $node
+ property.split('.').each do |segment|
+ latest = latest[segment]
+ fail "Required node property `#{property}` is missing." if latest.nil?
+ end
+ return latest
+ end
+
+ #
+ # a handy function to get the value of a long property path
+ # without needing to test the existance individually of each part
+ # in the tree.
+ #
+ # e.g. property("stunnel.clients.couch_client")
+ #
+ def property(property)
+ latest = $node
+ property.split('.').each do |segment|
+ latest = latest[segment]
+ return nil if latest.nil?
+ end
+ return latest
+ end
+
+end \ No newline at end of file
diff --git a/tests/helpers/http_helper.rb b/tests/helpers/http_helper.rb
new file mode 100644
index 00000000..0d0bb7d5
--- /dev/null
+++ b/tests/helpers/http_helper.rb
@@ -0,0 +1,157 @@
+require 'net/http'
+
+class LeapTest
+
+ #
+ # In order to easily provide detailed error messages, it is useful
+ # to append a memo to a url string that details what this url is for
+ # (e.g. stunnel, haproxy, etc).
+ #
+ # So, the url happens to be a UrlString, the memo field is used
+ # if there is an error in assert_get.
+ #
+ class URLString < String
+ attr_accessor :memo
+ end
+
+ #
+ # aliases for http_send()
+ #
+ def get(url, params=nil, options=nil, &block)
+ http_send("GET", url, params, options, &block)
+ end
+ def delete(url, params=nil, options=nil, &block)
+ http_send("DELETE", url, params, options, &block)
+ end
+ def post(url, params=nil, options=nil, &block)
+ http_send("POST", url, params, options, &block)
+ end
+ def put(url, params=nil, options=nil, &block)
+ http_send("PUT", url, params, options, &block)
+ end
+
+ #
+ # send a GET, DELETE, POST, or PUT
+ # yields |body, response, error|
+ #
+ def http_send(method, url, params=nil, options=nil)
+ options ||= {}
+ response = nil
+
+ # build uri
+ uri = URI(url)
+ if params && (method == 'GET' || method == 'DELETE')
+ uri.query = URI.encode_www_form(params)
+ end
+
+ # build http
+ http = Net::HTTP.new uri.host, uri.port
+ if uri.scheme == 'https'
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ http.use_ssl = true
+ end
+
+ # build request
+ request = build_request(method, uri, params, options)
+
+ # make http request
+ http.start do |agent|
+ response = agent.request(request)
+ yield response.body, response, nil
+ end
+ rescue => exc
+ yield nil, response, exc
+ end
+
+ #
+ # Aliases for assert_http_send()
+ #
+ def assert_get(url, params=nil, options=nil, &block)
+ assert_http_send("GET", url, params, options, &block)
+ end
+ def assert_delete(url, params=nil, options=nil, &block)
+ assert_http_send("DELETE", url, params, options, &block)
+ end
+ def assert_post(url, params=nil, options=nil, &block)
+ assert_http_send("POST", url, params, options, &block)
+ end
+ def assert_put(url, params=nil, options=nil, &block)
+ assert_http_send("PUT", url, params, options, &block)
+ end
+
+ #
+ # calls http_send, yielding results if successful or failing with
+ # descriptive info otherwise.
+ #
+ # options:
+ # - error_msg: custom error message to display.
+ # - ok_codes: in addition to 2xx, codes in this array will not produce an error.
+ #
+ def assert_http_send(method, url, params=nil, options=nil, &block)
+ options ||= {}
+ error_msg = options[:error_msg] || (url.respond_to?(:memo) ? url.memo : nil)
+ http_send(method, url, params, options) do |body, response, error|
+ if response
+ code = response.code.to_i
+ ok = code >= 200 && code < 300
+ if options[:ok_codes]
+ ok ||= options[:ok_codes].include?(code)
+ end
+ if ok
+ if block
+ yield(body) if block.arity == 1
+ yield(body, response) if block.arity == 2
+ yield(body, response, error) if block.arity == 3
+ end
+ else
+ fail ["Expected success code from #{method} #{url}, but got #{response.code} instead.", error_msg, body].compact.join("\n")
+ end
+ else
+ fail ["Expected a response from #{method} #{url}, but got \"#{error}\" instead.", error_msg, body].compact.join("\n"), error
+ end
+ end
+ end
+
+ #
+ # only a warning for now, should be a failure in the future
+ #
+ def assert_auth_fail(url, params)
+ uri = URI(url)
+ get(url, params) do |body, response, error|
+ unless response.code.to_s == "401"
+ warn "Expected a '401 Unauthorized' response, but got #{response.code} instead (GET #{uri.request_uri} with username '#{uri.user}')."
+ return false
+ end
+ end
+ true
+ end
+
+ private
+
+ def build_request(method, uri, params, options)
+ request = case method
+ when "GET" then Net::HTTP::Get.new(uri.request_uri)
+ when "DELETE" then Net::HTTP::Delete.new(uri.request_uri)
+ when "POST" then Net::HTTP::Post.new(uri.request_uri)
+ when "PUT" then Net::HTTP::Put.new(uri.request_uri)
+ end
+ if uri.user
+ request.basic_auth uri.user, uri.password
+ end
+ if params && (method == 'POST' || method == 'PUT')
+ if options[:format] == :json || options[:format] == 'json'
+ request["Content-Type"] = "application/json"
+ request.body = params.to_json
+ else
+ request.set_form_data(params) if params
+ end
+ end
+ if options[:headers]
+ options[:headers].each do |key, value|
+ request[key] = value
+ end
+ end
+ request
+ end
+
+end \ No newline at end of file
diff --git a/tests/helpers/network_helper.rb b/tests/helpers/network_helper.rb
new file mode 100644
index 00000000..713d57aa
--- /dev/null
+++ b/tests/helpers/network_helper.rb
@@ -0,0 +1,79 @@
+class LeapTest
+
+ #
+ # tcp connection helper with timeout
+ #
+ def try_tcp_connect(host, port, timeout = 5)
+ addr = Socket.getaddrinfo(host, nil)
+ sockaddr = Socket.pack_sockaddr_in(port, addr[0][3])
+
+ Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0).tap do |socket|
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
+ begin
+ socket.connect_nonblock(sockaddr)
+ rescue IO::WaitReadable
+ if IO.select([socket], nil, nil, timeout) == nil
+ raise "Connection timeout"
+ else
+ socket.connect_nonblock(sockaddr)
+ end
+ rescue IO::WaitWritable
+ if IO.select(nil, [socket], nil, timeout) == nil
+ raise "Connection timeout"
+ else
+ socket.connect_nonblock(sockaddr)
+ end
+ end
+ return socket
+ end
+ end
+
+ def try_tcp_write(socket, timeout = 5)
+ begin
+ socket.write_nonblock("\0")
+ rescue IO::WaitReadable
+ if IO.select([socket], nil, nil, timeout) == nil
+ raise "Write timeout"
+ else
+ retry
+ end
+ rescue IO::WaitWritable
+ if IO.select(nil, [socket], nil, timeout) == nil
+ raise "Write timeout"
+ else
+ retry
+ end
+ end
+ end
+
+ def try_tcp_read(socket, timeout = 5)
+ begin
+ socket.read_nonblock(1)
+ rescue IO::WaitReadable
+ if IO.select([socket], nil, nil, timeout) == nil
+ raise "Read timeout"
+ else
+ retry
+ end
+ rescue IO::WaitWritable
+ if IO.select(nil, [socket], nil, timeout) == nil
+ raise "Read timeout"
+ else
+ retry
+ end
+ end
+ end
+
+ def assert_tcp_socket(host, port, msg=nil)
+ begin
+ socket = try_tcp_connect(host, port, 1)
+ #try_tcp_write(socket,1)
+ #try_tcp_read(socket,1)
+ rescue StandardError => exc
+ fail ["Failed to open socket #{host}:#{port}", exc, msg].compact.join("\n")
+ ensure
+ socket.close if socket
+ end
+ end
+
+end \ No newline at end of file
diff --git a/tests/helpers/os_helper.rb b/tests/helpers/os_helper.rb
new file mode 100644
index 00000000..9923d5b1
--- /dev/null
+++ b/tests/helpers/os_helper.rb
@@ -0,0 +1,41 @@
+class LeapTest
+
+ #
+ # works like pgrep command line
+ # return an array of hashes like so [{:pid => "1234", :process => "ls"}]
+ #
+ def pgrep(match)
+ output = `pgrep --full --list-name '#{match}'`
+ output.each_line.map{|line|
+ pid = line.split(' ')[0]
+ process = line.gsub(/(#{pid} |\n)/, '')
+ # filter out pgrep cmd itself
+ # on wheezy hosts, the "process" var contains the whole cmd including all parameters
+ # on jessie hosts, it only contains the first cmd (which is the default sheel invoked by 'sh')
+ if process =~ /^sh/
+ nil
+ else
+ {:pid => pid, :process => process}
+ end
+ }.compact
+ end
+
+ def assert_running(process, options={})
+ processes = pgrep(process)
+ assert processes.any?, "No running process for #{process}"
+ if options[:single]
+ assert processes.length == 1, "More than one process for #{process}"
+ end
+ end
+
+ #
+ # runs the specified command, failing on a non-zero exit status.
+ #
+ def assert_run(command)
+ output = `#{command} 2>&1`
+ if $?.exitstatus != 0
+ fail "Error running `#{command}`:\n#{output}"
+ end
+ end
+
+end \ No newline at end of file
diff --git a/tests/helpers/smtp_helper.rb b/tests/helpers/smtp_helper.rb
new file mode 100644
index 00000000..ea7fb9fa
--- /dev/null
+++ b/tests/helpers/smtp_helper.rb
@@ -0,0 +1,45 @@
+require 'net/smtp'
+
+class LeapTest
+
+ TEST_EMAIL_USER = "test_user_email"
+ TEST_BAD_USER = "test_user_bad"
+
+ MSG_BODY = %(Since it seems that any heart which beats for freedom has the right only to a
+lump of lead, I too claim my share. If you let me live, I shall never stop
+crying for revenge and I shall avenge my brothers. I have finished. If you are
+not cowards, kill me!
+
+--Louise Michel)
+
+ def send_email(recipient, options={})
+ sender = options[:sender] || recipient
+ helo_domain = property('domain.full_suffix')
+ headers = {
+ "Date" => Time.now.utc,
+ "From" => sender,
+ "To" => recipient,
+ "Subject" => "Test Message",
+ "X-LEAP-TEST" => "true"
+ }.merge(options[:headers]||{})
+ message = []
+ headers.each do |key, value|
+ message << "#{key}: #{value}"
+ end
+ message << ""
+ message << MSG_BODY
+ Net::SMTP.start('localhost', 25, helo_domain) do |smtp|
+ smtp.send_message message.join("\n"), recipient, sender
+ end
+ end
+
+ def assert_send_email(recipient, options={})
+ begin
+ send_email(recipient, options)
+ rescue IOError, Net::OpenTimeout,
+ Net::ReadTimeout, Net::SMTPError => e
+ fail "Could not send mail to #{recipient} (#{e})"
+ end
+ end
+
+end \ No newline at end of file
diff --git a/tests/helpers/soledad_sync.py b/tests/helpers/soledad_sync.py
new file mode 100755
index 00000000..f4fc81ae
--- /dev/null
+++ b/tests/helpers/soledad_sync.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python
+"""
+soledad_sync.py
+
+This script exercises soledad synchronization.
+Its exit code is 0 if the sync took place correctly, 1 otherwise.
+
+It takes 5 arguments:
+
+ uuid: uuid of the user to sync
+ token: a valid session token
+ server: the url of the soledad server we should connect to
+ cert_file: the file containing the certificate for the CA that signed the
+ cert for the soledad server.
+ password: the password for the user to sync
+
+__author__: kali@leap.se
+"""
+import os
+import shutil
+import sys
+import tempfile
+
+# This is needed because the twisted shipped with wheezy is too old
+# to do proper ssl verification.
+os.environ['SKIP_TWISTED_SSL_CHECK'] = '1'
+
+from twisted.internet import defer, reactor
+from twisted.python import log
+
+from client_side_db import get_soledad_instance
+from leap.common.events import flags
+
+flags.set_events_enabled(False)
+
+NUMDOCS = 1
+USAGE = "Usage: %s uuid token server cert_file password" % sys.argv[0]
+
+
+def bail(msg, exitcode):
+ print "[!] %s" % msg
+ sys.exit(exitcode)
+
+
+def create_docs(soledad):
+ """
+ Populates the soledad database with dummy messages, so we can exercise
+ sending payloads during the sync.
+ """
+ deferreds = []
+ for index in xrange(NUMDOCS):
+ deferreds.append(soledad.create_doc({'payload': 'dummy'}))
+ return defer.gatherResults(deferreds)
+
+# main program
+
+if __name__ == '__main__':
+
+ tempdir = tempfile.mkdtemp()
+
+ def rm_tempdir():
+ shutil.rmtree(tempdir)
+
+ if len(sys.argv) < 6:
+ bail(USAGE, 2)
+
+ uuid, token, server, cert_file, passphrase = sys.argv[1:]
+ s = get_soledad_instance(
+ uuid, passphrase, tempdir, server, cert_file, token)
+
+ def onSyncDone(sync_result):
+ print "SYNC_RESULT:", sync_result
+ s.close()
+ rm_tempdir()
+ reactor.stop()
+
+ def log_and_exit(f):
+ log.err(f)
+ rm_tempdir()
+ reactor.stop()
+
+ def start_sync():
+ d = create_docs(s)
+ d.addCallback(lambda _: s.sync())
+ d.addCallback(onSyncDone)
+ d.addErrback(log_and_exit)
+
+ reactor.callWhenRunning(start_sync)
+ reactor.run()
diff --git a/tests/helpers/srp_helper.rb b/tests/helpers/srp_helper.rb
new file mode 100644
index 00000000..b30fa768
--- /dev/null
+++ b/tests/helpers/srp_helper.rb
@@ -0,0 +1,171 @@
+#
+# Here are some very stripped down helper methods for SRP, useful only for
+# testing the client side.
+#
+
+require 'digest'
+require 'openssl'
+require 'securerandom'
+require 'base64'
+
+module SRP
+
+ ##
+ ## UTIL
+ ##
+
+ module Util
+ PRIME_N = <<-EOS.split.join.hex
+115b8b692e0e045692cf280b436735c77a5a9e8a9e7ed56c965f87db5b2a2ece3
+ EOS
+ BIG_PRIME_N = <<-EOS.split.join.hex # 1024 bits modulus (N)
+eeaf0ab9adb38dd69c33f80afa8fc5e86072618775ff3c0b9ea2314c9c25657
+6d674df7496ea81d3383b4813d692c6e0e0d5d8e250b98be48e495c1d6089da
+d15dc7d7b46154d6b6ce8ef4ad69b15d4982559b297bcf1885c529f566660e5
+7ec68edbc3c05726cc02fd4cbf4976eaa9afd5138fe8376435b9fc61d2fc0eb
+06e3
+ EOS
+ GENERATOR = 2 # g
+
+ def hn_xor_hg
+ byte_xor_hex(sha256_int(BIG_PRIME_N), sha256_int(GENERATOR))
+ end
+
+ # a^n (mod m)
+ def modpow(a, n, m = BIG_PRIME_N)
+ r = 1
+ while true
+ r = r * a % m if n[0] == 1
+ n >>= 1
+ return r if n == 0
+ a = a * a % m
+ end
+ end
+
+ # Hashes the (long) int args
+ def sha256_int(*args)
+ sha256_hex(*args.map{|a| "%02x" % a})
+ end
+
+ # Hashes the hex args
+ def sha256_hex(*args)
+ h = args.map{|a| a.length.odd? ? "0#{a}" : a }.join('')
+ sha256_str([h].pack('H*'))
+ end
+
+ def sha256_str(s)
+ Digest::SHA2.hexdigest(s)
+ end
+
+ def bigrand(bytes)
+ OpenSSL::Random.random_bytes(bytes).unpack("H*")[0]
+ end
+
+ def multiplier
+ @muliplier ||= calculate_multiplier
+ end
+
+ protected
+
+ def calculate_multiplier
+ sha256_int(BIG_PRIME_N, GENERATOR).hex
+ end
+
+ def byte_xor_hex(a, b)
+ a = [a].pack('H*')
+ b = [b].pack('H*')
+ a.bytes.each_with_index.map do |a_byte, i|
+ (a_byte ^ (b[i].ord || 0)).chr
+ end.join
+ end
+ end
+
+ ##
+ ## SESSION
+ ##
+
+ class Session
+ include SRP::Util
+ attr_accessor :user
+ attr_accessor :bb
+
+ def initialize(user, aa=nil)
+ @user = user
+ @a = bigrand(32).hex
+ end
+
+ def m
+ @m ||= sha256_hex(n_xor_g_long, login_hash, @user.salt.to_s(16), aa, bb, k)
+ end
+
+ def aa
+ @aa ||= modpow(GENERATOR, @a).to_s(16) # A = g^a (mod N)
+ end
+
+ protected
+
+ # client: K = H( (B - kg^x) ^ (a + ux) )
+ def client_secret
+ base = bb.hex
+ base -= modpow(GENERATOR, @user.private_key) * multiplier
+ base = base % BIG_PRIME_N
+ modpow(base, @user.private_key * u.hex + @a)
+ end
+
+ def k
+ @k ||= sha256_int(client_secret)
+ end
+
+ def n_xor_g_long
+ @n_xor_g_long ||= hn_xor_hg.bytes.map{|b| "%02x" % b.ord}.join
+ end
+
+ def login_hash
+ @login_hash ||= sha256_str(@user.username)
+ end
+
+ def u
+ @u ||= sha256_hex(aa, bb)
+ end
+ end
+
+ ##
+ ## Dummy USER
+ ##
+
+ class User
+ include SRP::Util
+
+ attr_accessor :username, :password, :salt, :verifier, :id, :session_token, :ok, :deleted
+
+ def initialize(username=nil)
+ @username = username || "tmp_user_" + SecureRandom.urlsafe_base64(10).downcase.gsub(/[_-]/, '')
+ @password = "password_" + SecureRandom.urlsafe_base64(10)
+ @salt = bigrand(4).hex
+ @verifier = modpow(GENERATOR, private_key)
+ @ok = false
+ @deleted = false
+ end
+
+ def private_key
+ @private_key ||= calculate_private_key
+ end
+
+ def to_params
+ {
+ 'user[login]' => @username,
+ 'user[password_verifier]' => @verifier.to_s(16),
+ 'user[password_salt]' => @salt.to_s(16)
+ }
+ end
+
+ private
+
+ def calculate_private_key
+ shex = '%x' % [@salt]
+ inner = sha256_str([@username, @password].join(':'))
+ sha256_hex(shex, inner).hex
+ end
+ end
+
+end
diff --git a/tests/order.rb b/tests/order.rb
new file mode 100644
index 00000000..14aad9be
--- /dev/null
+++ b/tests/order.rb
@@ -0,0 +1,22 @@
+class LeapCli::Config::Node
+ #
+ # returns a list of node names that should be tested before this node.
+ # make sure to not return ourselves (please no dependency loops!).
+ #
+ # NOTE: this method determines the order that nodes are tested in. To specify
+ # the order of tests on a particular node, each test can call class method
+ # LeapTest.depends_on().
+ #
+ def test_dependencies
+ dependents = LeapCli::Config::ObjectList.new
+
+ # webapp, mx, and soledad depend on couchdb nodes
+ if services.include?('webapp') || services.include?('mx') || services.include?('soledad')
+ if !services.include?('couchdb')
+ dependents.merge! nodes_like_me[:services => 'couchdb']
+ end
+ end
+
+ dependents.keys.delete_if {|name| self.name == name}
+ end
+end \ No newline at end of file
diff --git a/tests/white-box/couchdb.rb b/tests/white-box/couchdb.rb
new file mode 100644
index 00000000..85dc6840
--- /dev/null
+++ b/tests/white-box/couchdb.rb
@@ -0,0 +1,186 @@
+raise SkipTest unless service?(:couchdb)
+
+require 'json'
+
+class CouchDB < LeapTest
+ depends_on "Network"
+
+ def setup
+ end
+
+ def test_00_Are_daemons_running?
+ assert_running 'bin/beam'
+ if multimaster?
+ assert_running 'bin/epmd'
+ end
+ pass
+ end
+
+ #
+ # check to make sure we can get welcome response from local couchdb
+ #
+ def test_01_Is_CouchDB_running?
+ assert_get(couchdb_url) do |body|
+ assert_match /"couchdb":"Welcome"/, body, "Could not get welcome message from #{couchdb_url}. Probably couchdb is not running."
+ end
+ pass
+ end
+
+ #
+ # compare the configured nodes to the nodes that are actually listed in bigcouch
+ #
+ def test_02_Is_cluster_membership_ok?
+ return unless multimaster?
+ url = couchdb_backend_url("/nodes/_all_docs")
+ neighbors = assert_property('couch.bigcouch.neighbors')
+ neighbors << assert_property('domain.full')
+ neighbors.sort!
+ assert_get(url) do |body|
+ response = JSON.parse(body)
+ nodes_in_db = response['rows'].collect{|row| row['id'].sub(/^bigcouch@/, '')}.sort
+ assert_equal neighbors, nodes_in_db, "The couchdb replication node list is wrong (/nodes/_all_docs)"
+ end
+ pass
+ end
+
+ #
+ # all configured nodes are in 'cluster_nodes'
+ # all nodes online and communicating are in 'all_nodes'
+ #
+ # this seems backward to me, so it might be the other way around.
+ #
+ def test_03_Are_configured_nodes_online?
+ return unless multimaster?
+ url = couchdb_url("/_membership", :username => 'admin')
+ assert_get(url) do |body|
+ response = JSON.parse(body)
+ nodes_configured_but_not_available = response['cluster_nodes'] - response['all_nodes']
+ nodes_available_but_not_configured = response['all_nodes'] - response['cluster_nodes']
+ if nodes_configured_but_not_available.any?
+ warn "These nodes are configured but not available:", nodes_configured_but_not_available
+ end
+ if nodes_available_but_not_configured.any?
+ warn "These nodes are available but not configured:", nodes_available_but_not_configured
+ end
+ if response['cluster_nodes'] == response['all_nodes']
+ pass
+ end
+ end
+ end
+
+ def test_04_Do_ACL_users_exist?
+ acl_users = ['_design/_auth', 'leap_mx', 'nickserver', 'soledad', 'webapp', 'replication']
+ url = couchdb_backend_url("/_users/_all_docs", :username => 'admin')
+ assert_get(url) do |body|
+ response = JSON.parse(body)
+ assert_equal acl_users.count, response['total_rows']
+ actual_users = response['rows'].map{|row| row['id'].sub(/^org.couchdb.user:/, '') }
+ assert_equal acl_users.sort, actual_users.sort
+ end
+ pass
+ end
+
+ def test_05_Do_required_databases_exist?
+ dbs_that_should_exist = ["customers","identities","keycache","shared","tickets","users", "tmp_users"]
+ dbs_that_should_exist << "tokens_#{rotation_suffix}"
+ dbs_that_should_exist << "sessions_#{rotation_suffix}"
+ dbs_that_should_exist.each do |db_name|
+ url = couchdb_url("/"+db_name, :username => 'admin')
+ assert_get(url) do |body|
+ assert response = JSON.parse(body)
+ assert_equal db_name, response['db_name']
+ end
+ end
+ pass
+ end
+
+ # disable ACL enforcement, because it's a known issue with bigcouch
+ # and will only confuse the user
+ # see https://leap.se/code/issues/6030 for more details
+ #
+ ## for now, this just prints warnings, since we are failing these tests.
+ ##
+
+ #def test_06_Is_ACL_enforced?
+ # ok = assert_auth_fail(
+ # couchdb_url('/users/_all_docs', :username => 'leap_mx'),
+ # {:limit => 1}
+ # )
+ # ok = assert_auth_fail(
+ # couchdb_url('/users/_all_docs', :username => 'leap_mx'),
+ # {:limit => 1}
+ # ) && ok
+ # pass if ok
+ #end
+
+ def test_07_Can_records_be_created?
+ record = DummyRecord.new
+ url = couchdb_url("/tokens_#{rotation_suffix}", :username => 'admin')
+ assert_post(url, record, :format => :json) do |body|
+ assert response = JSON.parse(body), "POST response should be JSON"
+ assert response["ok"], "POST response should be OK"
+ assert_delete(File.join(url, response["id"]), :rev => response["rev"]) do |body|
+ assert response = JSON.parse(body), "DELETE response should be JSON"
+ assert response["ok"], "DELETE response should be OK"
+ end
+ end
+ pass
+ end
+
+ #
+ # This is not really a "test", just an attempt to make sure that
+ # the mx tests that fire off dummy emails don't fill up the
+ # storage db.
+ #
+ # mx tests can't run this because they don't have access to
+ # the storage db.
+ #
+ # This "test" is responsible for both creating the db if it does not
+ # exist, and destroying if it does.
+ #
+ # Yes, this is super hacky. Properly, we should add something to
+ # the soledad api to support create/delete of user storage dbs.
+ #
+ def test_99_Delete_mail_storage_used_in_mx_tests
+ user = find_user_by_login(TEST_EMAIL_USER)
+ if user
+ if user_db_exists?(user["id"])
+ # keep the test email db from filling up:
+ assert_destroy_user_db(user["id"], :username => 'admin')
+ end
+ # either way, make sure we leave a db for the mx tests:
+ assert_create_user_db(user["id"], :username => 'admin')
+ end
+ silent_pass
+ end
+
+ private
+
+ def multimaster?
+ mode == "multimaster"
+ end
+
+ def mode
+ assert_property('couch.mode')
+ end
+
+ # TODO: admin port is hardcoded for now but should be configurable.
+ def couchdb_backend_url(path="", options={})
+ options = {port: multimaster? && "5986"}.merge options
+ couchdb_url(path, options)
+ end
+
+ def rotation_suffix
+ rotation_suffix = Time.now.utc.to_i / 2592000 # monthly
+ end
+
+ require 'securerandom'
+ require 'digest/sha2'
+ class DummyRecord < Hash
+ def initialize
+ self['data'] = SecureRandom.urlsafe_base64(32).gsub(/^_*/, '')
+ self['_id'] = Digest::SHA512.hexdigest(self['data'])
+ end
+ end
+
+end
diff --git a/tests/white-box/dummy.rb b/tests/white-box/dummy.rb
new file mode 100644
index 00000000..a3e8ad68
--- /dev/null
+++ b/tests/white-box/dummy.rb
@@ -0,0 +1,71 @@
+# only run in the dummy case where there is no hiera.yaml file.
+raise SkipTest unless $node["dummy"]
+
+class Robot
+ def can_shoot_lasers?
+ "OHAI!"
+ end
+
+ def can_fly?
+ "YES!"
+ end
+end
+
+class TestDummy < LeapTest
+ def setup
+ @robot = Robot.new
+ end
+
+ def test_lasers
+ assert_equal "OHAI!", @robot.can_shoot_lasers?
+ pass
+ end
+
+ def test_fly
+ refute_match /^no/i, @robot.can_fly?
+ pass
+ end
+
+ def test_fail
+ fail "fail"
+ pass
+ end
+
+ def test_01_will_be_skipped
+ skip "test this later"
+ pass
+ end
+
+ def test_socket_failure
+ assert_tcp_socket('localhost', 900000)
+ pass
+ end
+
+ def test_warn
+ block_test do
+ warn "not everything", "is a success or failure"
+ end
+ end
+
+ # used to test extracting the proper caller even when in a block
+ def block_test
+ yield
+ end
+
+ def test_socket_success
+ fork {
+ Socket.tcp_server_loop('localhost', 12345) do |sock, client_addrinfo|
+ begin
+ sock.write('hi')
+ ensure
+ sock.close
+ exit
+ end
+ end
+ }
+ sleep 0.2
+ assert_tcp_socket('localhost', 12345)
+ pass
+ end
+
+end
diff --git a/tests/white-box/mx.rb b/tests/white-box/mx.rb
new file mode 100644
index 00000000..e0cb273a
--- /dev/null
+++ b/tests/white-box/mx.rb
@@ -0,0 +1,267 @@
+raise SkipTest unless service?(:mx)
+
+require 'date'
+require 'json'
+require 'net/smtp'
+
+class Mx < LeapTest
+ depends_on "Network"
+ depends_on "Webapp" if service?(:webapp)
+
+ def setup
+ end
+
+ def test_01_Can_contact_couchdb?
+ dbs = ["identities"]
+ dbs.each do |db_name|
+ couchdb_urls("/"+db_name, couch_url_options).each do |url|
+ assert_get(url) do |body|
+ assert response = JSON.parse(body)
+ assert_equal db_name, response['db_name']
+ end
+ end
+ end
+ pass
+ end
+
+ def test_02_Can_contact_couchdb_via_haproxy?
+ if property('haproxy.couch')
+ url = couchdb_url_via_haproxy("", couch_url_options)
+ assert_get(url) do |body|
+ assert_match /"couchdb":"Welcome"/, body, "Request to #{url} should return couchdb welcome message."
+ end
+ pass
+ end
+ end
+
+ #
+ # this test picks a random identity document, then queries
+ # using the by_address view for that same document again.
+ #
+ def test_03_Can_query_identities_db?
+ ident = pick_random_identity
+ address = ident['address']
+ url_base = %(/identities/_design/Identity/_view/by_address)
+ params = %(?include_docs=true&reduce=false&startkey="#{address}"&endkey="#{address}")
+ assert_get(couchdb_url(url_base+params, couch_url_options)) do |body|
+ assert response = JSON.parse(body)
+ assert record = response['rows'].first
+ assert_equal address, record['doc']['address']
+ pass
+ end
+ end
+
+ def test_04_Are_MX_daemons_running?
+ assert_running '.*/usr/bin/twistd.*mx.tac'
+ assert_running '^/usr/lib/postfix/master$'
+ assert_running '^/usr/sbin/postfwd'
+ assert_running 'postfwd2::cache$'
+ assert_running 'postfwd2::policy$'
+ assert_running '^/usr/sbin/unbound$'
+ assert_running '^/usr/bin/freshclam'
+ assert_running '^/usr/sbin/opendkim'
+ if Dir.glob("/var/lib/clamav/main.{c[vl]d,inc}").size > 0 and Dir.glob("/var/lib/clamav/daily.{c[vl]d,inc}").size > 0
+ assert_running '^/usr/sbin/clamd'
+ assert_running '^/usr/sbin/clamav-milter'
+ else
+ skip "Downloading the clamav signature files (/var/lib/clamav/{daily,main}.{c[vl]d,inc}) is still in progress, so clamd is not running.\nDon't worry, mail delivery will work without clamav. The download should finish soon."
+ end
+ pass
+ end
+
+ #
+ # TODO: test to make sure postmap returned the right result
+ #
+ def test_05_Can_postfix_query_leapmx?
+ ident = pick_random_identity(10, :with_public_key => true)
+ address = ident["address"]
+
+ #
+ # virtual alias map:
+ #
+ # user@domain => 41c29a80a44f4775513c64ac9cab91b9@deliver.local
+ #
+ assert_run("postmap -v -q \"#{address}\" tcp:localhost:4242")
+
+ #
+ # recipient access map:
+ #
+ # user@domain => [OK|REJECT|TEMP_FAIL]
+ #
+ # This map is queried by the mail server before delivery to the mail spool
+ # directory, and should check if the address is able to receive messages.
+ # Examples of reasons for denying delivery would be that the user is out of
+ # quota, is user, or have no pgp public key in the server.
+ #
+ # NOTE: in the future, when we support quota, we need to make sure that
+ # we don't randomly pick a user for this test that happens to be over quota.
+ #
+ assert_run("postmap -v -q \"#{address}\" tcp:localhost:2244")
+
+ #
+ # certificate validity map:
+ #
+ # fa:2a:70:1f:d8:16:4e:1a:3b:15:c1:67:00:f0 => [200|500]
+ #
+ # Determines whether a particular SMTP client cert is authorized
+ # to relay mail, based on the fingerprint.
+ #
+ if ident["cert_fingerprints"]
+ not_expired = ident["cert_fingerprints"].select {|key, value|
+ Time.now.utc < DateTime.strptime("2016-01-03", "%F").to_time.utc
+ }
+ if not_expired.any?
+ fingerprint = not_expired.first
+ assert_run("postmap -v -q #{fingerprint} tcp:localhost:2424")
+ end
+ end
+
+ pass
+ end
+
+ #
+ # The email sent by this test might get bounced back.
+ # In this case, the test will pass, but the bounce message will
+ # get sent to root, so the sysadmin will still figure out pretty
+ # quickly that something is wrong.
+ #
+ def test_06_Can_deliver_email?
+ addr = [TEST_EMAIL_USER, property('domain.full_suffix')].join('@')
+ bad_addr = [TEST_BAD_USER, property('domain.full_suffix')].join('@')
+
+ assert !identity_exists?(bad_addr), "the address #{bad_addr} must not exist."
+ if !identity_exists?(addr)
+ user = assert_create_user(TEST_EMAIL_USER, :monitor)
+ upload_public_key(user.id, TEST_EMAIL_PUBLIC_KEY)
+ end
+ assert identity_exists?(addr), "The identity #{addr} should have been created, but it doesn't exist yet."
+ assert_send_email(addr)
+ assert_raises(Net::SMTPError) do
+ send_email(bad_addr)
+ end
+ pass
+ end
+
+ private
+
+ def couch_url_options
+ {
+ :username => property('couchdb_leap_mx_user.username'),
+ :password => property('couchdb_leap_mx_user.password')
+ }
+ end
+
+ #
+ # returns a random identity record that also has valid address
+ # and destination fields.
+ #
+ # options:
+ #
+ # * :with_public_key -- searches only for identities with public keys
+ #
+ # note to self: for debugging, here is the curl you want:
+ # curl --netrc "127.0.0.1:5984/identities/_design/Identity/_view/by_address?startkey=\"xxxx@leap.se\"&endkey=\"xxxx@leap.se\"&reduce=false&include_docs=true"
+ #
+ def pick_random_identity(tries=5, options={})
+ assert_get(couchdb_url("/identities", couch_url_options)) do |body|
+ assert response = JSON.parse(body)
+ doc_count = response['doc_count'].to_i
+ if doc_count <= 1
+ # the design document counts as one document.
+ skip "There are no identity documents yet."
+ else
+ # try repeatedly to get a valid doc
+ for i in 1..tries
+ offset = rand(doc_count) # pick a random document
+ url = couchdb_url("/identities/_all_docs?include_docs=true&limit=1&skip=#{offset}", couch_url_options)
+ assert_get(url) do |body|
+ assert response = JSON.parse(body)
+ record = response['rows'].first
+ if record['id'] =~ /_design/
+ next
+ elsif record['doc'] && record['doc']['address']
+ next if record['doc']['destination'].nil? || record['doc']['destination'].empty?
+ next if options[:with_public_key] && !record_has_key?(record)
+ return record['doc']
+ else
+ fail "Identity document #{record['id']} is missing an address field. #{record['doc'].inspect}"
+ end
+ end
+ end
+ if options[:with_public_key]
+ skip "Could not find an Identity document with a public key for testing."
+ else
+ fail "Failed to find a valid Identity document (with address and destination)."
+ end
+ end
+ end
+ end
+
+ def record_has_key?(record)
+ !record['doc']['keys'].nil? &&
+ !record['doc']['keys'].empty? &&
+ !record['doc']['keys']['pgp'].nil? &&
+ !record['doc']['keys']['pgp'].empty?
+ end
+
+ TEST_EMAIL_PUBLIC_KEY=<<HERE
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+mI0EVvzIKQEEAN4f8FOGntJGTTD+fFUQS6y/ihn6tYLtyGZZbCOd0t/9kHt/raoR
+xEUks8rCOPMqHX+yeHsvDBtDyZYTvyhtfuWrBUbYGW+QZ4Pdvo+7NyLHPW0dKsCB
+Czrx7pxqpq1oq+LpUFqpSfjJTfYaGVDNXrPK144a7Rox2+MCbgq3twnFABEBAAG0
+EiA8dGVzdF91c2VyX2VtYWlsPoi4BBMBAgAiBQJW/MgpAhsvBgsJCAcDAgYVCAIJ
+CgsEFgIDAQIeAQIXgAAKCRAqYf65XmeSk0orBADUXjEiGnjzyBpXqaiVmJr4MyfP
+IfKTK4a+4qvR+2fseD7hteF98m26i1YRI5omLp4/MnxGSpgKFKIuWIdkEiLg7IJc
+pFZVdoDVufEtzbj9gmOHlnteksbCtuESyB0Hytsba4uS9afcTJdGiPNMHeniI/SY
+UKcCcIrQmpNIoOA5OLiNBFb8yCkBBAC+WMUQ+FC6GQ+pyaWlwTRsBAT4+Tp8w9jD
+7PK4xeEmVZDirP0VkW18UeQEueWJ63ia7wIGf1WyVH1tbvgVyRLsjT2cpKo8c6Ok
+NkhfGfjTnUJPeBNy8734UDIdqZLXJl0z6Z1R0CfOjBqvV25kWUvMkz/NEgZBhE+c
+m3JuZy1k7QARAQABiQE9BBgBAgAJBQJW/MgpAhsuAKgJECph/rleZ5KTnSAEGQEC
+AAYFAlb8yCkACgkQsJSYitQUOv4w1wQAn3atI5EsmRyw6iC6UVWWJv/lKi1Priyt
+DsrdH5xUmHUgp6VU8Pw9Y6G+sv50KLfbVQ1l+8/3B71TjadsOxh+PBPsEyYpK6WX
+TVGy44IDvFWGyOod8tmfcFN9IpU5DmSk/vny9G7RK/nbnta2VnfZOzwm5i3cNkPr
+FGPL1z0K3qs0VwP+M7BXdqBRSFDDBpG1J0TrZioEjvKeOsT/Ul8mbVt7HQpcN93I
+wTO4uky0Woy2nb7SbTQw6wOpU54u7+5dSQ03ltUHg1owy6Y3CMOeFL+e9ALpAZAU
+aMwY7zMFhqlPVZZMfdMLRsdLin67RIM+OJ6A925AM52bEQT1YwkQlP4mvQY=
+=qclE
+-----END PGP PUBLIC KEY BLOCK-----
+HERE
+
+ TEST_EMAIL_PRIVATE_KEY = <<HERE
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+lQHYBFb8yCkBBADeH/BThp7SRk0w/nxVEEusv4oZ+rWC7chmWWwjndLf/ZB7f62q
+EcRFJLPKwjjzKh1/snh7LwwbQ8mWE78obX7lqwVG2BlvkGeD3b6Puzcixz1tHSrA
+gQs68e6caqataKvi6VBaqUn4yU32GhlQzV6zyteOGu0aMdvjAm4Kt7cJxQARAQAB
+AAP8DTFfcE6UG1AioJDU6KZ9oCaGONHLuxmNaArSofDrR/ODA9rLAUlp22N5LEdJ
+46NyOhXrEwHx2aK2k+vbVDbgrP4ZTH7GxIK/2KzmH4zX0fWUNsaRy94Q12lJegXH
+sH2Im8Jjxu16YwGgFNTX1fCPqLB6WdQpf1796s6+/3PnCDcCAOXTCul3N7V5Yl+9
+N2Anupn+qNDXKT/kiKIZLHsMbo7EriGWReG3lLj1cOJPC6Nf0uOEri4ErSjFEadR
+F2TNITsCAPdsZjc5RGppUXyBfxhQkAnZ0r+UT2meCH3g3EVh3W9SBrXNhwipNpW3
+bPzRjUCDtmA8EOvd93oPCZv4/tb50P8B/jC+QIZ3GncP1CFPSVDoIZ7OUU5M1330
+DP77vG1GxeQvYO/hlxL5/KdtTR6m5zlIuooDxUaNJz1w5/oVjlG3NZKpl7QSIDx0
+ZXN0X3VzZXJfZW1haWw+iLgEEwECACIFAlb8yCkCGy8GCwkIBwMCBhUIAgkKCwQW
+AgMBAh4BAheAAAoJECph/rleZ5KTSisEANReMSIaePPIGlepqJWYmvgzJ88h8pMr
+hr7iq9H7Z+x4PuG14X3ybbqLVhEjmiYunj8yfEZKmAoUoi5Yh2QSIuDsglykVlV2
+gNW58S3NuP2CY4eWe16SxsK24RLIHQfK2xtri5L1p9xMl0aI80wd6eIj9JhQpwJw
+itCak0ig4Dk4nQHYBFb8yCkBBAC+WMUQ+FC6GQ+pyaWlwTRsBAT4+Tp8w9jD7PK4
+xeEmVZDirP0VkW18UeQEueWJ63ia7wIGf1WyVH1tbvgVyRLsjT2cpKo8c6OkNkhf
+GfjTnUJPeBNy8734UDIdqZLXJl0z6Z1R0CfOjBqvV25kWUvMkz/NEgZBhE+cm3Ju
+Zy1k7QARAQABAAP9HrUaGvdpqTwVx3cHyXUhId6GzCuuKyaP4mZoGeBCcaQS2vQR
+YtiykwBwX/AlfwSFJmmHKB6EErWIA+QyaEFR/fO56cHD2TY3Ql0BGcuHIx3+9pkp
+biPBZdiiGz7oa6k6GWsbKSksqwV8poSXV7qbn+Bjm2xCM4VnjNZIrFtL7fkCAMOf
+e9yHBFoXfc175bkNXEUXrNS34kv2ODAlx6KyY+PS77D+nprpHpGCnLn77G+xH1Xi
+qvX1Dr/iSQU5Tzsd+tcCAPkYZulaC/9itwme7wIT3ur+mdqMHymsCzv9193iLgjJ
+9t7fARo18yB845hI9Xv7TwRcoyuSpfvuM05rCMRzydsCAOI1MZeKtZSogXVa9QTX
+sVGZeCkrujSVOgsA3w48OLc2OrwZskDfx5QHfeJnumjQLut5qsnZ+1onj9P2dGdn
+JaChe4kBPQQYAQIACQUCVvzIKQIbLgCoCRAqYf65XmeSk50gBBkBAgAGBQJW/Mgp
+AAoJELCUmIrUFDr+MNcEAJ92rSORLJkcsOogulFVlib/5SotT64srQ7K3R+cVJh1
+IKelVPD8PWOhvrL+dCi321UNZfvP9we9U42nbDsYfjwT7BMmKSull01RsuOCA7xV
+hsjqHfLZn3BTfSKVOQ5kpP758vRu0Sv5257WtlZ32Ts8JuYt3DZD6xRjy9c9Ct6r
+NFcD/jOwV3agUUhQwwaRtSdE62YqBI7ynjrE/1JfJm1bex0KXDfdyMEzuLpMtFqM
+tp2+0m00MOsDqVOeLu/uXUkNN5bVB4NaMMumNwjDnhS/nvQC6QGQFGjMGO8zBYap
+T1WWTH3TC0bHS4p+u0SDPjiegPduQDOdmxEE9WMJEJT+Jr0G
+=hvJM
+-----END PGP PRIVATE KEY BLOCK-----
+HERE
+
+end
diff --git a/tests/white-box/network.rb b/tests/white-box/network.rb
new file mode 100644
index 00000000..436fc8a8
--- /dev/null
+++ b/tests/white-box/network.rb
@@ -0,0 +1,90 @@
+require 'socket'
+require 'openssl'
+
+raise SkipTest if $node["dummy"]
+
+class Network < LeapTest
+
+ def setup
+ end
+
+ def test_01_Can_connect_to_internet?
+ assert_get('http://www.google.com/images/srpr/logo11w.png')
+ pass
+ end
+
+ #
+ # example properties:
+ #
+ # stunnel:
+ # ednp_clients:
+ # elk_9002:
+ # accept_port: 4003
+ # connect: elk.dev.bitmask.i
+ # connect_port: 19002
+ # couch_server:
+ # accept: 15984
+ # connect: "127.0.0.1:5984"
+ #
+ def test_02_Is_stunnel_running?
+ ignore unless $node['stunnel']
+ good_stunnel_pids = []
+ release = `facter lsbmajdistrelease`
+ if release.to_i > 7
+ # on jessie, there is only one stunnel proc running instead of 6
+ expected = 1
+ else
+ expected = 6
+ end
+ $node['stunnel']['clients'].each do |stunnel_type, stunnel_configs|
+ stunnel_configs.each do |stunnel_name, stunnel_conf|
+ config_file_name = "/etc/stunnel/#{stunnel_name}.conf"
+ processes = pgrep(config_file_name)
+ assert_equal expected, processes.length, "There should be #{expected} stunnel processes running for `#{config_file_name}`"
+ good_stunnel_pids += processes.map{|ps| ps[:pid]}
+ assert port = stunnel_conf['accept_port'], 'Field `accept_port` must be present in `stunnel` property.'
+ assert_tcp_socket('localhost', port)
+ end
+ end
+ $node['stunnel']['servers'].each do |stunnel_name, stunnel_conf|
+ config_file_name = "/etc/stunnel/#{stunnel_name}.conf"
+ processes = pgrep(config_file_name)
+ assert_equal expected, processes.length, "There should be #{expected} stunnel processes running for `#{config_file_name}`"
+ good_stunnel_pids += processes.map{|ps| ps[:pid]}
+ assert accept_port = stunnel_conf['accept_port'], "Field `accept` must be present in property `stunnel.servers.#{stunnel_name}`"
+ assert_tcp_socket('localhost', accept_port)
+ assert connect_port = stunnel_conf['connect_port'], "Field `connect` must be present in property `stunnel.servers.#{stunnel_name}`"
+ assert_tcp_socket('localhost', connect_port,
+ "The local connect endpoint for stunnel `#{stunnel_name}` is unavailable.\n"+
+ "This is probably caused by a daemon that died or failed to start on\n"+
+ "port `#{connect_port}`, not stunnel itself.")
+ end
+ all_stunnel_pids = pgrep('/usr/bin/stunnel').collect{|process| process[:pid]}.uniq
+ assert_equal good_stunnel_pids.sort, all_stunnel_pids.sort, "There should not be any extra stunnel processes that are not configured in /etc/stunnel"
+ pass
+ end
+
+ def test_03_Is_shorewall_running?
+ ignore unless File.exists?('/sbin/shorewall')
+ assert_run('/sbin/shorewall status')
+ pass
+ end
+
+ THIRTY_DAYS = 60*60*24*30
+
+ def test_04_Are_server_certificates_valid?
+ cert_paths = ["/etc/x509/certs/leap_commercial.crt", "/etc/x509/certs/leap.crt"]
+ cert_paths.each do |cert_path|
+ if File.exists?(cert_path)
+ cert = OpenSSL::X509::Certificate.new(File.read(cert_path))
+ if Time.now > cert.not_after
+ fail "The certificate #{cert_path} expired on #{cert.not_after}"
+ elsif Time.now + THIRTY_DAYS > cert.not_after
+ fail "The certificate #{cert_path} will expire soon, on #{cert.not_after}"
+ end
+ end
+ end
+ pass
+ end
+
+end
diff --git a/tests/white-box/openvpn.rb b/tests/white-box/openvpn.rb
new file mode 100644
index 00000000..170d4503
--- /dev/null
+++ b/tests/white-box/openvpn.rb
@@ -0,0 +1,16 @@
+raise SkipTest unless service?(:openvpn)
+
+class OpenVPN < LeapTest
+ depends_on "Network"
+
+ def setup
+ end
+
+ def test_01_Are_daemons_running?
+ assert_running '^/usr/sbin/openvpn .* /etc/openvpn/tcp_config.conf$'
+ assert_running '^/usr/sbin/openvpn .* /etc/openvpn/udp_config.conf$'
+ assert_running '^/usr/sbin/unbound$'
+ pass
+ end
+
+end
diff --git a/tests/white-box/soledad.rb b/tests/white-box/soledad.rb
new file mode 100644
index 00000000..d41bee58
--- /dev/null
+++ b/tests/white-box/soledad.rb
@@ -0,0 +1,17 @@
+raise SkipTest unless service?(:soledad)
+
+require 'json'
+
+class Soledad < LeapTest
+ depends_on "Network"
+ depends_on "CouchDB" if service?(:couchdb)
+
+ def setup
+ end
+
+ def test_00_Is_Soledad_running?
+ assert_running '.*/usr/bin/twistd.*--wsgi=leap.soledad.server.application'
+ pass
+ end
+
+end
diff --git a/tests/white-box/webapp.rb b/tests/white-box/webapp.rb
new file mode 100644
index 00000000..68f3dcd2
--- /dev/null
+++ b/tests/white-box/webapp.rb
@@ -0,0 +1,134 @@
+raise SkipTest unless service?(:webapp)
+
+require 'json'
+
+class Webapp < LeapTest
+ depends_on "Network"
+
+ def setup
+ end
+
+ def test_01_Can_contact_couchdb?
+ url = couchdb_url("", url_options)
+ assert_get(url) do |body|
+ assert_match /"couchdb":"Welcome"/, body, "Request to #{url} should return couchdb welcome message."
+ end
+ pass
+ end
+
+ def test_02_Can_contact_couchdb_via_haproxy?
+ if property('haproxy.couch')
+ url = couchdb_url_via_haproxy("", url_options)
+ assert_get(url) do |body|
+ assert_match /"couchdb":"Welcome"/, body, "Request to #{url} should return couchdb welcome message."
+ end
+ pass
+ end
+ end
+
+ def test_03_Are_daemons_running?
+ assert_running '^/usr/sbin/apache2'
+ assert_running '^/usr/bin/ruby /usr/bin/nickserver'
+ pass
+ end
+
+ #
+ # this is technically a black-box test. so, move this when we have support
+ # for black box tests.
+ #
+ def test_04_Can_access_webapp?
+ assert_get('https://' + $node['webapp']['domain'] + '/')
+ pass
+ end
+
+ def test_05_Can_create_and_authenticate_and_delete_user_via_API?
+ if property('webapp.allow_registration')
+ assert_tmp_user
+ pass
+ else
+ skip "New user registrations are disabled."
+ end
+ end
+
+ def test_06_Can_sync_Soledad?
+ return unless property('webapp.allow_registration')
+ soledad_config = property('definition_files.soledad_service')
+ if soledad_config && !soledad_config.empty?
+ soledad_server = pick_soledad_server(soledad_config)
+ if soledad_server
+ assert_tmp_user do |user|
+ command = File.expand_path "../../helpers/soledad_sync.py", __FILE__
+ soledad_url = "https://#{soledad_server}/user-#{user.id}"
+ soledad_cert = "/usr/local/share/ca-certificates/leap_ca.crt"
+ assert_run "#{command} #{user.id} #{user.session_token} #{soledad_url} #{soledad_cert} #{user.password}"
+ assert_user_db_exists(user)
+ pass
+ end
+ end
+ else
+ skip 'No soledad service configuration'
+ end
+ end
+
+ private
+
+ def url_options
+ {
+ :username => property('webapp.couchdb_webapp_user.username'),
+ :password => property('webapp.couchdb_webapp_user.password')
+ }
+ end
+
+ #
+ # pick a random soledad server.
+ # I am not sure why, but using IP address directly does not work.
+ #
+ def pick_soledad_server(soledad_config_json_str)
+ soledad_config = JSON.parse(soledad_config_json_str)
+ host_name = soledad_config['hosts'].keys.shuffle.first
+ if host_name
+ hostname = soledad_config['hosts'][host_name]['hostname']
+ port = soledad_config['hosts'][host_name]['port']
+ return "#{hostname}:#{port}"
+ else
+ return nil
+ end
+ end
+
+ #
+ # returns true if the per-user db created by soledad-server exists.
+ # we try three times, and give up after that.
+ #
+ def assert_user_db_exists(user)
+ db_name = "user-#{user.id}"
+ repeatedly_try("/#{db_name}") do |body, response, error|
+ assert false, "Could not find user db `#{db_name}` for test user `#{user.username}`\nuuid=#{user.id}\nHTTP #{response.code} #{error} #{body}"
+ end
+ repeatedly_try("/#{db_name}/_design/docs") do |body, response, error|
+ assert false, "Could not find design docs for user db `#{db_name}` for test user `#{user.username}`\nuuid=#{user.id}\nHTTP #{response.code} #{error} #{body}"
+ end
+ end
+
+ #
+ # tries the URL repeatedly, giving up and yield the last response if
+ # no try returned a 200 http status code.
+ #
+ def repeatedly_try(url, &block)
+ last_body, last_response, last_error = nil
+ 3.times do
+ sleep 0.2
+ get(couchdb_url(url)) do |body, response, error|
+ last_body, last_response, last_error = body, response, error
+ # After moving to couchdb, webapp user is not allowed to Read user dbs,
+ # but the return code for non-existent databases is 404. See #7674
+ if response.code.to_i == 401
+ return
+ end
+ end
+ sleep 1
+ end
+ yield last_body, last_response, last_error
+ return
+ end
+
+end
diff --git a/vagrant/add-pixelated.sh b/vagrant/add-pixelated.sh
new file mode 100755
index 00000000..f9908947
--- /dev/null
+++ b/vagrant/add-pixelated.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+#
+# adds pixelated-server to the node
+
+. /vagrant/vagrant/vagrant.config
+
+cd "$PROVIDERDIR"
+
+if ! git submodule status files/puppet/modules/pixelated > /dev/null 2>&1; then
+ git submodule add https://github.com/pixelated/puppet-pixelated.git files/puppet/modules/pixelated
+fi
+
+echo '{}' > services/pixelated.json
+[ -d files/puppet/modules/custom/manifests ] || mkdir -p files/puppet/modules/custom/manifests
+echo 'class custom { include ::pixelated}' > files/puppet/modules/custom/manifests/init.pp
+
+$LEAP $OPTS -v 2 deploy
+
+echo '==============================================='
+echo 'testing the platform'
+echo '==============================================='
+
+$LEAP $OPTS -v 2 test --continue
+
+
+echo -e '\n===========================================================================================================\n\n'
+echo -e 'You are now ready to use your vagrant Pixelated provider.\n'
+
+echo -e 'The LEAP webapp is available at https://localhost:4443. Use it to register an account before using the Pixelated Useragent.\n'
+echo -e 'The Pixelated Useragent is available at https://localhost:8080\n'
+
+echo -e 'Please add an exception for both sites in your browser dialog to allow the self-signed certificate.\n'
diff --git a/vagrant/configure-leap.sh b/vagrant/configure-leap.sh
new file mode 100755
index 00000000..9ddee039
--- /dev/null
+++ b/vagrant/configure-leap.sh
@@ -0,0 +1,92 @@
+#!/bin/bash
+
+
+. /vagrant/vagrant/vagrant.config
+
+echo '==============================================='
+echo 'configuring leap'
+echo '==============================================='
+
+# purge $PROVIDERDIR so this script can be run multiple times
+[ -e $PROVIDERDIR ] && rm -rf $PROVIDERDIR
+
+mkdir -p $PROVIDERDIR
+chown ${USER}:${USER} ${PROVIDERDIR}
+cd $PROVIDERDIR
+
+$LEAP $OPTS new --contacts "$contacts" --domain "$provider_domain" --name "$provider_name" --platform=/vagrant .
+echo -e '\n@log = "./deploy.log"' >> Leapfile
+
+if [ ! -e /home/${USER}/.ssh/id_rsa ]; then
+ $SUDO ssh-keygen -f /home/${USER}/.ssh/id_rsa -P ''
+ [ -d /root/.ssh ] || mkdir /root/.ssh
+ cat /home/${USER}/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys
+fi
+
+$SUDO mkdir -p ${PROVIDERDIR}/files/nodes/${NODE}
+sh -c "cat /etc/ssh/ssh_host_rsa_key.pub | cut -d' ' -f1,2 >> $PROVIDERDIR/files/nodes/$NODE/${NODE}_ssh.pub"
+chown ${USER}:${USER} ${PROVIDERDIR}/files/nodes/${NODE}/${NODE}_ssh.pub
+
+$LEAP $OPTS add-user --self
+$LEAP $OPTS cert ca
+$LEAP $OPTS cert csr
+$LEAP $OPTS node add $NODE ip_address:"$(facter ipaddress)" couch.mode:plain services:"$services" tags:production
+echo '{ "webapp": { "admins": ["testadmin"] } }' > services/webapp.json
+
+$LEAP $OPTS compile
+
+$GIT init
+$GIT add .
+$GIT commit -m'configured provider'
+
+$LEAP $OPTS node init $NODE
+if [ $? -eq 1 ]; then
+ echo 'node init failed'
+ exit 1
+fi
+
+# couchrest gem does currently not install on jessie
+# https://leap.se/code/issues/7754
+# workaround is to install rake as gem
+gem install rake
+
+$LEAP $OPTS -v 2 deploy
+
+$GIT add .
+$GIT commit -m'initialized and deployed provider'
+
+# Vagrant: leap_mx fails to start on jessie
+# https://leap.se/code/issues/7755
+# Workaround: we stop and start leap-mx after deploy and
+# before testing
+
+service leap-mx stop
+service leap-mx start
+
+
+
+echo '==============================================='
+echo 'testing the platform'
+echo '==============================================='
+
+$LEAP $OPTS -v 2 test --continue
+
+echo '==============================================='
+echo 'setting node to demo-mode'
+echo '==============================================='
+postconf -e default_transport='error: in demo mode'
+
+# add users: testadmin and testuser with passwords "hallo123"
+curl -s -k https://localhost/1/users.json -d "user%5Blogin%5D=testuser&user%5Bpassword_salt%5D=7d4880237a038e0e&user%5Bpassword_verifier%5D=b98dc393afcd16e5a40fb57ce9cddfa6a978b84be326196627c111d426cada898cdaf3a6427e98b27daf4b0ed61d278bc856515aeceb2312e50c8f816659fcaa4460d839a1e2d7ffb867d32ac869962061368141c7571a53443d58dc84ca1fca34776894414c1090a93e296db6cef12c2cc3f7a991b05d49728ed358fd868286"
+curl -s -k https://localhost/1/users.json -d "user%5Blogin%5D=testadmin&user%5Bpassword_salt%5D=ece1c457014d8282&user%5Bpassword_verifier%5D=9654d93ab409edf4ff1543d07e08f321107c3fd00de05c646c637866a94f28b3eb263ea9129dacebb7291b3374cc6f0bf88eb3d231eb3a76eed330a0e8fd2a5c477ed2693694efc1cc23ae83c2ae351a21139701983dd595b6c3225a1bebd2a4e6122f83df87606f1a41152d9890e5a11ac3749b3bfcf4407fc83ef60b4ced68"
+
+echo -e '\n===========================================================================================================\n\n'
+echo -e 'You are now ready to use your local LEAP provider.\n'
+echo 'If you want to use the *Bitmask client* with your provider, please update your /etc/hosts with following dns overrides:'
+
+$LEAP list --print ip_address,domain.full,dns.aliases | sed 's/^.* //' | sed 's/, null//g' | tr -d '\]\[",'
+
+echo 'Please see https://leap.se/en/docs/platform/tutorials/vagrant#use-the-bitmask-client-to-do-an-initial-soledad-sync for more details how to use and test your LEAP provider.'
+echo -e "\nIf you don't want to use the Bitmask client, please ignore the above instructions.\n"
+echo -e 'The LEAP webapp is now available at https://localhost:4443\n'
+echo -e 'Please add an exception in your browser dialog to allow the self-signed certificate.\n'
diff --git a/vagrant/install-platform.pp b/vagrant/install-platform.pp
new file mode 100755
index 00000000..223853c1
--- /dev/null
+++ b/vagrant/install-platform.pp
@@ -0,0 +1,15 @@
+class {'apt': }
+Exec['update_apt'] -> Package <||>
+
+# install leap_cli from source, so it will work with the develop
+# branch of leap_platform
+class { '::leap::cli::install':
+ source => true,
+}
+
+file { [ '/srv/leap', '/srv/leap/configuration', '/var/log/leap' ]:
+ ensure => directory
+}
+
+# install prerequisites for configuring the provider
+include ::git
diff --git a/vagrant/vagrant.config b/vagrant/vagrant.config
new file mode 100644
index 00000000..e601488d
--- /dev/null
+++ b/vagrant/vagrant.config
@@ -0,0 +1,22 @@
+# provider config values used by vagrant provision scripts
+provider_domain='example.org'
+provider_name='Leap Example Provider'
+contacts="no-reply@$provider_domain"
+
+# serivces that get configured
+# note that the "openvpn" service does currently *not* work
+# in a vagrant setup,
+# see https://leap.se/en/docs/platform/troubleshooting/known-issues#Special.Environments
+# to speed up things, don't deploy monitor service by default
+# services='webapp,mx,couchdb,soledad,monitor'
+services='webapp,mx,couchdb,soledad'
+
+# default vars used by vagrant provision scripts
+OPTS=''
+USER='vagrant'
+NODE='node1'
+SUDO="sudo -u ${USER}"
+PROVIDERDIR="/home/${USER}/leap/configuration"
+LEAP="$SUDO /usr/local/bin/leap"
+GIT="$SUDO git"
+