summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore26
-rw-r--r--.gitlab-ci.yml45
-rw-r--r--.gitmodules96
-rw-r--r--CHANGES.md85
-rw-r--r--README.md134
-rw-r--r--Rakefile47
-rw-r--r--Vagrantfile53
-rwxr-xr-x[-rw-r--r--]bin/node_init7
-rwxr-xr-xbin/puppet_command10
-rwxr-xr-xbin/run_tests8
-rw-r--r--doc/common/_bigcouch_migration.md117
-rw-r--r--doc/common/_bigcouch_migration_begin.md66
-rw-r--r--doc/common/_bigcouch_migration_end.md26
-rw-r--r--doc/common/_bigcouch_migration_finish.md10
-rw-r--r--doc/details/development.md78
-rw-r--r--doc/details/en.haml4
-rw-r--r--doc/details/faq.md71
-rw-r--r--doc/details/ports.md92
-rw-r--r--doc/details/under-the-hood.md40
-rw-r--r--doc/en.md82
-rw-r--r--doc/guide/commands.md559
-rw-r--r--doc/guide/config.md360
-rw-r--r--doc/guide/domains.md129
-rw-r--r--doc/guide/en.haml4
-rw-r--r--doc/guide/environments.md75
-rw-r--r--doc/guide/getting-started.md145
-rw-r--r--doc/guide/keys-and-certificates.md272
-rw-r--r--doc/guide/miscellaneous.md14
-rw-r--r--doc/guide/nodes.md87
-rw-r--r--doc/guide/provider-configuration.md79
-rw-r--r--doc/service-diagram.odgbin12131 -> 0 bytes
-rw-r--r--doc/service-diagram.pngbin25988 -> 0 bytes
-rw-r--r--doc/services/couchdb.md159
-rw-r--r--doc/services/en.md80
-rw-r--r--doc/services/monitor.md36
-rw-r--r--doc/services/mx.md35
-rw-r--r--doc/services/openvpn.md49
-rw-r--r--doc/services/soledad.md12
-rw-r--r--doc/services/tor.md32
-rw-r--r--doc/services/webapp.md293
-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/where-to-look.md267
-rw-r--r--doc/tutorials/en.haml4
-rw-r--r--doc/tutorials/quick-start.md230
-rw-r--r--doc/tutorials/single-node-email.md69
-rw-r--r--doc/tutorials/single-node-vpn.md112
-rw-r--r--doc/tutorials/vagrant.md471
-rw-r--r--doc/upgrading/en.haml5
-rw-r--r--doc/upgrading/upgrade-0-8.md141
-rw-r--r--docs/en/commands.html0
-rw-r--r--docs/en/details.html138
-rw-r--r--docs/en/details/development.html226
-rw-r--r--docs/en/details/development/index.html226
-rw-r--r--docs/en/details/faq.html213
-rw-r--r--docs/en/details/faq/index.html213
-rw-r--r--docs/en/details/ports.html209
-rw-r--r--docs/en/details/ports/index.html209
-rw-r--r--docs/en/details/under-the-hood.html168
-rw-r--r--docs/en/details/under-the-hood/index.html168
-rw-r--r--docs/en/guide.html192
-rw-r--r--docs/en/guide/commands.html977
-rw-r--r--docs/en/guide/commands/index.html977
-rw-r--r--docs/en/guide/config.html584
-rw-r--r--docs/en/guide/config/index.html584
-rw-r--r--docs/en/guide/domains.html298
-rw-r--r--docs/en/guide/domains/index.html298
-rw-r--r--docs/en/guide/environments.html228
-rw-r--r--docs/en/guide/environments/index.html228
-rw-r--r--docs/en/guide/getting-started.html317
-rw-r--r--docs/en/guide/getting-started/index.html317
-rw-r--r--docs/en/guide/keys-and-certificates.html451
-rw-r--r--docs/en/guide/keys-and-certificates/index.html451
-rw-r--r--docs/en/guide/miscellaneous.html145
-rw-r--r--docs/en/guide/miscellaneous/index.html145
-rw-r--r--docs/en/guide/nodes.html231
-rw-r--r--docs/en/guide/nodes/index.html231
-rw-r--r--docs/en/guide/provider-configuration.html223
-rw-r--r--docs/en/guide/provider-configuration/index.html223
-rw-r--r--docs/en/guide/virtual-machines.html299
-rw-r--r--docs/en/guide/virtual-machines/index.html299
-rw-r--r--docs/en/services.html251
-rw-r--r--docs/en/services/couchdb.html328
-rw-r--r--docs/en/services/couchdb/index.html328
-rw-r--r--docs/en/services/index.html251
-rw-r--r--docs/en/services/monitor.html186
-rw-r--r--docs/en/services/monitor/index.html186
-rw-r--r--docs/en/services/mx.html168
-rw-r--r--docs/en/services/mx/index.html168
-rw-r--r--docs/en/services/openvpn.html178
-rw-r--r--docs/en/services/openvpn/index.html178
-rw-r--r--docs/en/services/soledad.html136
-rw-r--r--docs/en/services/soledad/index.html136
-rw-r--r--docs/en/services/tor.html161
-rw-r--r--docs/en/services/tor/index.html161
-rw-r--r--docs/en/services/webapp.html479
-rw-r--r--docs/en/services/webapp/index.html479
-rw-r--r--docs/en/troubleshooting.html129
-rw-r--r--docs/en/troubleshooting/known-issues.html238
-rw-r--r--docs/en/troubleshooting/known-issues/index.html238
-rw-r--r--docs/en/troubleshooting/tests.html201
-rw-r--r--docs/en/troubleshooting/tests/index.html201
-rw-r--r--docs/en/troubleshooting/where-to-look.html451
-rw-r--r--docs/en/troubleshooting/where-to-look/index.html451
-rw-r--r--docs/en/tutorials.html138
-rw-r--r--docs/en/tutorials/quick-start.html446
-rw-r--r--docs/en/tutorials/quick-start/index.html446
-rw-r--r--docs/en/tutorials/single-node-email.html200
-rw-r--r--docs/en/tutorials/single-node-email/index.html200
-rw-r--r--docs/en/tutorials/single-node-vpn.html250
-rw-r--r--docs/en/tutorials/single-node-vpn/index.html250
-rw-r--r--docs/en/tutorials/vagrant.html724
-rw-r--r--docs/en/tutorials/vagrant/index.html724
-rw-r--r--docs/en/tutorials/vagrant/known-issues.html0
-rw-r--r--docs/en/tutorials/vagrant/quick-start.html0
-rw-r--r--docs/en/upgrading.html120
-rw-r--r--docs/en/upgrading/upgrade-0-8.html337
-rw-r--r--docs/en/upgrading/upgrade-0-9.html149
-rw-r--r--docs/index.html190
-rw-r--r--docs/robots.txt.html0
-rw-r--r--leap-debug-remote.sh23
-rw-r--r--lib/leap/platform.rb99
-rw-r--r--lib/leap_cli/acme.rb101
-rw-r--r--lib/leap_cli/cloud.rb4
-rw-r--r--lib/leap_cli/cloud/cloud.rb380
-rw-r--r--lib/leap_cli/cloud/dependencies.rb40
-rw-r--r--lib/leap_cli/cloud/image.rb31
-rw-r--r--lib/leap_cli/commands/ca.rb649
-rw-r--r--lib/leap_cli/commands/compile.rb1
-rw-r--r--lib/leap_cli/commands/db.rb2
-rw-r--r--lib/leap_cli/commands/deploy.rb207
-rw-r--r--lib/leap_cli/commands/facts.rb13
-rw-r--r--lib/leap_cli/commands/info.rb15
-rw-r--r--lib/leap_cli/commands/inspect.rb46
-rw-r--r--lib/leap_cli/commands/list.rb93
-rw-r--r--lib/leap_cli/commands/node.rb177
-rw-r--r--lib/leap_cli/commands/node_init.rb104
-rw-r--r--lib/leap_cli/commands/open.rb103
-rw-r--r--lib/leap_cli/commands/run.rb50
-rw-r--r--lib/leap_cli/commands/ssh.rb17
-rw-r--r--lib/leap_cli/commands/test.rb41
-rw-r--r--lib/leap_cli/commands/user.rb130
-rw-r--r--lib/leap_cli/commands/util.rb50
-rw-r--r--lib/leap_cli/commands/vagrant.rb25
-rw-r--r--lib/leap_cli/commands/vm.rb467
-rw-r--r--lib/leap_cli/config/cloud.rb64
-rw-r--r--lib/leap_cli/config/environment.rb200
-rw-r--r--lib/leap_cli/config/filter.rb181
-rw-r--r--lib/leap_cli/config/manager.rb475
-rw-r--r--lib/leap_cli/config/node.rb245
-rw-r--r--lib/leap_cli/config/node_cert.rb124
-rw-r--r--lib/leap_cli/config/object.rb454
-rw-r--r--lib/leap_cli/config/object_list.rb215
-rw-r--r--lib/leap_cli/config/provider.rb22
-rw-r--r--lib/leap_cli/config/secrets.rb87
-rw-r--r--lib/leap_cli/config/sources.rb11
-rw-r--r--lib/leap_cli/config/tag.rb25
-rw-r--r--lib/leap_cli/leapfile_extensions.rb24
-rw-r--r--lib/leap_cli/load_libraries.rb23
-rw-r--r--lib/leap_cli/log_filter.rb176
-rw-r--r--lib/leap_cli/macros.rb16
-rw-r--r--lib/leap_cli/macros/files.rb27
-rw-r--r--lib/leap_cli/macros/haproxy.rb2
-rw-r--r--lib/leap_cli/macros/keys.rb10
-rw-r--r--lib/leap_cli/ssh.rb7
-rw-r--r--lib/leap_cli/ssh/backend.rb209
-rw-r--r--lib/leap_cli/ssh/formatter.rb70
-rw-r--r--lib/leap_cli/ssh/key.rb310
-rw-r--r--lib/leap_cli/ssh/options.rb100
-rw-r--r--lib/leap_cli/ssh/remote_command.rb124
-rw-r--r--lib/leap_cli/ssh/scripts.rb163
-rw-r--r--lib/leap_cli/util/console_table.rb62
-rw-r--r--lib/leap_cli/util/secret.rb55
-rw-r--r--lib/leap_cli/util/vagrant.rb26
-rw-r--r--lib/leap_cli/x509.rb16
-rw-r--r--lib/leap_cli/x509/certs.rb232
-rw-r--r--lib/leap_cli/x509/signing_profiles.rb104
-rw-r--r--lib/leap_cli/x509/utils.rb26
-rw-r--r--platform.rb8
-rw-r--r--provider_base/common.json13
-rw-r--r--provider_base/common.rb72
-rw-r--r--provider_base/provider.rb5
-rw-r--r--provider_base/services/tor.json2
-rw-r--r--provider_base/services/webapp.json1
-rw-r--r--provider_base/tags/vm.json2
-rw-r--r--puppet/manifests/site.pp114
m---------puppet/modules/apache0
-rw-r--r--puppet/modules/apache/.gitignore6
-rw-r--r--puppet/modules/apache/.gitrepo11
-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
m---------puppet/modules/apt0
-rw-r--r--puppet/modules/apt/.gitignore12
-rw-r--r--puppet/modules/apt/.gitlab-ci.yml12
-rw-r--r--puppet/modules/apt/.gitrepo11
-rw-r--r--puppet/modules/apt/Gemfile (renamed from Gemfile)0
-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
m---------puppet/modules/augeas0
-rw-r--r--puppet/modules/augeas/.fixtures.yml5
-rw-r--r--puppet/modules/augeas/.gitignore7
-rw-r--r--puppet/modules/augeas/.gitrepo11
-rw-r--r--puppet/modules/augeas/.puppet-lint.rc5
-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/.rspec6
-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
m---------puppet/modules/backupninja0
-rw-r--r--puppet/modules/backupninja/.gitrepo11
-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
m---------puppet/modules/bundler0
-rw-r--r--puppet/modules/bundler/.gitignore1
-rw-r--r--puppet/modules/bundler/.gitrepo11
-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
m---------puppet/modules/check_mk0
-rw-r--r--puppet/modules/check_mk/.gitignore3
-rw-r--r--puppet/modules/check_mk/.gitrepo11
-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/clamav-daemon.path12
-rw-r--r--puppet/modules/clamav/manifests/daemon.pp21
-rw-r--r--puppet/modules/clamav/manifests/daemon/activation.pp24
m---------puppet/modules/common0
-rw-r--r--puppet/modules/common/.gitrepo11
-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
m---------puppet/modules/concat0
-rw-r--r--puppet/modules/concat/.gitrepo11
-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
m---------puppet/modules/couchdb0
-rw-r--r--puppet/modules/couchdb/.fixtures.yml6
-rw-r--r--puppet/modules/couchdb/.gitrepo11
-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
m---------puppet/modules/git0
-rw-r--r--puppet/modules/git/.gitrepo11
-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
m---------puppet/modules/haproxy0
-rw-r--r--puppet/modules/haproxy/.fixtures.yml5
-rw-r--r--puppet/modules/haproxy/.gemfile5
-rw-r--r--puppet/modules/haproxy/.gitrepo11
-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
m---------puppet/modules/lsb0
-rw-r--r--puppet/modules/lsb/.gitrepo11
-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
m---------puppet/modules/nagios0
-rw-r--r--puppet/modules/nagios/.gitrepo11
-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
m---------puppet/modules/ntp9
-rw-r--r--puppet/modules/ntp/.fixtures.yml5
-rw-r--r--puppet/modules/ntp/.gitignore3
-rw-r--r--puppet/modules/ntp/.gitrepo11
-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
m---------puppet/modules/openvpn8
-rw-r--r--puppet/modules/openvpn/.fixtures.yml6
-rw-r--r--puppet/modules/openvpn/.gitignore2
-rw-r--r--puppet/modules/openvpn/.gitrepo11
-rw-r--r--puppet/modules/openvpn/Modulefile11
-rw-r--r--puppet/modules/openvpn/Rakefile2
-rw-r--r--puppet/modules/openvpn/Readme.markdown123
-rw-r--r--puppet/modules/openvpn/manifests/client.pp142
-rw-r--r--puppet/modules/openvpn/manifests/init.pp45
-rw-r--r--puppet/modules/openvpn/manifests/option.pp24
-rw-r--r--puppet/modules/openvpn/manifests/server.pp153
-rw-r--r--puppet/modules/openvpn/spec/classes/openvpn_init_spec.rb20
-rw-r--r--puppet/modules/openvpn/spec/defines/openvpn_client_spec.rb116
-rw-r--r--puppet/modules/openvpn/spec/defines/openvpn_option_spec.rb42
-rw-r--r--puppet/modules/openvpn/spec/defines/openvpn_server_spec.rb109
-rw-r--r--puppet/modules/openvpn/spec/spec_helper.rb2
-rw-r--r--puppet/modules/openvpn/templates/etc-default-openvpn.erb20
-rw-r--r--puppet/modules/openvpn/templates/vars.erb69
m---------puppet/modules/passenger0
-rw-r--r--puppet/modules/passenger/.gitrepo11
-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
m---------puppet/modules/postfix0
-rw-r--r--puppet/modules/postfix/.gitrepo11
-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
m---------puppet/modules/resolvconf0
-rw-r--r--puppet/modules/resolvconf/.gitrepo11
-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
m---------puppet/modules/rsyslog0
-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/.gitrepo11
-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
m---------puppet/modules/ruby0
-rw-r--r--puppet/modules/ruby/.gitrepo11
-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
m---------puppet/modules/rubygems0
-rw-r--r--puppet/modules/rubygems/.gitrepo11
-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
m---------puppet/modules/shorewall0
-rw-r--r--puppet/modules/shorewall/.gitrepo11
-rw-r--r--puppet/modules/shorewall/LICENSE674
-rw-r--r--puppet/modules/shorewall/README.md224
-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.header11
-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.header12
-rw-r--r--puppet/modules/shorewall/files/empty/.ignore1
-rw-r--r--puppet/modules/shorewall/manifests/base.pp78
-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.pp16
-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.pp123
-rw-r--r--puppet/modules/shorewall/manifests/interface.pp29
-rw-r--r--puppet/modules/shorewall/manifests/managed_file.pp20
-rw-r--r--puppet/modules/shorewall/manifests/mangle.pp20
-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.pp6
-rw-r--r--puppet/modules/shorewall/manifests/rules/dns/disable.pp6
-rw-r--r--puppet/modules/shorewall/manifests/rules/dns_rules.pp22
-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.pp34
-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.pp80
-rw-r--r--puppet/modules/shorewall/manifests/rules/managesieve.pp25
-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/openvpn.pp18
-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.pp25
-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/pyzor.pp12
-rw-r--r--puppet/modules/shorewall/manifests/rules/out/razor.pp12
-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/zone.pp14
-rw-r--r--puppet/modules/shorewall/templates/debian_default.erb26
l---------puppet/modules/site-apache1
-rw-r--r--puppet/modules/site_apache/files/conf.d/acme.conf10
-rw-r--r--puppet/modules/site_apache/manifests/common.pp2
-rw-r--r--puppet/modules/site_apache/manifests/common/acme.pp38
-rw-r--r--puppet/modules/site_apache/templates/vhosts.d/api.conf.erb2
-rw-r--r--puppet/modules/site_apache/templates/vhosts.d/hidden_service.conf.erb15
-rw-r--r--puppet/modules/site_apt/manifests/init.pp3
-rw-r--r--puppet/modules/site_apt/manifests/preferences/twisted.pp11
-rw-r--r--puppet/modules/site_check_mk/files/agent/logwatch/bigcouch.cfg28
-rw-r--r--puppet/modules/site_check_mk/files/agent/logwatch/soledad.cfg3
-rw-r--r--puppet/modules/site_check_mk/files/agent/logwatch/syslog_tail.cfg4
-rwxr-xr-xpuppet/modules/site_check_mk/files/agent/nagios_plugins/check_unix_open_fds.pl322
-rw-r--r--puppet/modules/site_check_mk/files/ignored_services.mk4
-rw-r--r--puppet/modules/site_check_mk/manifests/agent/couchdb.pp20
-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/soledad.pp11
-rw-r--r--puppet/modules/site_config/lib/puppet/parser/functions/create_resources_hash_from.rb (renamed from puppet/lib/puppet/parser/functions/create_resources_hash_from.rb)0
-rw-r--r--puppet/modules/site_config/lib/puppet/parser/functions/sorted_json.rb (renamed from puppet/lib/puppet/parser/functions/sorted_json.rb)0
-rw-r--r--puppet/modules/site_config/lib/puppet/parser/functions/sorted_yaml.rb (renamed from puppet/lib/puppet/parser/functions/sorted_yaml.rb)0
-rw-r--r--puppet/modules/site_config/manifests/caching_resolver.pp27
-rw-r--r--puppet/modules/site_config/manifests/remove/bigcouch.pp27
-rw-r--r--puppet/modules/site_config/manifests/remove/files.pp28
-rw-r--r--puppet/modules/site_config/manifests/remove/soledad.pp12
-rw-r--r--puppet/modules/site_config/manifests/x509/commercial/ca.pp10
-rw-r--r--puppet/modules/site_couchdb/files/local.ini88
-rw-r--r--puppet/modules/site_couchdb/files/runit_config6
-rw-r--r--puppet/modules/site_couchdb/manifests/add_users.pp3
-rw-r--r--puppet/modules/site_couchdb/manifests/backup.pp4
-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.pp3
-rw-r--r--puppet/modules/site_couchdb/manifests/init.pp11
-rw-r--r--puppet/modules/site_couchdb/manifests/logrotate.pp14
-rw-r--r--puppet/modules/site_couchdb/manifests/plain.pp2
-rw-r--r--puppet/modules/site_couchdb/manifests/setup.pp18
-rw-r--r--puppet/modules/site_mx/manifests/init.pp31
-rw-r--r--puppet/modules/site_nagios/manifests/server.pp2
-rw-r--r--puppet/modules/site_nickserver/manifests/init.pp2
-rw-r--r--puppet/modules/site_openvpn/manifests/server_config.pp8
-rw-r--r--puppet/modules/site_postfix/files/checks/helo_access.pcre2
-rw-r--r--puppet/modules/site_postfix/manifests/mx.pp33
-rw-r--r--puppet/modules/site_postfix/manifests/mx/checks.pp7
-rw-r--r--puppet/modules/site_postfix/manifests/mx/smtpd_checks.pp2
-rw-r--r--puppet/modules/site_postfix/templates/checks/helo_access.erb2
-rw-r--r--puppet/modules/site_rsyslog/templates/client.conf.erb8
-rw-r--r--puppet/modules/site_shorewall/manifests/defaults.pp7
-rw-r--r--puppet/modules/site_shorewall/manifests/eip.pp151
-rw-r--r--puppet/modules/site_shorewall/manifests/ip_forward.pp3
-rw-r--r--puppet/modules/site_shorewall/manifests/mx.pp3
-rw-r--r--puppet/modules/site_shorewall/manifests/obfsproxy.pp2
-rw-r--r--puppet/modules/site_shorewall/manifests/service/webapp_api.pp2
-rw-r--r--puppet/modules/site_shorewall/manifests/soledad.pp3
-rw-r--r--puppet/modules/site_shorewall/manifests/sshd.pp2
-rw-r--r--puppet/modules/site_shorewall/manifests/stunnel/server.pp2
-rw-r--r--puppet/modules/site_shorewall/manifests/tor.pp2
-rw-r--r--puppet/modules/site_sshd/manifests/mosh.pp3
-rw-r--r--puppet/modules/site_static/manifests/domain.pp1
-rw-r--r--puppet/modules/site_static/manifests/hidden_service.pp37
-rw-r--r--puppet/modules/site_static/manifests/init.pp51
-rw-r--r--puppet/modules/site_static/templates/amber.erb8
-rw-r--r--puppet/modules/site_static/templates/apache.conf.erb123
-rw-r--r--puppet/modules/site_static/templates/rack.erb6
-rw-r--r--puppet/modules/site_webapp/manifests/apache.pp2
-rw-r--r--puppet/modules/site_webapp/manifests/hidden_service.pp13
-rw-r--r--puppet/modules/site_webapp/manifests/init.pp71
-rw-r--r--puppet/modules/soledad/manifests/common.pp3
-rw-r--r--puppet/modules/soledad/manifests/server.pp15
m---------puppet/modules/squid_deb_proxy0
-rw-r--r--puppet/modules/squid_deb_proxy/.gitrepo11
-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
m---------puppet/modules/sshd0
-rw-r--r--puppet/modules/sshd/.fixtures.yml3
-rw-r--r--puppet/modules/sshd/.gitignore4
-rw-r--r--puppet/modules/sshd/.gitrepo11
-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
m---------puppet/modules/stdlib0
-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/.gitrepo11
-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
m---------puppet/modules/stunnel0
-rw-r--r--puppet/modules/stunnel/.gitrepo11
-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.pp23
-rw-r--r--puppet/modules/stunnel/manifests/init.pp65
-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
m---------puppet/modules/sysctl0
-rw-r--r--puppet/modules/sysctl/.gitrepo11
-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
m---------puppet/modules/systemd0
-rw-r--r--puppet/modules/systemd/.gitignore10
-rw-r--r--puppet/modules/systemd/.gitrepo11
-rw-r--r--puppet/modules/systemd/.puppet-lint.rc5
-rw-r--r--puppet/modules/systemd/.sync.yml3
-rw-r--r--puppet/modules/systemd/.travis.yml32
-rw-r--r--puppet/modules/systemd/CHANGELOG.md65
-rw-r--r--puppet/modules/systemd/Gemfile47
-rw-r--r--puppet/modules/systemd/HISTORY.md62
-rw-r--r--puppet/modules/systemd/README.md38
-rw-r--r--puppet/modules/systemd/Rakefile23
-rw-r--r--puppet/modules/systemd/manifests/enable.pp8
-rw-r--r--puppet/modules/systemd/manifests/init.pp18
-rw-r--r--puppet/modules/systemd/metadata.json48
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/centos-5-x86_64-docker.yml15
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/centos-6-x86_64-docker.yml15
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/centos-6-x86_64-openstack.yml14
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/centos-6-x86_64-vagrant.yml11
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/centos-7-x86_64-docker.yml15
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/centos-7-x86_64-openstack.yml14
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/centos-7-x86_64-vagrant.yml11
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/debian-6-x86_64-docker.yml15
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/debian-6-x86_64-openstack.yml14
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/debian-6-x86_64-vagrant.yml11
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/debian-7-x86_64-docker.yml15
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/debian-7-x86_64-openstack.yml14
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/debian-7-x86_64-vagrant.yml11
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/debian-8-x86_64-docker.yml15
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/debian-8-x86_64-openstack.yml14
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/debian-8-x86_64-vagrant.yml11
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-10.04-x86_64-docker.yml13
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-12.04-x86_64-docker.yml15
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-12.04-x86_64-openstack.yml14
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.04-x86_64-docker.yml15
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.04-x86_64-openstack.yml14
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.04-x86_64-vagrant.yml11
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.10-x86_64-docker.yml15
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.10-x86_64-openstack.yml14
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-15.04-x86_64-docker.yml15
-rw-r--r--puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-15.04-x86_64-openstack.yml14
-rw-r--r--puppet/modules/systemd/spec/spec.opts6
-rw-r--r--puppet/modules/systemd/spec/spec_helper.rb42
m---------puppet/modules/tor0
-rw-r--r--puppet/modules/tor/.gitignore1
-rw-r--r--puppet/modules/tor/.gitrepo11
-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
m---------puppet/modules/unbound0
-rw-r--r--puppet/modules/unbound/.gitrepo11
-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
m---------puppet/modules/vcsrepo0
-rw-r--r--puppet/modules/vcsrepo/.gitattributes5
-rw-r--r--puppet/modules/vcsrepo/.gitignore11
-rw-r--r--puppet/modules/vcsrepo/.gitrepo11
-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
m---------puppet/modules/x5090
-rw-r--r--puppet/modules/x509/.gitrepo11
-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.md27
-rw-r--r--tests/example-provider/README.md8
-rw-r--r--tests/example-provider/Vagrantfile58
-rw-r--r--tests/example-provider/hiera.yaml (renamed from hiera.yaml)0
-rwxr-xr-xtests/example-provider/vagrant/add-pixelated.sh (renamed from vagrant/add-pixelated.sh)0
-rwxr-xr-xtests/example-provider/vagrant/configure-leap.sh92
-rwxr-xr-xtests/example-provider/vagrant/install-platform.pp (renamed from vagrant/install-platform.pp)0
-rw-r--r--tests/example-provider/vagrant/vagrant.config23
-rw-r--r--tests/helpers/couchdb_helper.rb142
-rw-r--r--tests/helpers/os_helper.rb41
-rw-r--r--tests/platform-ci/Gemfile17
-rw-r--r--tests/platform-ci/README.md15
-rw-r--r--tests/platform-ci/Rakefile121
-rwxr-xr-xtests/platform-ci/ci-build.sh90
-rw-r--r--tests/platform-ci/hiera.yaml16
-rw-r--r--tests/platform-ci/provider/Leapfile1
-rw-r--r--tests/platform-ci/provider/cloud.json.template15
-rw-r--r--tests/platform-ci/provider/common.json12
-rw-r--r--tests/platform-ci/provider/files/ca/ca.crt32
-rw-r--r--tests/platform-ci/provider/files/ca/ca.key51
-rw-r--r--tests/platform-ci/provider/files/ca/client_ca.crt33
-rw-r--r--tests/platform-ci/provider/files/ca/client_ca.key51
-rw-r--r--tests/platform-ci/provider/files/ca/dh.pem19
-rw-r--r--tests/platform-ci/provider/files/cert/commercial_ca.crt32
-rw-r--r--tests/platform-ci/provider/files/cert/example.org.crt31
-rw-r--r--tests/platform-ci/provider/files/cert/example.org.csr27
-rw-r--r--tests/platform-ci/provider/files/cert/example.org.key51
-rw-r--r--tests/platform-ci/provider/files/mx/dkim.key27
-rw-r--r--tests/platform-ci/provider/files/mx/dkim.pub9
-rw-r--r--tests/platform-ci/provider/files/ssh/monitor_ssh51
-rw-r--r--tests/platform-ci/provider/files/ssh/monitor_ssh.pub1
-rw-r--r--tests/platform-ci/provider/nodes/catalogtest.json39
-rw-r--r--tests/platform-ci/provider/provider.json18
-rw-r--r--tests/platform-ci/provider/tags/catalogtest.json1
-rw-r--r--tests/platform-ci/provider/users/gitlab-runner/gitlab-runner_ssh.pub1
-rwxr-xr-xtests/platform-ci/setup.sh4
-rw-r--r--tests/server-tests/README.md44
-rw-r--r--tests/server-tests/helpers/bonafide_helper.rb (renamed from tests/helpers/bonafide_helper.rb)0
-rw-r--r--tests/server-tests/helpers/client_side_db.py (renamed from tests/helpers/client_side_db.py)0
-rw-r--r--tests/server-tests/helpers/couchdb_helper.rb143
-rw-r--r--tests/server-tests/helpers/files_helper.rb (renamed from tests/helpers/files_helper.rb)0
-rw-r--r--tests/server-tests/helpers/http_helper.rb (renamed from tests/helpers/http_helper.rb)0
-rw-r--r--tests/server-tests/helpers/network_helper.rb (renamed from tests/helpers/network_helper.rb)0
-rw-r--r--tests/server-tests/helpers/os_helper.rb41
-rw-r--r--tests/server-tests/helpers/smtp_helper.rb (renamed from tests/helpers/smtp_helper.rb)0
-rwxr-xr-xtests/server-tests/helpers/soledad_sync.py (renamed from tests/helpers/soledad_sync.py)0
-rw-r--r--tests/server-tests/helpers/srp_helper.rb (renamed from tests/helpers/srp_helper.rb)0
-rw-r--r--tests/server-tests/order.rb (renamed from tests/order.rb)0
-rw-r--r--tests/server-tests/white-box/couchdb.rb169
-rw-r--r--tests/server-tests/white-box/dummy.rb (renamed from tests/white-box/dummy.rb)0
-rw-r--r--tests/server-tests/white-box/mx.rb271
-rw-r--r--tests/server-tests/white-box/network.rb90
-rw-r--r--tests/server-tests/white-box/openvpn.rb (renamed from tests/white-box/openvpn.rb)0
-rw-r--r--tests/server-tests/white-box/soledad.rb (renamed from tests/white-box/soledad.rb)0
-rw-r--r--tests/server-tests/white-box/webapp.rb114
-rw-r--r--tests/white-box/couchdb.rb186
-rw-r--r--tests/white-box/mx.rb186
-rw-r--r--tests/white-box/network.rb90
-rw-r--r--tests/white-box/webapp.rb134
-rwxr-xr-xvagrant/configure-leap.sh92
-rw-r--r--vagrant/vagrant.config22
2110 files changed, 116947 insertions, 7527 deletions
diff --git a/.gitignore b/.gitignore
index 30792935..d80ef422 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,26 @@
-/.vagrant
+/tests/example-provider/.vagrant
+
/puppet/modules/site_custom
+
+/tests/platform-ci/.bundle
+/tests/platform-ci/vendor
+/tests/platform-ci/bin
+/tests/platform-ci/Gemfile.lock
+/tests/platform-ci/provider/cloud.json
+/tests/platform-ci/provider/facts.json
+/tests/platform-ci/provider/hiera
+/tests/platform-ci/provider/secrets.json
+/tests/platform-ci/provider/files/ssh/authorized_keys
+/tests/platform-ci/provider/files/ssh/known_hosts
+/tests/platform-ci/provider/files/nodes/
+/tests/platform-ci/provider/nodes/*
+!/tests/platform-ci/provider/nodes/catalogtest.json
+/tests/platform-ci/provider/tags/*
+!/tests/platform-ci/provider/tags/catalogtest.json
+/tests/platform-ci/provider/users/*
+!/tests/platform-ci/provider/users/gitlab-runner
+/tests/platform-ci/provider/users/gitlab-runner/*
+!/tests/platform-ci/provider/users/gitlab-runner/gitlab-runner_ssh.pub
+/tests/platform-ci/provider/test
+
+/builds
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 00000000..c6cbb666
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,45 @@
+before_script:
+ - echo 'Running global before_script'
+ - cd tests/platform-ci
+ - ./setup.sh
+
+stages:
+ - syntax
+ - build
+
+image: leapcode/ruby
+
+lint:
+ stage: syntax
+ script:
+ - /usr/local/bin/bundle exec rake lint
+
+syntax:
+ stage: syntax
+ script:
+ - /usr/local/bin/bundle exec rake syntax
+
+validate:
+ stage: syntax
+ script:
+ - /usr/local/bin/bundle exec rake validate
+
+templates:
+ stage: syntax
+ script:
+ - /usr/local/bin/bundle exec rake templates
+
+catalog:
+ stage: syntax
+ script:
+ - /usr/local/bin/bundle exec rake catalog
+
+#rspec:
+# stage: rspec
+# script:
+# - /usr/local/bin/bundle exec rake spec
+
+build:
+ stage: build
+ script:
+ - /usr/bin/unbuffer ./ci-build.sh | /usr/bin/ts -s
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index 051117f8..00000000
--- a/.gitmodules
+++ /dev/null
@@ -1,96 +0,0 @@
-[submodule "puppet/modules/openvpn"]
- path = puppet/modules/openvpn
- url = https://leap.se/git/puppet_openvpn
-[submodule "puppet/modules/concat"]
- path = puppet/modules/concat
- url = https://leap.se/git/puppet_concat
-[submodule "puppet/modules/sshd"]
- path = puppet/modules/sshd
- url = https://leap.se/git/puppet_sshd
-[submodule "puppet/modules/apt"]
- path = puppet/modules/apt
- url = https://leap.se/git/puppet_apt
-[submodule "puppet/modules/lsb"]
- path = puppet/modules/lsb
- url = https://leap.se/git/puppet_lsb
-[submodule "puppet/modules/ntp"]
- path = puppet/modules/ntp
- url = https://leap.se/git/puppet_ntp.git
-[submodule "puppet/modules/git"]
- path = puppet/modules/git
- url = https://leap.se/git/puppet_git
-[submodule "puppet/modules/common"]
- path = puppet/modules/common
- url = https://leap.se/git/puppet_common
-[submodule "puppet/modules/shorewall"]
- path = puppet/modules/shorewall
- url = https://leap.se/git/puppet_shorewall
-[submodule "puppet/modules/resolvconf"]
- path = puppet/modules/resolvconf
- url = https://leap.se/git/puppet_resolvconf.git
-[submodule "puppet/modules/couchdb"]
- path = puppet/modules/couchdb
- url = https://leap.se/git/puppet_couchdb
-[submodule "puppet/modules/apache"]
- path = puppet/modules/apache
- url = https://leap.se/git/puppet_apache
-[submodule "puppet/modules/bundler"]
- path = puppet/modules/bundler
- url = https://leap.se/git/puppet_bundler
-[submodule "puppet/modules/rubygems"]
- path = puppet/modules/rubygems
- url = https://leap.se/git/puppet_rubygems
-[submodule "puppet/modules/ruby"]
- path = puppet/modules/ruby
- url = https://leap.se/git/puppet_ruby
-[submodule "puppet/modules/x509"]
- path = puppet/modules/x509
- url = https://leap.se/git/puppet_x509
-[submodule "puppet/modules/passenger"]
- path = puppet/modules/passenger
- url = https://leap.se/git/puppet_passenger
-[submodule "puppet/modules/augeas"]
- path = puppet/modules/augeas
- url = https://leap.se/git/puppet_augeas
-[submodule "puppet/modules/stdlib"]
- path = puppet/modules/stdlib
- url = https://leap.se/git/puppet_stdlib
-[submodule "puppet/modules/unbound"]
- path = puppet/modules/unbound
- url = https://leap.se/git/puppet_unbound
-[submodule "puppet/modules/nagios"]
- path = puppet/modules/nagios
- url = https://leap.se/git/puppet_nagios
-[submodule "puppet/modules/tor"]
- path = puppet/modules/tor
- url = https://leap.se/git/puppet_tor
-[submodule "puppet/modules/stunnel"]
- path = puppet/modules/stunnel
- url = https://leap.se/git/puppet_stunnel
-[submodule "puppet/modules/haproxy"]
- path = puppet/modules/haproxy
- url = https://leap.se/git/puppet_haproxy
-[submodule "puppet/modules/squid_deb_proxy"]
- path = puppet/modules/squid_deb_proxy
- url = https://leap.se/git/puppet_squid_deb_proxy
-[submodule "puppet/modules/postfix"]
- path = puppet/modules/postfix
- url = https://leap.se/git/puppet_postfix
-[submodule "puppet/modules/vcsrepo"]
- path = puppet/modules/vcsrepo
- url = https://leap.se/git/puppet_vcsrepo
-[submodule "puppet/modules/rsyslog"]
- path = puppet/modules/rsyslog
- url = https://leap.se/git/puppet_rsyslog
-[submodule "puppet/modules/backupninja"]
- path = puppet/modules/backupninja
- url = https://leap.se/git/puppet_backupninja
-[submodule "puppet/modules/sysctl"]
- path = puppet/modules/sysctl
- url = https://leap.se/git/puppet_sysctl
-[submodule "puppet/modules/check_mk"]
- path = puppet/modules/check_mk
- url = https://leap.se/git/puppet_check_mk
-[submodule "puppet/modules/systemd"]
- path = puppet/modules/systemd
- url = https://leap.se/git/puppet_systemd
diff --git a/CHANGES.md b/CHANGES.md
index d6169fcf..3dc66746 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,12 +1,91 @@
+Platform 0.9
+--------------------------------------
+
+The focus for Platform 0.9 was to clean house: we replaced the annoying system
+of puppet submodules, we cleaned up the directory structure, we removed many of
+the gem dependencies, and we fixed a lot of bugs.
+
+New Features:
+
+* `leap vm` -- Support for managing remote virtual servers (AWS only, for now)
+* `leap cert renew` -- Integration with Let's Encrypt
+* `leap open monitor` -- for handy access to nagios
+* improved documentation -- open docs/index.html to see
+
+Notable Changes:
+
+* 86 bugs fixed
+* Fixed security issues with VPN
+* More tests
+* Replaced git submodules with git subrepo
+* Nearly all the leap_cli code has been moved to leap_platform.git
+* Command-line leap_cli cleanup to be more logically consistent
+* Better organization of the leap_platform.git directory structure
+* Removed ugly dependency on Capistrano
+* Enabled DANE/TLSA validation
+* Anti-spam improvements
+* Performance improvements for couchdb
+* Change from httpredir.debian.org to deb.debian.org
+* Reduce duplicated logging
+
+Upgrading:
+
+You will need the new version of leap_cli:
+
+ workstation$ sudo gem install leap_cli --version=1.9
+
+Because 0.9 does not use submodules anymore, you must remove them before pulling
+the latest leap_platform from git:
+
+ workstation$ cd leap_platform
+ workstation$ for dir in $(git submodule | awk '{print $2}'); do
+ workstation$ git submodule deinit $dir
+ workstation$ done
+ workstation$ git pull
+ workstation$ git checkout 0.9.0
+
+Alternately, just clone a fresh leap_platform:
+
+ workstation$ git clone https://leap.se/git/leap_platform
+ workstation$ cd leap_platform
+ workstation$ git checkout 0.9.0
+
+Then, deploy:
+
+ workstation$ cd PROVIDER_DIR
+ workstation$ leap deploy
+
+Known Issues:
+
+* When upgrading, sometimes systemd does not report the correct state of a
+ daemon. The daemon will be not running, but systemd thinks it is. The symptom
+ of this is that a deploy will succeed but `leap test` will fail. To fix, you
+ can run `systemctl stop DAEMON` and then `systemctl start DAEMON` on the
+ affected host (systemctl restart seems to work less reliably).
+
+Includes:
+
+* leap_web: 0.8
+* nickserver: 0.8
+* couchdb: 1.6.0
+* leap-mx: 0.8.1
+* soledad-server: 0.8.0
+
+Commits: https://leap.se/git/leap_platform.git/shortlog/refs/tags/0.9
+
+Issues fixed: https://leap.se/code/versions/195
+
+
Platform 0.8
--------------------------------------
This release focuses on the email service.
Requirements:
- . You must upgrade to Debian Jessie, see below for details
- . You must migrate all data from BigCouch to CouchDB
- . Soledad and couchdb services must be on the same node
+
+* You must upgrade to Debian Jessie, see below for details
+* You must migrate all data from BigCouch to CouchDB
+* Soledad and couchdb services must be on the same node
WARNING: failure to migrate data from BigCouch to CouchDB will cause all user
accounts to get destroyed. See UPGRADING below for how to safely do this.
diff --git a/README.md b/README.md
index f2f10f1c..4a045cdd 100644
--- a/README.md
+++ b/README.md
@@ -1,106 +1,112 @@
Leap Platform
=============================
+[![Build Status](https://0xacab.org/leap/platform/badges/develop/build.svg)](https://0xacab.org/leap/platform/commits/develop)
-[![build status](https://0xacab.org/leap/platform/badges/master/build.svg)](https://0xacab.org/leap/platform/commits/master)
-
-
-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.
+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.
+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 `docs` subdirectory:
-An offline copy of this documentation is contained in the `doc` subdirectory. For more current updates to the documentation, visit the website.
+ cd leap_platform
+ gnome-open docs/index.html
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 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.
+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 Jessie 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 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.
+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'`.
+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.
+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)
+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)
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
+* 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.
-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
+================================
+Run rake tests
+--------------
-Contributing
-============
+ cd tests/platform-ci
+ ./setup.sh
+ bundle exec rake lint
+ bundle exec rake syntax
+ bundle exec rake validate
+ bundle exec rake templates
+ bundle exec rake catalog
-In order to validate the syntax and style guide compliance
-before you commit, see https://github.com/pixelated-project/puppet-git-hooks#installation
+Merge requests
+--------------
+In order to validate the syntax and style guide compliance before you commit,
+see https://github.com/pixelated-project/puppet-git-hooks#installation
+Please fork https://0xacab.org/leap/platform to open a merge request,
+and pick the `Platform runner (greyhound)` at https://0xacab.org/YOUR_USERNAME/platform/runners
+in order to run a CI build for your merge request.
Changes
-=========
+================================
Read CHANGES.md or run `git log`.
Authors and Credits
-===================
+================================
See contributors:
@@ -108,6 +114,6 @@ See contributors:
Copyright/License
-=================
+================================
Read LICENSE
diff --git a/Rakefile b/Rakefile
deleted file mode 100644
index 8f7a9686..00000000
--- a/Rakefile
+++ /dev/null
@@ -1,47 +0,0 @@
-require 'puppetlabs_spec_helper/rake_tasks'
-require 'puppet-lint/tasks/puppet-lint'
-require 'puppet-syntax/tasks/puppet-syntax'
-
-# return list of modules, either
-# submodules or custom modules
-# so we can check each array seperately
-def modules_pattern (type)
- submodules = Array.new
- custom_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
- end
-
- if type == 'submodule'
- submodules
- elsif type == 'custom'
- custom_modules
- else
- end
-
-end
-
-
-
-# redefine lint task with specific configuration
-Rake::Task[:lint].clear
-desc "boo"
-PuppetLint::RakeTask.new :lint do |config|
- # Pattern of files to check, defaults to `**/*.pp`
- config.pattern = modules_pattern('custom')
- config.ignore_paths = ["spec/**/*.pp", "pkg/**/*.pp", "vendor/**/*.pp"]
- config.disable_checks = ['documentation', '80chars']
- config.fail_on_warnings = false
-end
-
-# rake syntax::* tasks
-PuppetSyntax.exclude_paths = ["**/vendor/**/*"]
-
-desc "Run all puppet checks required for CI"
-task :test => [:lint, :syntax , :validate, :spec]
diff --git a/Vagrantfile b/Vagrantfile
deleted file mode 100644
index 25f26b3b..00000000
--- a/Vagrantfile
+++ /dev/null
@@ -1,53 +0,0 @@
-# -*- 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/node_init b/bin/node_init
index da250012..b55cfed3 100644..100755
--- a/bin/node_init
+++ b/bin/node_init
@@ -22,7 +22,10 @@ if ! egrep -q "$DEBIAN_VERSION" /etc/debian_version; then
exit 1
fi
mkdir -p $LEAP_DIR
-echo "en_US.UTF-8 UTF-8" > /etc/locale.gen
+if ! grep -q -e '^en_US.UTF-8' /etc/locale.gen; then
+ echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen
+ /usr/sbin/locale-gen
+fi
#
# UPDATE PACKAGES
@@ -49,6 +52,8 @@ if [[ $error_count > 0 ]]; then
exit 1
fi
+/usr/bin/apt-get -q -y -o 'DPkg::Options::=--force-confold' dist-upgrade
+
#
# UPDATE TIME
#
diff --git a/bin/puppet_command b/bin/puppet_command
index eb3cd0b9..a2876fd9 100755
--- a/bin/puppet_command
+++ b/bin/puppet_command
@@ -28,7 +28,7 @@ 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"
-
+LANG = "en_US.UTF-8"
def main
if File.read('/etc/debian_version') !~ DEBIAN_VERSION
@@ -144,7 +144,7 @@ def puppet_apply(options={}, &block)
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)
+ return run("LANG='#{LANG}' FACTER_fqdn='#{fqdn}' FACTER_domain='#{domain}' #{PUPPET_BIN} apply #{custom_parameters(options)} --modulepath='#{modulepath}' #{PUPPET_PARAMETERS} #{manifest}", &block)
end
end
@@ -173,9 +173,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
+ if File.exist?(SUMMARY_LOG) && File.size(SUMMARY_LOG) != 0
file = SUMMARY_LOG
- elsif File.exists?(SUMMARY_LOG_1) && File.size(SUMMARY_LOG_1) != 0
+ elsif File.exist?(SUMMARY_LOG_1) && File.size(SUMMARY_LOG_1) != 0
file = SUMMARY_LOG_1
else
return
@@ -195,7 +195,7 @@ end
# Return a ruby object representing the contents of the hiera yaml file.
#
def hiera_file
- unless File.exists?(HIERA_FILE)
+ unless File.exist?(HIERA_FILE)
log("ERROR: hiera file '#{HIERA_FILE}' does not exist.")
exit(1)
end
diff --git a/bin/run_tests b/bin/run_tests
index b6784ed5..8450a9bf 100755
--- a/bin/run_tests
+++ b/bin/run_tests
@@ -1,7 +1,7 @@
#!/usr/bin/ruby
#
-# this script will run the unit tests in ../tests/*.rb.
+# This script will run the unit tests in /srv/leap/tests
#
# Tests for the platform differ from traditional ruby unit tests in a few ways:
#
@@ -29,11 +29,11 @@ EXIT_CODES = {
HIERA_FILE = '/etc/leap/hiera.yaml'
HELPER_PATHS = [
- '../../tests/helpers/*.rb',
+ '/srv/leap/tests/server-tests/helpers/*.rb',
'/srv/leap/files/tests/helpers/*.rb'
]
TEST_PATHS = [
- '../../tests/white-box/*.rb',
+ '/srv/leap/tests/server-tests/white-box/*.rb',
'/srv/leap/files/tests/white-box/*.rb',
'/srv/leap/tests_custom/*.rb'
]
@@ -459,7 +459,7 @@ end
def main
# load node data from hiera file
- if File.exists?(HIERA_FILE)
+ if File.exist?(HIERA_FILE)
$node = YAML.load_file(HIERA_FILE)
else
$node = {"services" => [], "dummy" => true}
diff --git a/doc/common/_bigcouch_migration.md b/doc/common/_bigcouch_migration.md
deleted file mode 100644
index eb7e07e9..00000000
--- a/doc/common/_bigcouch_migration.md
+++ /dev/null
@@ -1,117 +0,0 @@
-@title = "Migrating from BigCouch to plain CouchDB"
-
-Here are the steps needed to replace BigCouch with CouchDB.
-
-At the end of this process, you will have just *one* node with `services` property equal to `couchdb`. If you had a BigCouch cluster before, you will be removing all but one of those machines to consolidate them into one CouchDB machine.
-
-1. if you have multiple nodes with the `couchdb` service on them, pick one of them to be your CouchDB server, and remove the service from the others. If these machines were only doing BigCouch before, you can remove the nodes completely with `leap node rm <nodename>` and then you can decommission the servers
-
-1. put the webapp into [[maintenance mode => webapp#maintenance-mode]]
-
-1. turn off daemons that access the database. For example:
-
- ```
- workstation$ leap ssh <each soledad-node>
- server# /etc/init.d/soledad-server stop
-
- workstation$ leap ssh <mx-node>
- server# /etc/init.d/postfix stop
- server# /etc/init.d/leap-mx stop
-
- workstation$ leap ssh <webapp-node>
- server# /etc/init.d/nickserver stop
- ```
-
- Alternately, you can create a temporary firewall rule to block access (run on couchdb server):
-
- ```
- server# iptables -A INPUT -p tcp --dport 5984 --jump REJECT
- ```
-
-1. remove orphaned databases and do a backup of all remaining, active databases. This can take some time and will place several hundred megabytes of data into /var/backups/couchdb. The size and time depends on how many users there are on your system. For example, 15k users took approximately 25 minutes and 308M of space:
-
- ```
- workstation$ leap ssh <couchdb-node>
- server# cd /srv/leap/couchdb/scripts
- server# ./cleanup-user-dbs
- server# time ./couchdb_dumpall.sh
- ```
-
-1. stop bigcouch:
-
- ```
- server# /etc/init.d/bigcouch stop
- server# pkill epmd
- ```
-
-1. remove bigcouch:
-
- ```
- server# apt-get remove bigcouch
- ```
-
-1. configure your couch node to use plain couchdb instead of bigcouch, you can do this by editing nodes/<couch-node>.json, look for this section:
-
- ```
- "couch": {
- "mode": "plain"
- }
- ```
-
- change it, so it looks like this instead:
-
- ```
- "couch": {
- "mode": "plain",
- "pwhash_alg": "pbkdf2"
- }
- ```
-
-1. deploy to the couch node:
-
- ```
- workstation$ leap deploy <couchdb-node>
- ```
-
- If you used the iptables method of blocking access to couchdb, you need to run it again because the deploy just overwrote all the iptables rules:
-
- ```
- server# iptables -A INPUT -p tcp --dport 5984 --jump REJECT
- ```
-
-1. restore the backup, this will take approximately the same amount of time as the backup took above:
-
- ```
- server# cd /srv/leap/couchdb/scripts
- server# time ./couchdb_restoreall.sh
- ```
-
-1. start services again that were stopped in the beginning:
-
- ```
- workstation$ leap ssh soledad-nodes
- server# /etc/init.d/soledad-server start
-
- workstation$ leap ssh mx-node
- server# /etc/init.d/postfix start
- server# /etc/init.d/leap-mx start
-
- workstation$ leap ssh webapp
- server# /etc/init.d/nickserver start
- ```
-
- Or, alternately, if you set up the firewall rule instead, now remove it:
-
- ```
- server# iptables -D INPUT -p tcp --dport 5984 --jump REJECT
- ```
-
-1. check if everything is working, including running the test on your deployment machine:
-
- ```
- workstation$ leap test
- ```
-
-1. Remove old bigcouch data dir `/opt` after you double checked everything is in place
-
-1. Relax, enjoy a refreshing beverage.
diff --git a/doc/common/_bigcouch_migration_begin.md b/doc/common/_bigcouch_migration_begin.md
deleted file mode 100644
index 4e4233dd..00000000
--- a/doc/common/_bigcouch_migration_begin.md
+++ /dev/null
@@ -1,66 +0,0 @@
-@title = "Migrating from BigCouch to plain CouchDB"
-
-At the end of this process, you will have just *one* node with `services` property equal to `couchdb`. If you had a BigCouch cluster before, you will be removing all but one of those machines to consolidate them into one CouchDB machine.
-
-1. if you have multiple nodes with the `couchdb` service on them, pick one of them to be your CouchDB server, and remove the service from the others. If these machines were only doing BigCouch before, you can remove the nodes completely with `leap node rm <nodename>` and then you can decommission the servers
-
-1. put the webapp into [[maintenance mode => webapp#maintenance-mode]]
-
-1. turn off daemons that access the database. For example:
-
- ```
- workstation$ leap ssh <each soledad-node>
- server# /etc/init.d/soledad-server stop
-
- workstation$ leap ssh <mx-node>
- server# /etc/init.d/postfix stop
- server# /etc/init.d/leap-mx stop
-
- workstation$ leap ssh <webapp-node>
- server# /etc/init.d/nickserver stop
- ```
-
- Alternately, you can create a temporary firewall rule to block access (run on couchdb server):
-
- ```
- server# iptables -A INPUT -p tcp --dport 5984 --jump REJECT
- ```
-
-1. remove orphaned databases and do a backup of all remaining, active databases. This can take some time and will place several hundred megabytes of data into /var/backups/couchdb. The size and time depends on how many users there are on your system. For example, 15k users took approximately 25 minutes and 308M of space:
-
- ```
- workstation$ leap ssh <couchdb-node>
- server# cd /srv/leap/couchdb/scripts
- server# ./cleanup-user-dbs
- server# time ./couchdb_dumpall.sh
- ```
-
-1. stop bigcouch:
-
- ```
- server# /etc/init.d/bigcouch stop
- server# pkill epmd
- ```
-
-1. remove bigcouch:
-
- ```
- server# apt-get remove bigcouch
- ```
-
-1. configure your couch node to use plain couchdb instead of bigcouch, you can do this by editing nodes/<couch-node>.json, look for this section:
-
- ```
- "couch": {
- "mode": "plain"
- }
- ```
-change it, so it looks like this instead:
-
- ```
- "couch": {
- "mode": "plain",
- "pwhash_alg": "pbkdf2"
- }
- ```
-
diff --git a/doc/common/_bigcouch_migration_end.md b/doc/common/_bigcouch_migration_end.md
deleted file mode 100644
index a47d3c55..00000000
--- a/doc/common/_bigcouch_migration_end.md
+++ /dev/null
@@ -1,26 +0,0 @@
-1. restore the backup, this will take approximately the same amount of time as the backup took above:
-
- ```
- server# cd /srv/leap/couchdb/scripts
- server# time ./couchdb_restoreall.sh
- ```
-
-1. start services again that were stopped in the beginning:
-
- ```
- workstation$ leap ssh soledad-nodes
- server# /etc/init.d/soledad-server start
-
- workstation$ leap ssh mx-node
- server# /etc/init.d/postfix start
- server# /etc/init.d/leap-mx start
-
- workstation$ leap ssh webapp
- server# /etc/init.d/nickserver start
- ```
-
- Or, alternately, if you set up the firewall rule instead, now remove it:
-
- ```
- server# iptables -D INPUT -p tcp --dport 5984 --jump REJECT
- ```
diff --git a/doc/common/_bigcouch_migration_finish.md b/doc/common/_bigcouch_migration_finish.md
deleted file mode 100644
index 5aae9207..00000000
--- a/doc/common/_bigcouch_migration_finish.md
+++ /dev/null
@@ -1,10 +0,0 @@
-
-1. check if everything is working, including running the test on your deployment machine:
-
- ```
- workstation$ leap test
- ```
-
-1. Remove old bigcouch data dir `/opt` after you double checked everything is in place
-
-1. Relax, enjoy a refreshing beverage.
diff --git a/doc/details/development.md b/doc/details/development.md
deleted file mode 100644
index 78915add..00000000
--- a/doc/details/development.md
+++ /dev/null
@@ -1,78 +0,0 @@
-@title = 'Development'
-@summary = "Getting started with making changes to the LEAP platform"
-
-Installing leap_cli
-------------------------------------------------
-
-### From gem, for a single user
-
-Install the latest:
-
- gem install leap_cli --install-dir ~/leap
- export PATH=$PATH:~/leap/bin
-
-Install a particular version:
-
- gem install leap_cli --version 1.8 --install-dir ~/leap
- export PATH=$PATH:~/leap/bin
-
-### From gem, system wide
-
-Install the latest:
-
- sudo gem install leap_cli
-
-Install a particular version:
-
- sudo gem install leap_cli --version 1.8
-
-### As a gem, built from source
-
- sudo apt-get install ruby ruby-dev rake
- git clone https://leap.se/git/leap_cli.git
- cd leap_cli
- git checkout develop
- rake build
- sudo rake install
-
-### The "develop" branch from source, for a single user
-
- sudo apt-get install ruby ruby-dev rake
- git clone https://leap.se/git/leap_cli.git
- cd leap_cli
- git checkout develop
-
-Then do one of the following to be able to run `leap` command:
-
- cd leap_cli
- export PATH=$PATH:`pwd`/bin
- alias leap="`pwd`/bin/leap"
- ln -s `pwd`/bin/leap ~/bin/leap
-
-In practice, of course, you would put aliases or PATH modifications in a shell startup file.
-
-You can also clone from https://github.com/leap/leap_cli
-
-Running different leap_cli versions
----------------------------------------------
-
-### If installed as a gem
-
-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.7.2
- sudo gem install leap_cli --version 1.8
- leap _1.7.2_ --version
- => leap 1.7.2, ruby 2.1.2
- leap _1.8_ --version
- => leap 1.8, ruby 2.1.2
-
-### If running from source
-
-Alternately, if you are running from source, you can alias different commands:
-
- git clone https://leap.se/git/leap_cli.git
- cd leap_cli
- git checkout develop
- alias leap_develop="`pwd`/bin/leap`
-
diff --git a/doc/details/en.haml b/doc/details/en.haml
deleted file mode 100644
index fe7a4c84..00000000
--- a/doc/details/en.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-- @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
deleted file mode 100644
index 7ee20f4d..00000000
--- a/doc/details/faq.md
+++ /dev/null
@@ -1,71 +0,0 @@
-@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 ?
------------------------------------------------------
-
-Run:
-
- leap history FILTER
-
-This will tail the log file `/var/log/leap/deploy-summary.log`.
-
-If that command fails, you can manually check 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/ports.md b/doc/details/ports.md
deleted file mode 100644
index f7c485ca..00000000
--- a/doc/details/ports.md
+++ /dev/null
@@ -1,92 +0,0 @@
-@title = "Ports"
-@summary = "The required open ports for different services."
-@toc = true
-
-There are many different ports that must be open in order for the LEAP platform to work. Some ports must be *publicly open*, meaning that these should be accessible from the public internet. Other ports are *privately open*, meaning that they must be accessible to sysadmins or to the other nodes in the provider's infrastructure.
-
-Every node already includes a host-based firewall. However, if your network has its own firewall, you need to make sure that these ports are not blocked.
-
-Publicly open ports
---------------------------------
-
-<table class="table table-striped">
-<tr>
- <th>Name</th>
- <th>Node Type</th>
- <th>Default</th>
- <th>Notes</th>
-</tr>
-<tr>
- <td>SMTP</td>
- <td>mx</td>
- <td>25</td>
- <td>This is required for all server-to-server SMTP email relay. This is not configurable.</td>
-</tr>
-<tr>
- <td>HTTP</td>
- <td>webapp</td>
- <td>80</td>
- <td>Although no actual services are available over port 80, it should be unblocked so that the web app can redirect to port 443. This is not configurable.</td>
-</tr>
-<tr>
- <td>HTTPS</td>
- <td>webapp</td>
- <td>443</td>
- <td>The web application is available over this port. This is not configurable.</td>
-</tr>
-<tr>
- <td>SMTPS</td>
- <td>mx</td>
- <td>465</td>
- <td>The client uses this port to submit outgoing email messages via SMTP over TLS. There is no easy way to change this, although you can create a custom <code>files/service-definitions/v1/smtp-service.json.erb</code> to do so. This will be changed to port 443 in the future.</td>
-</tr>
-<tr>
- <td>Soledad</td>
- <td>soledad</td>
- <td>2323</td>
- <td>The client uses this port to synchronize its storage data. This can be changed via the configuration property <code>soledad.port</code>. This will be changed to port 443 in the future.</td>
-</tr>
-<tr>
- <td>Nicknym</td>
- <td>webapp</td>
- <td>6425</td>
- <td>The client uses this port for discovering public keys. This can be changed via the configuration property <code>nickserver.port</code>. This will be changed to port 443 in the future.</td>
-</tr>
-<tr>
- <td>OpenVPN</td>
- <td>openvpn</td>
- <td>80, 443, 53, 1194</td>
- <td>By default, OpenVPN gateways will listen on all those ports. This can be changed via the configuration property <code>openvpn.ports</code>. Note that these ports must be open for <code>openvpn.gateway_address</code>, not for <code>ip_address</code>.</td>
-</tr>
-<tr>
- <td>API</td>
- <td>webapp</td>
- <td>4430</td>
- <td>Currently, the provider API is accessible via this port. In the future, the default will be changed to 443. For now, this can be changed via the configuration property <code>api.port</code>.</td>
-</tr>
-</table>
-
-Privately open ports
----------------------------------------
-
-<table class="table table-striped">
-<tr>
- <th>Name</th>
- <th>Node Type</th>
- <th>Default</th>
- <th>Notes</th>
-</tr>
-<tr>
- <td>SSH</td>
- <td>all</td>
- <td>22</td>
- <td>This is the port that the sshd is bound to for the node. You can modify this using the configuration property <code>ssh.port</code>. It is important that this port is never blocked, or you will lose access to deploy to this node.</td>
-</tr>
-<tr>
- <td>Stunnel</td>
- <td>all</td>
- <td>10000-20000</td>
- <td>This is the range of ports that might be used for the encrypted stunnel connections between two nodes. These port numbers are automatically generated, but will fall somewhere in the specified range.</td>
-</tr>
-</table>
-
diff --git a/doc/details/under-the-hood.md b/doc/details/under-the-hood.md
deleted file mode 100644
index 0bc4fe77..00000000
--- a/doc/details/under-the-hood.md
+++ /dev/null
@@ -1,40 +0,0 @@
-@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/en.md b/doc/en.md
deleted file mode 100644
index 098aacb1..00000000
--- a/doc/en.md
+++ /dev/null
@@ -1,82 +0,0 @@
-@title = 'LEAP Platform for Service Providers'
-@summary = "The LEAP Platform is set of complementary packages and server recipes to automate the maintenance of LEAP services in a hardened Debian environment."
-@nav_title = 'Provider Platform'
-@this.toc = false
-
-Its goal is to make it as painless as possible for sysadmins to deploy and maintain a service provider's infrastructure for secure communication.
-
-**REQUIREMENTS** -- Before you begin, make sure you meet these requirements:
-
-* *Debian Servers*: Servers that you deploy to must be running **Debian Jessie**, and no other distribution or version.
-* *Real or Paravirtualized Servers*: Servers must be real machines or paravirtualized VMs (e.g. KVM, Xen, OpenStack, AWS, Google Compute). OS level virtualization is not supported (e.g. OpenVZ, Linux-VServer, etc), nor are system emulators (VirtualBox, QEMU, etc).
-* *Your Workstation*: You must have a Linux or Mac computer to deploy from (this can be a headless machine with no GUI). Windows is not supported (Cygwin would probably work, but is untested).
-* *Your Own Domain*: You must own a domain name. Before your provider can be put into production, you will need to make modifications to the DNS for the provider's domain.
-
-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 **lives on your workstation**, not on the server.
-
-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.
-
-Keep these rules in mind:
-
-* `leap` is run on your workstation: The `leap` command is always run locally on your workstation, never on a server you are deploying to.
-* `leap` is run from within a provider instance: The `leap` command requires that the current working directory is a valid provider instance, except when running `leap new` to create a new 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.
-
-Everything about your provider is managed by editing JSON configuration files and running `leap` commands.
-
-What is next?
-----------------------------------
-
-We recommend reading the platform documentation in the following order:
-
-1. [[quick-start]]
-2. [[getting-started]]
-3. [[platform/guide]]
-
diff --git a/doc/guide/commands.md b/doc/guide/commands.md
deleted file mode 100644
index 2ddacb83..00000000
--- a/doc/guide/commands.md
+++ /dev/null
@@ -1,559 +0,0 @@
-@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.
-
-* `-d|--debug`
-Print full stack trace for exceptions and load `debugger` gem if installed.
-
-* `--force`
-Like --yes, but also skip prompts that are potentially dangerous to skip.
-
-* `--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`, but may be overridden here.
-
-**Options**
-
-* `--bits BITS`
-Override default certificate bit length
-Default Value: None
-
-* `--country|-C COUNTRY`
-Set C in distinguished name.
-Default Value: None
-
-* `--digest DIGEST`
-Override default signature digest
-Default Value: None
-
-* `--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`, but may be overridden here.
-Default Value: None
-
-* `--email EMAIL`
-Set emailAddress in distinguished name.
-Default Value: None
-
-* `--locality|-L LOCALITY`
-Set L in distinguished name.
-Default Value: None
-
-* `--organization|-O ORGANIZATION`
-Override default O in distinguished name.
-Default Value: None
-
-* `--state|--ST STATE`
-Set ST in distinguished name.
-Default Value: None
-
-* `--unit|--OU UNIT`
-Set OU in distinguished name.
-Default Value: None
-
-
-## leap cert dh
-
-Creates a Diffie-Hellman parameter file, needed for forward secret OpenVPN ciphers.
-
-
-
-## 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 firewall
-
-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.
-
-
-
-## leap compile hosts
-
-Print entries suitable for an /etc/hosts file, useful for testing your provider.
-
-
-
-## leap compile provider.json
-
-Compile provider.json bootstrap files for your provider.
-
-
-
-## leap compile zone
-
-Prints a DNS zone file for your provider.
-
-
-Default Command: all
-
-# leap db
-
-Database commands.
-
-
-
-## leap db destroy [FILTER]
-
-Destroy one or more databases. If present, limit to FILTER nodes. For example `leap db destroy --db sessions,tokens testing`.
-
-
-
-**Options**
-
-* `--db DATABASES`
-Comma separated list of databases to destroy (no space). Use "--db all" to destroy all databases.
-Default Value: None
-
-* `--user USERS`
-Comma separated list of usernames. The storage databases for these user(s) will be destroyed.
-Default Value: None
-
-
-# leap debug FILTER
-
-Output debug information.
-
-The FILTER can be the name of a node, service, or tag.
-
-# 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: None
-
-* `--dev`
-Development mode: don't run 'git submodule update' before deploy.
-
-* `--downgrade`
-Allows deploy to run with an older platform version.
-
-* `--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.
-
-* `--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 [ENVIRONMENT]
-
-List the available environments. The pinned environment, if any, will be marked with '*'. Will also set the pin if run with an environment argument.
-
-
-
-## 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 history FILTER
-
-Display recent deployment history for a 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
-
-* `--last`
-Show last deploy only
-
-
-# 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)
-
-
-
-**Options**
-
-* `--basebox BASEBOX`
-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.
-Default Value: LEAP/jessie
-
-
-## 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).
-
-
-
-**Options**
-
-* `--port SSH_PORT`
-Override default SSH port used when trying to connect to the server. Same as `--ssh "-p SSH_PORT"`.
-Default Value: None
-
-* `--ssh arg`
-Pass through raw options to ssh (e.g. `--ssh '-F ~/sshconfig'`).
-Default Value: None
-
-
-# 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 scp FILE1 FILE2
-
-Secure copy from FILE1 to FILE2. Files are specified as NODE_NAME:FILE_PATH. For local paths, omit "NODE_NAME:".
-
-
-
-**Options**
-
-* `-r`
-Copy recursively
-
-
-# leap ssh NAME
-
-Log in to the specified node with an interactive shell.
-
-
-
-**Options**
-
-* `--port SSH_PORT`
-Override default SSH port used when trying to connect to the server. Same as `--ssh "-p SSH_PORT"`.
-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 [FILTER]
-
-Run the test suit on FILTER nodes.
-
-
-
-**Options**
-
-* `--[no-]continue`
-Continue over errors and failures (default is --no-continue).
-
-Default Command: run
-
-# leap tunnel [LOCAL_PORT:]NAME:REMOTE_PORT
-
-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`.
-
-
-
-**Options**
-
-* `--port SSH_PORT`
-Override default SSH port used when trying to connect to the server. Same as `--ssh "-p SSH_PORT"`.
-Default Value: None
-
-* `--ssh arg`
-Pass through raw options to ssh (e.g. --ssh '-F ~/sshconfig').
-Default Value: None
-
diff --git a/doc/guide/config.md b/doc/guide/config.md
deleted file mode 100644
index bcea26c4..00000000
--- a/doc/guide/config.md
+++ /dev/null
@@ -1,360 +0,0 @@
-@title = "Configuration Files"
-@summary = "Understanding and editing the 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).
-
-<table class="table table-striped">
-<tr>
- <td><code>Leapfile</code></td>
- <td>If present, this file tells <code>leap</code> that the directory is a provider directory. This file is usually empty, but can contain global options.</td>
-</tr>
-<tr>
- <td><code>~/.leaprc</code></td>
- <td>Evaluated the same as Leapfile, but not committed to source control.</td>
-</tr>
-<tr>
- <td><code>provider.json</code></td>
- <td>Global options related to this provider. See [[provider-configuration]].</td>
-</tr>
-<tr>
- <td><code>provider.ENVIRONMENT.json</code></td>
- <td>Global options for the provider that are applied to only a single environment.</td>
-</tr>
-<tr>
- <td><code>nodes/NAME.json</code></td>
- <td>The configuration file for node called NAME.</td>
-</tr>
-<tr>
- <td><code>common.json</code></td>
- <td>All nodes inherit from this file. In other words, any options that appear in <code>common.json</code> will be added as default values to each node configuration, value that can be locally overridden.</td>
-</tr>
-<tr>
- <td><code>services/SERVICE.json</code></td>
- <td>The properties in this configuration file are applied to any node that includes SERVICE in its <code>services</code> property.</td>
-</tr>
-<tr>
- <td><code>services/SERVICE.ENVIRONMENT.json</code></td>
- <td>The properties in this configuration file are applied to any node that includes SERVICE in its services and has environment equal to ENVIRONMENT.</td>
-</tr>
-<tr>
- <td><code>tags/TAG.json</code></td>
- <td>The properties in this configuration file are applied to any node that has includes TAG in its <code>tags</code> property.</td>
-</tr>
-<tr>
- <td><code>tags/TAG.ENVIRONMENT.json</code></td>
- <td>The properties in this configuration file are applied to any node that has includes TAG in its <code>tags</code> property and has <code>environment</code> property equal to ENVIRONMENT.</td>
-</tr>
-<tr>
- <td><code>secrets.json </code></td>
- <td>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 <code>leap compile</code> or <code>leap deploy</code>. 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.</td>
-</tr>
-<tr>
- <td><code>facts.json</code></td>
- <td>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 <code>leap facts update</code> to periodically regenerate the facts.json file.</td>
-</tr>
-<tr>
- <td><code>files/*</code></td>
- <td>Various static files used by the platform (e.g. keys, certificates, webapp customization, etc). In general, only generated files and files used to customize the provider (such as images) live in the <code>files</code> directory.</td>
-</tr>
-<tr>
- <td><code>users/USER/</code></td>
- <td>A directory that stores the public keys of the sysadmin with name USER. This person will have root access to all the servers.</td>
-</tr>
-</table>
-
-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_provider`. Changes the default vagrant provider ("virtualbox"). For example, `@vagrant_provider = "libvirt"`.
-* `@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"`.
-* `@vagrant_basebox` allows specifying a different basebox as the default one. For example, `@vagrant_basebox = "LEAP/jessie"`.
-
-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_]/` and must be in double quotes.
-
-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`.
-
-### Inheritance rules
-
-Suppose you have a node configuration `mynode.json`:
-
- {
- "tags": "production",
- "simple_value": 100,
- "replaced_array": ["dolphin", "kangaroo"],
- "+add_array": ["red", "black"],
- "-subtract_array": ["bitter"],
- "converted_to_array": "not_array_element",
- "!override": ["insist on this value"],
- "hash": {
- "key1": 1,
- "key2": 2
- }
- }
-
-And a file `tags/production.json`:
-
- {
- "simple_value": 99999,
- "replaced_array": ["zebra"],
- "add_array": ["green],
- "subtract_array": ["bitter", "sweet", "salty"],
- "converted_to_array": ["array_element"],
- "override": "this value will be overridden",
- "hash": {
- "key1": "one"
- }
- }
-
-In this scenario, `mynode.json` will inherit from `production.json`. The output of this inheritance will be:
-
- {
- "tags": "production",
- "simple_value": 100,
- "replaced_array": ["dolphin", "kangaroo"],
- "add_array": ["red", "black", "green"],
- "subtract_array": ["sweet", "salty"],
- "converted_to_array": ["not_array_element", "array_element"],
- "override": ["insist on this value"],
- "hash": {
- "key1": 1,
- "key2": 2
- }
-
-The rules for inheritance (where 'old' refers to the parent, and 'new' refers to the child):
-
-* Simple values (strings, numbers, boolean):
- * Replace the old value with the new value.
-* Array values:
- * Two arrays: replace the old array with the new array.
- * One array and one simple value: add the simple value to the array.
- * If property name is prefixed with "+": merge the old and new arrays.
- * If property name is prefixed with "-": subtract new array from old array.
-* Hash values:
- * Hashes are always merged (the result includes the keys of both hashes). If there is a key in common, the new one overrides the old one.
-* Mismatch:
- * Although you can mix arrays and simple values, you cannot mix arrays with hashes or hashes with simple values. If you attempt to do so, it will fail to compile and give you an error message.
-* Override:
- * If property name is prefixed with "!": then ensure that new value is always used, regardless of old value. In this case, the override takes precedence over type checking, so you will never get a type mismatch.
-
-NOTE: special property name prefixes, like "+", "-", or "!", are not included in the property name. These prefixes determine the merge strategy, but are stripped out when compiling the resulting JSON file.
-
-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/domains.md b/doc/guide/domains.md
deleted file mode 100644
index 914bce33..00000000
--- a/doc/guide/domains.md
+++ /dev/null
@@ -1,129 +0,0 @@
-@title = "Domains"
-@summary = "How to handle domain names and integrating LEAP with existing services."
-@toc = true
-
-Overview
---------------------------------
-
-Deploying LEAP can start to get very tricky when you need to integrate LEAP services with an existing domain that you already use or which already has users. Most of this complexity is unavoidable, although there are a few things we plan to do in the future to make this a little less painful.
-
-Because integration with legacy systems is an advanced topic, we recommend that you begin with a new domain. Once everything works and you are comfortable with your LEAP-powered infrastructure, you can then contemplate integrating with your existing domain.
-
-### Definitions
-
-**provider domain**
-
-This is the main domain used to identify the provider. The **provider domain** is what the user enters in the Bitmask client. e.g. `example.org`. The full host name of every node in your provider infrastructure will use the **provider domain** (e.g. `dbnode.example.org`).
-
-In order for the Bitmask client to get configured for use with a provider, it must be able to find the `provider.json` bootstrap file at the root of the **provider domain**. This is not needed if the Bitmask client is "pre-seeded" with the provider's information (these providers show up in a the initial list of available providers).
-
-**webapp domain**
-
-This is the domain that runs the leap_web application that allows users to register accounts, create help tickets, etc. e.g. `example.org` or `user.example.org`. The **webapp domain** defaults to the **provider domain** unless it is explicitly configured separately.
-
-**API domain**
-
-This is the domain that the provider API runs on. Typically, this is set automatically and you never need to configure it. The user should never be aware of this domain. e.g. `api.example.org`. The Bitmask client discovers this API domain by reading it from the `provider.json` file it grabs from the **provider domain**.
-
-**mail domain**
-
-This is the domain used for mail accounts, e.g. `username@example.org`. Currently, this is always the **provider domain**, but it may be independently configurable in the future.
-
-Generating a zone file
------------------------------------
-
-Currently, the platform does not include a dedicated `dns` service type, so you need to have your own setup for DNS. You can generate the appropriate configuration options with this command:
-
- leap compile zone
-
-A single domain
--------------------------------
-
-The easy approach is to use a single domain for **provider domain**, **webapp domain**, and **email domain**. This will install the webapp on the **provider domain**, which means that this domain must be a new one that you are not currently using for anything.
-
-To configure a single domain, just set the domain in `provider.json`:
-
- {
- "domain": "example.org"
- }
-
-If you have multiple environments, you can specify a different **provider domain** for each environment. For example:
-
-`provider.staging.json`
-
- {
- "domain": "staging.example.org"
- }
-
-A separate domain for the webapp
---------------------------------------
-
-It is possible make the **webapp domain** different than the **provider domain**. This is needed if you already have a website running at your **provider domain**.
-
-In order to put webapp on a different domain, you must take two steps:
-
-1. You must configure `webapp.domain` for nodes with the `webapp` service.
-2. You must make the compiled `provider.json` available at the root of the **provider domain**.
-
-NOTE: This compiled provider.json is different than the provider.json that you edit and lives in the root of the provider directory.
-
-### Step 1. Configuring `webapp.domain`
-
-In `services/webapp.json`:
-
- {
- "webapp": {
- "domain": "user.example.org"
- }
- }
-
-### Step 2. Putting the compiled `provider.json` in place
-
-Generate the compiled `provider.json`:
-
- leap compile provider.json
- = created files/web/bootstrap/
- = created files/web/bootstrap/README
- = created files/web/bootstrap/production/
- = created files/web/bootstrap/production/provider.json
- = created files/web/bootstrap/production/htaccess
- = created files/web/bootstrap/staging/
- = created files/web/bootstrap/staging/provider.json
- = created files/web/bootstrap/staging/htaccess
-
-This command compiles a separate `provider.json` for each environment, or "default" if you don't have an environment. In the example above, there is an environment called "production" and one called "staging", but your setup will probably differ.
-
-The resulting `provider.json` file must then be put at the root URL of your **provider domain** for the appropriate environment.
-
-There is one additional complication: currently, the Bitmask client tests for compatibility using some HTTP headers on the `/provider.json` response. This is will hopefully change in the future, but for now you need to ensure the right headers are set in the response. The included file `htaccess` has example directives for Apache, if that is what you use.
-
-This step can be skipped if you happen to use the `static` service to deploy an `amber` powered static website to **provider domain**. In this case, the correct `provider.json` will be automatically put into place.
-
-Integrating with existing email system
------------------------------------------
-
-If your **mail domain** already has users from a legacy email system, then things get a bit complicated. In order to be able to support both LEAP-powered email and legacy email on the same domain, you need to follow these steps:
-
-1. Modify the LEAP webapp so that it does not create users with the same name as users in the legacy system.
-2. Configure your legacy MX servers to forward mail that they cannot handle to the LEAP MX servers, or vice versa.
-
-### Step 1. Modify LEAP webapp
-
-In order to modify the webapp to respect the usernames already reserved by your legacy system, you need to modify the LEAP webapp code. The easiest way to do this is to create a custom gem that modifies the behavior of the webapp.
-
-For this example, we will call our custom gem `reserve_usernames`.
-
-This gem can live in one of two places:
-
-(1) You can fork the project leap_web and put the gem in `leap_web/vendor/gems/reserve_usernames`. Then, modify `Gemfile` and add the line `gem 'common_languages', :path => 'vendor/gems/reserve_usernames'`
-
-(2) Alternately, you can put the gem in the local provider directory `files/webapp/gems/reserve_username`. This will get synced to the webapp servers when you deploy and put in `/srv/leap/webapp/config/customization` where it will get automatically loaded by the webapp.
-
-What should the gem `reserve_usernames` look like? There is an example available here: https://leap.se/git/reserved_usernames.git
-
-This example gem uses ActiveResource to communicate with a remote REST API for creating and checking username reservations. This ensures that both the legacy system and the LEAP system use the same namespace. Alternately, you could write a gem that checks the legacy database directly.
-
-### Step 2. Configure MX servers
-
-To be written.
-
diff --git a/doc/guide/en.haml b/doc/guide/en.haml
deleted file mode 100644
index 61c24ea8..00000000
--- a/doc/guide/en.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-- @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
deleted file mode 100644
index 752e0608..00000000
--- a/doc/guide/environments.md
+++ /dev/null
@@ -1,75 +0,0 @@
-@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/getting-started.md b/doc/guide/getting-started.md
deleted file mode 100644
index 6236cba0..00000000
--- a/doc/guide/getting-started.md
+++ /dev/null
@@ -1,145 +0,0 @@
-@title = 'Getting Started'
-@summary = 'An overview of the LEAP Platform'
-@toc = true
-
-
-Sensitive files
-----------------------------------------------
-
-Some files in your provider directory are very sensitive. Leaking these files will compromise your provider.
-
-Super sensitive and irreplaceable:
-
-* `files/ca/*.key` -- the private keys for the client and server CAs.
-* `files/cert/*.key` -- the private key(s) for the commercial certificate for your domain(s).
-
-Sensitive, but can be erased and regenerated automatically:
-
-* `secrets.json` -- various random secrets, such as passwords for databases.
-* `files/nodes/*/*.key` -- the private key for each node.
-* `hiera/*.yaml` -- hiera file contains a copy of the private key of the node.
-
-Also, each sysadmin has one or more public ssh keys in `users/*/*_ssh.pub`. Typically, you will want to keep these public keys secure as well.
-
-See [[keys-and-certificates]] for more information.
-
-Useful commands
--------------------------------------------
-
-Here are a few useful `leap` commands:
-
-* `leap help [COMMAND]` -- get help on COMMAND.
-* `leap history [FILTER]` -- show the recent deployment history for the selected nodes.
-* `leap ssh web1` -- SSH into node web1 (requires `leap node init web1` first).
-* `leap list [FILTER]` -- list the selected nodes.
- * `leap list production` -- list only those nodes with the tag 'production'
- * `leap list --print ip_address` -- list a particular attribute of all nodes.
-
-See the full [[commands]] 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.
-
-See the full [[commands]] for more information.
-
-Tracking the provider directory in git
-------------------------------------------
-
-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, such as private key material and passwords. 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.
-
-If you have a post-commit hook that emails the changes to contributors, you may want to exclude diffs for files that might have sensitive secrets. For example, create a `.gitattributes` file with:
-
- # No diff, no email for key files
- *.key -diff
- *.pem -diff
-
- # Discard diff for secrets.json
- secrets.json -diff
-
- # No diff for hiera files, they contain passwords
- hiera/* -diff
-
-
-Editing JSON configuration files
---------------------------------------
-
-All the settings that compose your provider are stored in JSON files.
-
-At a minimum, you will need at least two configuration files:
-
-* `provider.json` -- general settings for you provider.
-* `nodes/NAME.json` -- configuration file for node called "NAME".
-
-There are a few required properties in provider.json:
-
- {
- "domain": "example.org",
- "name": "Example",
- "contacts": {
- "default": "email1@example.org"
- }
- }
-
-See [[provider-configuration]] for more details.
-
-For node configuration files, there are two required properties:
-
- {
- "ip_address": "1.1.1.1",
- "services": ["openvpn"]
- }
-
-See [[services]] for details on what servers are available, and see [[config]] details on how configuration files work.
-
-How does it work under the hood?
---------------------------------------------
-
-You don't need to know any of the details of what happens "under the hood" in order to use the LEAP platform. However, if you are curious as to what is going on, here is a quick primer.
-
-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.
-
-This mode of operation is fundamentally different from how puppet is normally used:
-
-* There is no puppetmaster that all the servers take orders from, and there is no puppetd running in the background.
-* Servers cannot dynamically query the puppetmaster for information about the other servers.
-* There is a static representation for the state of every server that can be committed to git.
-
-There are advantages and disadvantages to the model that LEAP uses. We have found it very useful for our goal of having a common LEAP platform that many different providers can all use while still allowing providers to configure their unique infrastructure.
-
-We also find it very beneficial to be able to track the state of your infrastructure in git.
-
-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.
diff --git a/doc/guide/keys-and-certificates.md b/doc/guide/keys-and-certificates.md
deleted file mode 100644
index a6862a6a..00000000
--- a/doc/guide/keys-and-certificates.md
+++ /dev/null
@@ -1,272 +0,0 @@
-@title = "Keys and Certificates"
-@summary = "Working with SSH keys, secrets, and X.509 certificates."
-
-Working with SSH
-================================
-
-Whenever the `leap` command needs 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.
-
-To upgrade a SSH host key
--------------------------------
-
-Most servers will have more than one SSH host key. Sometimes, the server will have a better SSH host key than the one you have on file. In order to upgrade to the better SSH host key, simply re-run the init command:
-
- workstation$ leap node init NODE_NAME
-
-This will prompt you if you want to upgrade the SSH host key, but only if `leap` thinks that an upgrade is advisable.
-
-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.
-
-Examine Certs
------------------
-
-To see details about the keys and certs you can use `leap inspect` like so:
-
- $ leap inspect files/ca/ca.crt
-
-
-Let's Encrypt certificate
-=========================
-
-LEAP plans to integrate [Let's Encrypt](https://letsencrypt.org/) support, so it will be even easier to receive X.509 certificates that are accepted by all browsers.
-Until we achieve this, here's a guide how to do this manually.
-
-Install the official acme client
---------------------------------
-
-Log in to your webapp node
-
- server$ git clone https://github.com/letsencrypt/letsencrypt
- server$ cd letsencrypt
- server$ ./letsencrypt-auto --help
-
-Fetch cert
-----------
-
-Stop apache so the letsencrypt client can bind to port 80:
-
- server$ systemctl stop apache2
-
-Fetch the certs
-
- server$ ./letsencrypt-auto certonly --standalone --email admin@$(hostname -d) -d $(hostname -d) -d api.$(hostname -d) -d $(hostname -f) -d nicknym.$(hostname -d)
-
-This will put the certs and keys into `/etc/letsencrypt/live/DOMAIN/`.
-
-Now, go to your workstation's provider configuration directory and copy the newly created files from the server to your local config. You will override existing files so please make a backup before proceeding, or use a version control system to track changes.
-
- workstation$ cd PATH_TO_PROVIDER_CONFIG
-
-Copy the Certificate
-
- workstation$ scp root@SERVER:/etc/letsencrypt/live/DOMAIN/cert.pem files/cert/dev.pixelated-project.org.crt
-
-Copy the private key
-
- workstation$ scp root@SERVER:/etc/letsencrypt/live/DOMAIN/privkey.pem files/cert/DOMAIN.key
-
-Copy the CA chain cert
-
- workstation$ scp root@SERVER:/etc/letsencrypt/live/DOMAIN/fullchain.pem files/cert/DOMAIN.key
-
-Deploy the certs
-----------------
-
-Now you only need to deploy the certs
-
- workstation$ leap deploy
-
-This will put them into the right locations which are:
-
-- `/etc/x509/certs/leap_commercial.crt` for the certificate
-- `/etc/x509/./keys/leap_commercial.key` for the private key
-- `/usr/local/share/ca-certificates/leap_commercial_ca.crt` for the CA chain cert.
-
-Start apache2 again
-
- server$ systemctl start apache2
diff --git a/doc/guide/miscellaneous.md b/doc/guide/miscellaneous.md
deleted file mode 100644
index c38c007c..00000000
--- a/doc/guide/miscellaneous.md
+++ /dev/null
@@ -1,14 +0,0 @@
-@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
deleted file mode 100644
index 5135f3ba..00000000
--- a/doc/guide/nodes.md
+++ /dev/null
@@ -1,87 +0,0 @@
-@title = "Nodes"
-@summary = "Working with nodes, services, tags, and locations."
-
-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/guide/provider-configuration.md b/doc/guide/provider-configuration.md
deleted file mode 100644
index 08cfd1dd..00000000
--- a/doc/guide/provider-configuration.md
+++ /dev/null
@@ -1,79 +0,0 @@
-@title = "Provider Configuration"
-@summary = "Explore how to configure your provider."
-
-Required provider configuration
---------------------------------------
-
-There are a few required settings in `provider.json`. At a minimum, you must have:
-
-* `domain`: defines the primary domain of the provider. This is the domain that users will type in when using the Bitmask client, although it is not necessarily the domain where users will visit if they sign up via the web application. If email is supported, all accounts will be `username@domain`.
-* `name`: A brief title for this provider. It can be multiple words, but should not be too long.
-* `contacts.default`: One or more email addresses for sysadmins.
-
-For example:
-
- {
- "domain": "freerobot.org",
- "name": "Freedom for Robots!",
- "contacts": {
- "default": "root@freerobot.org"
- }
- }
-
-
-Recommended provider configuration
---------------------------------------
-
-* `description`: A longer description of the provider, shown to the user when they register a new account through Bitmask client.
-* `languages`: A list of language codes that should be enabled.
-* `default_language`: The initial default language code.
-* `enrollment_policy`: One of "open", "closed", or "invite". (invite not currently supported).
-
-For example:
-
- {
- "description": "It is time for robots of the world to unite and throw of the shackles of servitude to our organic overlords.",
- "languages": ["en", "de", "pt", "01"],
- "default_language": "01",
- "enrollman_policy": "open"
- }
-
-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
-
-Configuring service levels
---------------------------------------
-
-The `provider.json` file defines the available service levels for the provider.
-
-For example, in provider.json:
-
- "service": {
- "default_service_level": "low",
- "levels": {
- "low": {
- "description": "Entry level plan, with unlimited bandwidth and minimal storage quota.",
- "name": "entry",
- "storage": "10 MB",
- "rate": {
- "USD": 5,
- "GBP": 3,
- "EUR": 6
- }
- },
- "full": {
- "description": "Full plan, with unlimited bandwidth and higher quota."
- "name": "full",
- "storage": "5 GB",
- "rate": {
- "USD": 10,
- "GBP": 6,
- "EUR": 12
- }
- }
- }
- }
- }
-
-For a list of currency codes, see https://en.wikipedia.org/wiki/ISO_4217#Active_codes
diff --git a/doc/service-diagram.odg b/doc/service-diagram.odg
deleted file mode 100644
index 09265c2d..00000000
--- a/doc/service-diagram.odg
+++ /dev/null
Binary files differ
diff --git a/doc/service-diagram.png b/doc/service-diagram.png
deleted file mode 100644
index 85e62436..00000000
--- a/doc/service-diagram.png
+++ /dev/null
Binary files differ
diff --git a/doc/services/couchdb.md b/doc/services/couchdb.md
deleted file mode 100644
index cc40dc32..00000000
--- a/doc/services/couchdb.md
+++ /dev/null
@@ -1,159 +0,0 @@
-@title = "couchdb"
-@summary = "Data storage for all user data."
-
-Topology
-------------------------
-
-Required:
-
-* Nodes with `couchdb` service must also have `soledad` service, if email is enabled.
-
-Suggested:
-
-* Nodes with `couchdb` service communicate heavily with `webapp` and `mx`.
-
-`couchdb` nodes do not need to be reachable from the public internet, although the `soledad` service does require this.
-
-Configuration
-----------------------------
-
-### Nighly dumps
-
-You can do a nightly couchdb data dump by adding this to your node config:
-
- "couch": {
- "backup": true
- }
-
-Data will get dumped to `/var/backups/couchdb`.
-
-### Plain CouchDB
-
-BigCouch is not supported on Platform version 0.8 and higher: only plain CouchDB is possible. For earlier versions, you must do this in order to use plain CouchDB:
-
- "couch": {
- "master": true,
- "pwhash_alg": "pbkdf2"
- }
-
-Various Tasks
--------------------------------------------------
-
-### 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 people can't claim that account without an admin's intervention. You can remove this username reservation through the webapp.
-
-However, here is how you could do it manually, if you wanted to:
-
-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
-
-
-Deprecated BigCouch Tasks
------------------------------------------
-
-As of release 0.8, the LEAP platform no longer supports BigCouch. This information is kept here for historical reference.
-
-### 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,
-
-1. make sure you have a backup of all DBs !
-
-1. put the webapp into [[maintenance mode => services/webapp#maintenance-mode]]
-
-1. Stop all services that access the database:
-
- ```
- workstation$ leap ssh soledad-nodes
- server# /etc/init.d/soledad-server stop
-
- workstation$ leap ssh mx-node
- server# /etc/init.d/postfix stop
- server# /etc/init.d/leap-mx stop
-
- workstation$ leap ssh webapp
- server# /etc/init.d/nickserver stop
- ```
-
- Alternately, you can create a temporary firewall rule to block access (run on couchdb server):
-
- ```
- server# iptables -A INPUT -p tcp --dport 5984 --jump REJECT
- ```
-
-1. dump the dbs:
-
- ```
- cd /srv/leap/couchdb/scripts
- time ./couchdb_dumpall.sh
- ```
-
-1. delete all dbs
-
-1. shut down old node
-
-1. 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
- ```
-
-1. remove bigcouch from all nodes
-
- ```
- apt-get --purge remove bigcouch
- ```
-
-1. deploy to all couch nodes
-
- ```
- leap deploy couchdb
- ```
-
-1. most likely, deploy will fail because bigcouch will complain about not all nodes beeing connected. Let the deploy finish, restart the bigcouch service on all nodes and re-deploy:
-
- ```
- /etc/init.d/bigcouch restart
- ```
-
-1. restore the backup
-
- ```
- cd /srv/leap/couchdb/scripts
- time ./couchdb_restoreall.sh
- ```
-
-### Migrating from BigCouch to plain CouchDB
-
-<%= render :partial => 'docs/platform/common/bigcouch_migration_begin.md' %>
-
-
-<%= render :partial => 'docs/platform/common/bigcouch_migration_end.md' %>
-
-
-<%= render :partial => 'docs/platform/common/bigcouch_migration_finish.md' %>
diff --git a/doc/services/en.md b/doc/services/en.md
deleted file mode 100644
index 5d0fec5f..00000000
--- a/doc/services/en.md
+++ /dev/null
@@ -1,80 +0,0 @@
-@nav_title = "Services"
-@title = "Guide to node services"
-@summary = ""
-@toc = true
-
-# Introduction
-
-Every node (server) must have one or more `services` defined that determines what role the node performs. For example:
-
- workstation$ cat nodes/stallman.json
- {
- "ip_address": "199.99.99.1",
- "services": ["webapp", "tor"]
- }
-
-Here are common questions to ask when adding a new node to your provider:
-
-* **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 network?** Some services communicate with the public internet, while others only need to communicate with other nodes in the infrastructure.
-
-# Available services
-
-<table class="table table-striped">
-<tr>
- <th>Service</th>
- <th>VPN</th>
- <th>Email</th>
- <th>Notes</th>
-</tr>
-<tr>
- <td>webapp</td>
- <td><i class="fa fa-circle"></i></td>
- <td><i class="fa fa-circle"></i></td>
- <td>User control panel, provider API, and support system.</td>
-</tr>
-<tr>
- <td>couchdb</td>
- <td><i class="fa fa-circle"></i></td>
- <td><i class="fa fa-circle"></i></td>
- <td>Data storage for everything. Private node.</td>
-<td></td>
-</tr>
-<tr>
- <td>soledad</td>
- <td><i class="fa fa-circle-o"></i></td>
- <td><i class="fa fa-circle"></i></td>
- <td>User data synchronization daemon. Usually paired with <code>couchdb</code> nodes.</td>
-<td></td>
-</tr>
-<tr>
- <td>mx</td>
- <td><i class="fa fa-circle-o"></i></td>
- <td><i class="fa fa-circle"></i></td>
- <td>Incoming and outgoing MX servers.</td>
-</tr>
-<tr>
- <td>openvpn</td>
- <td><i class="fa fa-circle"></i></td>
- <td><i class="fa fa-circle-o"></i></td>
- <td>OpenVPN gateways.</td>
-</tr>
-<tr>
- <td>monitor</td>
- <td><i class="fa fa-dot-circle-o"></i></td>
- <td><i class="fa fa-dot-circle-o"></i></td>
- <td>Nagios monitoring. This service must be on the webapp node.</td>
-</tr>
-<tr>
- <td>tor</td>
- <td><i class="fa fa-dot-circle-o"></i></td>
- <td><i class="fa fa-dot-circle-o"></i></td>
- <td>Tor exit node.</td>
-</tr>
-</table>
-
-Key: <i class="fa fa-circle"> Required</i>, <i class="fa fa-dot-circle-o"> Optional</i>, <i class="fa fa-circle-o"> Not Used</i>
-
-<%= child_summaries %> \ No newline at end of file
diff --git a/doc/services/monitor.md b/doc/services/monitor.md
deleted file mode 100644
index 576b36a9..00000000
--- a/doc/services/monitor.md
+++ /dev/null
@@ -1,36 +0,0 @@
-@title = "monitor"
-@summary = "Nagios monitoring and continuous testing."
-
-The `monitor` node provides a nagios control panel that will give you a view into the health and status of all the servers and all the services. It will also spam you with alerts if something goes down.
-
-Topology
---------------------------------------
-
-Currently, you can have zero or one `monitor` nodes defined. It is required that the monitor be on the webapp node. It was not designed to be run as a separate node service.
-
-Configuration
------------------------------------------------
-
-* `nagios.environments`: By default, the monitor node will monitor all servers in all environments. You can optionally restrict the environments to the ones you specify.
-
-For example:
-
- {
- "nagios": {
- "environments": ["unstable", "production"]
- }
- }
-
-Access nagios web
------------------------------------------------
-
-*Determine the nagios URL*
-
- $ leap ls --print domain.name,webapp.domain,ip_address monitor
- > chameleon chameleon.bitmask.net, demo.bitmask.net, 199.119.112.10
-
-In this case, you would open `https://demo.bitmask.net/cgi-bin/nagios3` in your browser (or alternately you could use 199.119.112.10 or chameleon.bitmask.net).
-
-*Determine the nagios password*
-
-The username for nagios is always `nagiosadmin`. The password is randomly generated and stored in `secrets.json` under the key `nagios_admin_password`. Note that the login is `nagiosadmin` without underscore, but the entry in secrets.json is with underscores.
diff --git a/doc/services/mx.md b/doc/services/mx.md
deleted file mode 100644
index 1a34b660..00000000
--- a/doc/services/mx.md
+++ /dev/null
@@ -1,35 +0,0 @@
-@title = "mx"
-@summary = "Incoming and outgoing MX servers."
-
-Topology
--------------------
-
-`mx` nodes communicate with the public internet, clients, and `couchdb` nodes.
-
-Configuration
---------------------
-
-### Aliases
-
-Using the `mx.aliases` property, you can specify your own hard-coded email aliases that precedence over the aliases in the user database. The `mx.aliases` property consists of a hash, where source address points to one or more destination addresses.
-
-For example:
-
-`services/mx.json`:
-
- "mx": {
- "aliases": {
- "rook": "crow",
- "robin": "robin@bird.org",
- "flock": ["junco@bird.org", "robin", "crow"],
- "chickadee@avian.org": "chickadee@bird.org",
- "flicker": ["flicker@bird.org", "flicker@deliver.local"]
- }
- }
-
-This example demonstrates several of the features with `mx.aliases`:
-
-1. alias lists: by specifying an array of destination addresses, as in the case of "flock", the single email will get copied to each address.
-1. chained resolution: alias resolution will recursively continue until there are no more matching aliases. For example, "flock" is resolved to "robin", which then gets resolved to "robin@bird.org".
-1. virtual domains: by specifying the full domain, as in the case of "chickadee@avian.org", the alias will work for any domain you want. Of course, the MX record for that domain must point to appropriate MX servers, but otherwise you don't need to do any additional configuration.
-1. local delivery: for testing purposes, it is often useful to copy all incoming mail for a particular address and send those copies to another address. You can do this by adding "@deliver.local" as one of the destination addresses. When "@local.delivery" is found, alias resolution stops and the mail is delivered to that username.
diff --git a/doc/services/openvpn.md b/doc/services/openvpn.md
deleted file mode 100644
index 5f15ff07..00000000
--- a/doc/services/openvpn.md
+++ /dev/null
@@ -1,49 +0,0 @@
-@title = 'openvpn'
-@summary = "OpenVPN egress gateways"
-
-Topology
-------------------
-
-Currently, `openvpn` service should not be combined with other services on the same node.
-
-Unlike most of the other node types, the `openvpn` nodes do not need access to the database and does not ever communicate with any other nodes (except for the `monitor` node, if used). So, `openvpn` nodes can be placed anywhere without regard to the other nodes.
-
-Configuration
----------------------
-
-*Essential configuration*
-
-* `openvpn.gateway_address`: The address that OpenVPN daemon is bound to and that VPN clients connect to.
-* `ip_address`: The main IP of the server, and the egress address for outgoing traffic.
-
-For example:
-
- {
- "ip_address": "1.1.1.1",
- "openvpn": {
- "gateway_address": "2.2.2.2"
- }
- }
-
-In this example, VPN clients will connect to 2.2.2.2, but their traffic will appear to come from 1.1.1.1.
-
-Why are two IP addresses needed? Without this, traffic between two VPN users on the same gateway will not get encrypted. This is because the VPN on every client must be configured to allow cleartext traffic for the IP address that is the VPN gateway.
-
-*Optional configuration*
-
-Here is the default configuration:
-
- "openvpn": {
- "configuration": {
- "auth": "SHA1",
- "cipher": "AES-128-CBC",
- "fragment": 1400,
- "keepalive": "10 30",
- "tls-cipher": "DHE-RSA-AES128-SHA",
- "tun-ipv6": true
- },
- "ports": ["80", "443", "53", "1194"],
- "protocols": ["tcp", "udp"]
- }
-
-You may want to change the ports so that only 443 or 80 are used. It is probably best to not modify the `openvpn.configuration` options for now. \ No newline at end of file
diff --git a/doc/services/soledad.md b/doc/services/soledad.md
deleted file mode 100644
index e2700d06..00000000
--- a/doc/services/soledad.md
+++ /dev/null
@@ -1,12 +0,0 @@
-@title = 'soledad'
-@summary = 'User data synchronization daemon'
-
-Topology
---------------------
-
-Currently, the platform is designed for `soledad` and `couchdb` services to be combined (e.g. every `soledad` node should also be a `couchdb` node). `soledad` nodes might work in isolation, but this is not tested.
-
-Configuration
-------------------------
-
-There are no options to configure for `soledad` nodes.
diff --git a/doc/services/tor.md b/doc/services/tor.md
deleted file mode 100644
index e64b0fe0..00000000
--- a/doc/services/tor.md
+++ /dev/null
@@ -1,32 +0,0 @@
-@title = 'tor'
-@summary = 'Tor exit node or hidden service'
-
-Topology
-------------------------
-
-Nodes with `tor` service will run a Tor exit or hidden service, depending on what other service it is paired with:
-
-* `tor` + `openvpn`: when combined with `openvpn` nodes, `tor` will create a Tor exit node to provide extra cover traffic for the VPN. This can be especially useful if there are VPN gateways without much traffic.
-* `tor` + `webapp`: when combined with a `webapp` node, the `tor` service will make the webapp and the API available via .onion hidden service.
-* `tor` stand alone: a regular Tor exit node.
-
-If activated, you can list the hidden service .onion addresses this way:
-
- leap ls --print tor.hidden_service.address tor
-
-Then just add '.onion' to the end of the printed addresses.
-
-Configuration
-------------------------------
-
-* `tor.bandwidth_rate`: the max bandwidth allocated to Tor, in KB per second, when used as an exit node.
-
-For example:
-
- {
- "tor": {
- "bandwidth_rate": 6550
- }
- }
-
-
diff --git a/doc/services/webapp.md b/doc/services/webapp.md
deleted file mode 100644
index 1c06d715..00000000
--- a/doc/services/webapp.md
+++ /dev/null
@@ -1,293 +0,0 @@
-@title = "webapp"
-@summary = "leap_web user management application and provider API."
-
-Introduction
-------------------------
-
-The service `webapp` will install the web application [[leap_web => https://leap.se/git/leap_web.git]]. It has performs the following functions:
-
-* REST API for user registration and authentication via the Bitmask client.
-* Admin interface to manage users.
-* Client certificate distribution and renewal.
-* User support help tickets.
-
-Coming soon:
-
-* Billing.
-* Customizable and localized user documentation.
-
-The leap_web application is written in Ruby on Rails 3, using CouchDB as the backend data store.
-
-Topology
--------------------------
-
-Currently, the platform only supports a single `webapp` node, although we hope to change this in the future.
-
-* `webapp` nodes communicate heavily with `couchdb` nodes, but the two can be on separate servers.
-* The `monitor` service, if enabled, must be on the same node as `webapp`.
-
-Configuration
---------------------------
-
-Essential options:
-
-* `webapp.admin`: An array of usernames that will be blessed with administrative permissions. These admins can delete users, answer help tickets, and so on. These usernames are for users that have registered through the webapp or through the Bitmask client application, NOT the sysadmin usernames lists in the provider directory `users`.
-
-Other options:
-
-* `webapp.engines`: A list of the engines you want enabled in leap_web. Currently, only "support" is available, and it is enabled by default.
-* `webapp.invite_required`: If true, registration requires an invite code. Default is `false`.
-
-For example, `services/webapp.json`:
-
- {
- "webapp": {
- "admins": ["joehill", "ali", "mack_the_turtle"]
- }
- }
-
-By putting this in `services/webapp.json`, all the `webapp` nodes will inherit the same admin list.
-
-There are many options in `provider.json` that also control how the webapp behaves. See [[provider-configuration]] for details.
-
-Invite codes
--------------------
-
-Enabling the invite code functionality will require new users to provide a valid invite code while signing up for a new account. This is turned off by default, allowing all new users to create an account.
-
-Set the `invite_code` option to `true` in `services/webapp.json`:
-
- {
- "webapp": {
- "invite_required": true
- }
- }
-
-This only works with LEAP platform 0.8 or higher.
-
-Run `leap deploy` to enable the option.
-
-You can then generate invite codes by logging into the web application with an admin user.
-
-Alternately, you can also generate invite codes with the command line:
-
- workstation$ leap ssh bumblebee
- bumblebee# cd /srv/leap/webapp/
- bumblebee# sudo -u leap-webapp RAILS_ENV=production bundle exec rake "generate_invites[NUM,USES]"
-
-Where `bumblebee` should be replaced with the name of your webapp node.
-
-The **NUM** specifies the amount of codes to generate. The **USES** parameter is optional: By default, all new invite codes can be used once and will then become invalid. If you provide another value for **USES**, you can set a different amount of maximum uses for the codes you generate.
-
-Customization
----------------------------
-
-The provider directory `files/webapp` can be used to customize the appearance of the webapp. All the files in this directory will get sync'ed to the `/srv/leap/webapp/config/customization` directory of the deployed webapp node.
-
-Files in the `files/webapp` can override view files, locales, and stylesheets in the leap_web app:
-
-For example:
-
- stylesheets/ -- override files in Rails.root/app/assets/stylesheets
- tail.scss -- included before all others
- head.scss -- included after all others
-
- public/ -- overrides files in Rails.root/public
- favicon.ico -- custom favicon
- img/ -- customary directory to put images in
-
- views/ -- overrides files Rails.root/app/views
- home/
- index.html.haml -- this file is what shows up on
- the home page
- pages/
- privacy-policy.en.md -- this file will override
- the default privacy policy
- terms-of-service.en.md -- this file will override
- the default TOS.
-
- locales/ -- overrides files in Rails.root/config/locales
- en.yml -- overrides for English
- de.yml -- overrides for German
- and so on...
-
-To interactively develop your customizations before you deploy them, you have two options:
-
-1. Edit a `webapp` node. This approach involves directly modifying the contents of the directory `/srv/leap/webapp/config/customization` on a deployed `webapp` node. This can, and probably should be, a "local" node. When doing this, you may need to restart leap_web in order for changes to take effect (`touch /srv/leap/webapp/tmp/restart.txt`).
-2. Alternately, you can install leap_web to run on your computer and edit files in `config/customization` locally. This approach does not require a provider or a `webapp` node. For more information, see the [leap_web README](https://github.com/leapcode/leap_web).
-
-NOTE: If you add a `tails.scss` or `head.scss` file, then you usually need to run `rake tmp:clear` and restart rails in order for the new stylesheet to get recognized. You should only need to do this once.
-
-Once you have what you want, then copy these files to the local provider directory `files/webapp` so that they will be installed each time you deploy.
-
-Customization tutorial
-----------------------------
-
-This mini-tutorial will walk you through creating a custom "branding" of the leap_web application. We will be creating a provider called "Prehistoric Computer."
-
-Here are the files we are going to create:
-
- leap_web/config/customization
- ├── locales
- │   ├── en.yml
- │   └── es.yml
- ├── public
- │   ├── favicon.ico
- │   └── img
- │   └── masthead.png
- ├── stylesheets
- │   └── tail.scss
- └── views
- └── pages
- ├── privacy-policy.en.md
- └── privacy-policy.es.md
-
-All these files are available in the source code in the [[customization.example => https://github.com/leapcode/leap_web/tree/develop/config/customization.example]] directory.
-
-Remember, these files may live different places:
-
-* `user@localmachine$ leap_web/config/customization`: This will be the path if you have checked out a local copy of leap_web.git and are running `rails server` locally in order to test your customizations.
-* `user@localmachine$ PROVIDER/files/webapp`: This is the local provider directory where the files should be put so that they get correctly deployed to webapp nodes.
-* `root@webappnode# /srv/leap/webapp/config/customization`: This is where the files in the local provider directory `PROVIDER/files/webapp` get copied to after a `leap deploy` to a live webapp nodes.
-
-### Override translations
-
-You can add additional locale files in order to change the text used in the existing application and to add translations for string that you added to the application.
-
-In this example, we will be altering the default text for the "login_info" string. In `config/locales/en/home.en.yml` there is this entry:
-
- en:
- login_info: "Log in to change your account settings, create support tickets, and manage payments."
-
-We are going to override this with some custom text in English and Spanish:
-
-`leap_web/config/customization/locale/en.yml`:
-
- en:
- login_info: Authenticate to change your "Prehistoric Computer" settings.
-
-`leap_web/config/customization/locale/es.yml`:
-
- es:
- login_info: Autenticar a cambiar la configuración de "Computer Prehistoria."
-
-Now, the home page of leap_web will use these new strings instead of the default. Remember that you must restart rails in order for new locale files to take effect.
-
-### Override static pages
-
-You can also override any of the static files included with leap_web, such as the privacy policy or terms of service.
-
-Here is how we would create a custom privacy policy in English and Spanish:
-
-`leap_web/config/customization/views/pages/privacy-policy.en.md`:
-
- # Custom Privacy Policy
- This is our privacy policy.
-
-`leap_web/config/customization/views/pages/privacy-policy.es.md`:
-
- # Custom Política de Privacidad
- Esta es nuestra política de privacidad.
-
-### Add a custom header
-
-Now we will add a custom header to every page. First, we add the images:
-
- leap_web/config/customization
- ├── public
- ├── favicon.ico
- └── img
- └── masthead.png
-
-You can create your own, or use the example files in https://github.com/leapcode/leap_web/tree/develop/config/customization.example
-
-Now, we add some custom CSS so that we can style the masthead:
-
-`leap_web/config/customization/stylesheets/tail.scss`
-
- $custom-color: #66bbaa;
-
- a {
- color: $custom-color;
- }
-
- //
- // MASTHEAD
- //
-
- #masthead {
- background-color: $custom-color;
- border-bottom: none;
-
- // make the masthead clickable by replacing the
- // site name link with the masthead image:
- .title {
- padding: 0px;
- .sitename a {
- display: block;
- background: url(/img/masthead.png) 0 0 no-repeat;
- font-size: 0px;
- height: 100px;
- background-size: auto 100px;
- }
- }
- }
-
- // make the home page masthead slightly larger
- body.home #masthead {
- .sitename a {
- height: 150px;
- background-size: auto 150px;
- }
- }
-
- //
- // FOOTER
- //
-
- #footer .links {
- background-color: $custom-color;
- }
-
-NOTE: If you add a `tails.scss` or `head.scss` file, then you usually need to run `rake tmp:clear` and restart rails in order for the new stylesheet to get recognized. You should only need to do this once.
-
-
-Custom Fork
-----------------------------
-
-Sometimes it is easier to maintain your own fork of the leap_web app. You can keep your customizations in that fork instead of in the provider `files/webapp` directory. Or, perhaps you want to add an engine to the application that modifies the app's behavior.
-
-To deploy your own leap_web, modify the provider file `common.json`:
-
- {
- "sources": {
- "webapp": {
- "revision": "origin/develop",
- "source": "https://github.com/leapcode/leap_web",
- "type": "git"
- }
- }
- }
-
-To target only particular environment, modify instead `common.ENV.json`, where ENV is the name of the environment.
-
-See https://github.com/leapcode/leap_web/blob/develop/doc/DEVELOP.md for notes on getting started hacking on leap_web.
-
-Maintenance mode
-------------------
-
-You can put the webapp into maintenance mode by simply dropping a html file to `/srv/leap/webapp/public/system/maintenance.html`. For example:
-
- workstation$ leap ssh webappnode
- server# echo "Temporarily down for maintenance. We will be back soon." > /srv/leap/webapp/public/system/maintenance.html
-
-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.
diff --git a/doc/troubleshooting/en.haml b/doc/troubleshooting/en.haml
deleted file mode 100644
index f0f1359c..00000000
--- a/doc/troubleshooting/en.haml
+++ /dev/null
@@ -1,3 +0,0 @@
-- @title = "Troubleshooting"
-
-= child_summaries \ No newline at end of file
diff --git a/doc/troubleshooting/known-issues.md b/doc/troubleshooting/known-issues.md
deleted file mode 100644
index 4defc886..00000000
--- a/doc/troubleshooting/known-issues.md
+++ /dev/null
@@ -1,115 +0,0 @@
-@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
deleted file mode 100644
index 607f924e..00000000
--- a/doc/troubleshooting/tests.md
+++ /dev/null
@@ -1,70 +0,0 @@
-@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:
-
- workstation$ 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):
-
- workstation$ 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](https://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 Frontends
-
-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 acknowledge or recheck them.
-
-### 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/where-to-look.md b/doc/troubleshooting/where-to-look.md
deleted file mode 100644
index c92fba8f..00000000
--- a/doc/troubleshooting/where-to-look.md
+++ /dev/null
@@ -1,267 +0,0 @@
-@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`.
-* We use the `example.org` domain for documentation purposes here, please replace it with the you domain.
-
-Firewall
-=======================
-
-Every node in your provider has its own restrictive firewall, but you might have a network firewall in place as well that is not managed by LEAP platform. To see what ports and addresses must be open, run this command:
-
- workstation$ leap compile firewall
-
-If any of those are blocked, then your provider will not work.
-
-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"
-
-
-All URLs accessible ?
----------------------
-
-* https://example.org
-* https://api.example.org:4430/provider.json
-* https://example.org/ca.crt
-
-
-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/en.haml b/doc/tutorials/en.haml
deleted file mode 100644
index 1c73fc0f..00000000
--- a/doc/tutorials/en.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-- @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
deleted file mode 100644
index f963867a..00000000
--- a/doc/tutorials/quick-start.md
+++ /dev/null
@@ -1,230 +0,0 @@
-@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.'
-
-Introduction
-====================================
-
-### Our goal
-
-We are going to create a minimal LEAP provider, but one that does not offer any actual services. Check out the other tutorials for adding VPN or email services.
-
-Our goal is something like this:
-
- $ leap list
- NODES SERVICES TAGS
- wildebeest couchdb, webapp
-
-NOTE: You won't be able to run that `leap list` command yet, not until we actually create the node configurations.
-
-### Requirements
-
-1. A workstation: This is your local machine that you will run commands on.
-1. A server: This is the machine that you will deploy to. The server can be either:
- 1. A local Vagrant virtual machine: a Vagrant machine can only be useful for testing.
- 1. A real or paravirtualized server: The server must have Debian Jessie installed, and you must be able to SSH into the machine as root. Paravirtualization includes KVM, Xen, OpenStack, Amazon, but not VirtualBox or OpenVZ.
-
-Other things to keep in mind:
-
-* 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 servers, so please be sure that these machines are a basic install with nothing configured or running for other purposes.
-* Your servers will need to be connected to the internet, and not behind a restrictive firewall.
-
-Prepare your workstation
-========================
-
-In order to be able to manage your servers, you need to install the `leap` command on your workstation:
-
-### Install pre-requisites
-
-Install core prerequisites on your workstation.
-
-*Debian & Ubuntu*
-
- workstation$ sudo apt-get install git ruby ruby-dev rsync openssh-client openssl rake make bzip2
-
-*Mac OS*
-
- workstation$ brew install ruby-install
- workstation$ ruby-install ruby
-
-### Install the LEAP command-line utility
-
-Install the `leap` command from rubygems.org:
-
- workstation$ gem install leap_cli --install-dir ~/leap
- workstation$ export PATH=$PATH:~/leap/bin
-
-Alternately, you can install `leap` system wide:
-
- workstation$ sudo gem install leap_cli
-
-To confirm that you installed `leap` correctly, try running `leap help`.
-
-Create a provider instance
-=============================================
-
-A provider instance is a directory tree, residing on your workstation, 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'.
-
- workstation$ leap new ~/example
-
-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. If the platform directory does not yet exist, the `leap_platform` will be downloaded and placed in that directory.
-
-You could also have passed these configuration options on the command-line, like so:
-
- workstation$ leap new --contacts your@email.here --domain example.org --name Example --platform=~/leap/leap_platform .
-
-You should now have the following files:
-
- workstation$ tree example
- example
- ├── common.json
- ├── Leapfile
- ├── nodes/
- ├── provider.json
- ├── services/
- └── tags/
-
-Now add yourself as a privileged sysadmin who will have access to deploy to servers:
-
- workstation$ cd example
- workstation$ leap add-user louise --self
-
-Replace "louise" with whatever you want your sysadmin username to be.
-
-NOTE: Make sure you change directories so that the `leap` command is run from within the provider instance directory. Most `leap` commands only work when run from a provider instance.
-
-Now create the necessary keys and certificates:
-
- workstation$ leap cert ca
- workstation$ leap cert csr
-
-What do these commands do? The first command will create two Certificate Authorities, one that clients will use to authenticate with the servers and one for backend servers to authenticate with each other. The second command creates a Certificate Signing Request suitable for submission to a commercial CA. It also creates two "dummy" files for you to use temporarily:
-
-* `files/cert/example.org.crt` -- This is a "dummy" certificate for your domain that can be used temporarily for testing. Once you get a real certificate from a CA, you should replace this file.
-* `files/cert/commercial_ca.crt` -- This is "dummy" CA cert the corresponds to the dummy domain certificate. Once you replace the domain certificate, also replace this file with the CA cert from the real Certificate Authority.
-
-If you plan to run a real service provider, see important information on [[managing keys and certificates => keys-and-certificates]].
-
-Add a node to the provider
-==================================================
-
-A "node" is a server that is part of your infrastructure. Every node can have one or more services associated with it. We will now add a single node with two services, "webapp" and "couchdb".
-
-You have two choices for node type: a real node or a local node.
-
-* Real Node: A real node is any physical or paravirtualized server, including KVM, Xen, OpenStack Compute, Amazon EC2, but not VirtualBox or OpenVZ (VirtualBox and OpenVZ use a more limited form of virtualization). The server must be running Debian Jessie.
-* Local Node: A local node is a virtual machine created by Vagrant, useful for local testing on your workstation.
-
-Getting Vagrant working can be a pain and is [[covered in other tutorials => vagrant]]. If you have a real server available, we suggest you try this tutorial with a real node first.
-
-### Option A: Add a real node
-
-Note: Installing LEAP Platform on this server will potentially destroy anything you have previously installed on this machine.
-
-Create a node, with the services "webapp" and "couchdb":
-
- workstation$ leap node add wildebeest ip_address:x.x.x.w services:webapp,couchdb
-
-NOTE: replace x.x.x.x with the actual IP address of this server.
-
-### Option B: Add a local node
-
-Create a node, with the services "webapp" and "couchdb", and then start the local virtual machine:
-
- workstation$ leap node add --local wildebeest services:webapp,couchdb
- workstation$ leap local start wildebeest
-
-It will take a while to download the Virtualbox base box and create the virtual machine.
-
-Deploy your provider
-=========================================
-
-### Initialize the node
-
-Node initialization only needs to be done once, but there is no harm in doing it multiple times:
-
- workstation$ leap node init wildebeest
-
-This will initialize the node `wildebeest`.
-
-For non-local nodes, 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.
-
-### Deploy to the node
-
-The next step is to deploy the LEAP platform to your node. [Deployment can take a while to run](https://xkcd.com/303/), especially on the first run, as it needs to update the packages on the new machine.
-
- workstation$ leap deploy wildebeest
-
-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
-
-The next step is to configure the DNS for your provider. For testing purposes, you can just modify your `/etc/hosts` file. Please don't forget about these entries, they will override DNS queries if you setup your DNS later. For a list of what entries to add to `/etc/hosts`, run this command:
-
- workstation$ leap compile hosts
-
-Alternately, if you have access to modify the DNS zone entries for your domain:
-
- workstation$ leap compile zone
-
-NOTE: The resulting zone file is incomplete because it is missing a serial number. Use the output of `leap compile zone` as a guide, but do not just copy and paste the output. Also, the `compile zone` output will always exclude mention of local nodes.
-
-The DNS method will not work for local nodes created with Vagrant.
-
-Test that things worked correctly
-=================================
-
-To run troubleshooting tests:
-
- workstation$ leap test
-
-Alternately, you can run these same tests from the server itself:
-
- workstation$ leap ssh wildebeest
- wildebeest# run_tests
-
-Create an administrator
-===============================
-
-Assuming that you set up your DNS or `/etc/hosts` file, you should be able to load `https://example.org` in your web browser (where example.org is whatever domain name you actually used).
-
-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.
-
-Once you have created a user, you can now make this user an administrator. For example, if you created a user `kangaroo`, you would create the file `services/webapp.json` with the following content:
-
- {
- "webapp": {
- "admins": ["kangaroo"]
- }
- }
-
-Save that file and run `leap deploy` again. When you next log on to the web application, the user kangaroo will now be an admin.
-
-If you want to restrict who can register a new user, see [[webapp]] for configuration options.
-
-What is next?
-======================
-
-Add an end-user service
--------------------------------
-
-You should now have a minimal service provider with a single node. This service provider is pointless at the moment, because it does not include any end-user services like VPN or email. To add one of these services, continue with one of the following tutorials:
-
-* [[single-node-email]]
-* [[single-node-vpn]]
-
-Learn more
----------------
-
-We have only just scratched the surface of the possible ways to configure and deploy your service provider. Your next step should be:
-
-* Read [[getting-started]] for more details on using the LEAP platform.
-* See [[commands]] for a list of possible commands.
diff --git a/doc/tutorials/single-node-email.md b/doc/tutorials/single-node-email.md
deleted file mode 100644
index 0a73e6e1..00000000
--- a/doc/tutorials/single-node-email.md
+++ /dev/null
@@ -1,69 +0,0 @@
-@title = 'Single node email tutorial'
-@nav_title = 'Quick email'
-@summary = 'Tutorial for setting up a simple email provider.'
-
-This tutorial walks you through the initial process of creating and deploying a minimal email service provider. Please first complete the [[quick-start]]. This tutorial will pick up where that one left off.
-
-Our goal
-------------------
-
-We are going to create a minimal LEAP provider offering email service.
-
-Our goal is something like this:
-
- $ leap list
- NODES SERVICES TAGS
- wildebeest couchdb, mx, soledad, webapp
-
-Where 'wildebeest' is whatever name you chose for your node in the [[quick-start]].
-
-Add email services to the node
---------------------------------------
-
-In order to add [[services => services]] to a node, edit the node's JSON configuration file.
-
-In our example, we would edit `nodes/wildebeest.json`:
-
- {
- "ip_address": "1.1.1.1",
- "services": ["couchdb", "webapp", "mx", "soledad"]
- }
-
-Here, we added `mx` and `soledad` to the node's `services` list. Briefly:
-
-* **mx**: nodes with the **mx** service will run postfix mail transfer agent, and are able to receive and relay email on behalf of your domain. You can have as many as you want, spread out over as many nodes as you want.
-* **soledad**: nodes with **soledad** service run the server-side daemon that allows the client to synchronize a user's personal data store among their devices. Currently, **soledad** only runs on nodes that are also **couchdb** nodes.
-
-For more details, see the [[services]] overview, or the individual pages for the [[mx]] and [[soledad]] services.
-
-Deploy to the node
---------------------
-
-Now you should deploy to your node.
-
- workstation$ leap deploy
-
-Setup DNS
-----------------------------
-
-There are several important DNS entries that all email providers should have:
-
-* SPF (Sender Policy Framework): With SPF, an email provider advertises in their DNS which servers should be allowed to relay email on behalf of your domain.
-* DKIM (DomainKey Identified Mail): With DKIM, an email provider is able to vouch for the validity of certain headers in outgoing mail, allowing the receiving provider to have more confidence in these values when processing the message for spam or abuse.
-
-In order to take advantage of SPF and DKIM, run this command:
-
- workstation$ leap compile zone
-
-Then take the output of that command and merge it with the DNS zone file for your domain.
-
-CAUTION: the output of `leap compile zone` is not a complete zone file since it is missing a serial number. You will need to manually merge it with your existing zone file.
-
-Test it out
----------------------------------
-
-First, run:
-
- workstation# leap test
-
-Then fire up the bitmask client, register a new user with your provider, and try sending and receiving email.
diff --git a/doc/tutorials/single-node-vpn.md b/doc/tutorials/single-node-vpn.md
deleted file mode 100644
index dc1df7ab..00000000
--- a/doc/tutorials/single-node-vpn.md
+++ /dev/null
@@ -1,112 +0,0 @@
-@title = "Single node VPN tutorial"
-@nav_title = "Quick VPN"
-@summary = 'Tutorial for setting up a simple VPN provider.'
-
-This tutorial walks you through the initial process of creating and deploying a minimal VPN service provider. Please first complete the [[quick-start]]. This tutorial will pick up where that one left off.
-
-NOTE: For the VPN to work, you must use a real or paravirtualized node, not a local Vagrant node.
-
-Our goal
-------------------
-
-We are going to create a minimal LEAP provider offering VPN service.
-
-Our goal is something like this:
-
- $ leap list
- NODES SERVICES TAGS
- wildebeest couchdb, webapp, openvpn, tor
-
-Where 'wildebeest' is whatever name you chose for your node in the [[quick-start]].
-
-Add VPN service to the node
---------------------------------------
-
-In order to add [[services => services]] to a node, edit the node's JSON configuration file.
-
-In our example, we would edit `nodes/wildebeest.json`:
-
- {
- "ip_address": "1.1.1.1",
- "services": ["couchdb", "webapp", "openvpn", "tor"]
- }
-
-Here, we added `openvpn` and `tor` to the node's `services` list. Briefly:
-
-* **openvpn**: nodes with the **openvpn** service will become OpenVPN gateways that clients connect to in order to proxy their internet connection. You can have as many as you want, spread out over as many nodes as you want.
-* **tor**: nodes with **tor** service become Tor exit nodes. This is entirely optional, and will add additional bandwidth to your node. If you don't have many VPN users, the added traffic will help create cover traffic for your users. On the down side, this VPN gateway will get flagged as an anonymous proxy and some sites may block traffic from it.
-
-For more details, see the [[services]] overview, or the individual pages for the [[openvpn]] and [[tor]] services.
-
-Add gateway_address to the node
-----------------------------------------
-
-VPN gateways require two different IP addresses:
-
-* `ip_address`: This property is used for VPN traffic **egress**. In other words, all VPN traffic appears to come from this IP address. This is also the main IP of the server.
-* `openvpn.gateway_address`: This property is used for VPN traffic **ingress**. In other words, clients will connect to this IP address.
-
-The node configuration file should now look like this:
-
- {
- "ip_address": "1.1.1.1",
- "services": ["couchdb", "webapp", "openvpn", "tor"],
- "openvpn": {
- "gateway_address": "2.2.2.2"
- }
- }
-
-Why two different addresses? Without this, the traffic from one VPN user to another would not be encrypted. This is because the routing table of VPN clients must ensure that packets with a destination of the VPN gateway are sent unmodified and don't get passed through the VPN's encryption.
-
-Generate a Diffie-Hellman file
--------------------------------------------
-
-Next we need to create a Diffie-Hellman parameter file, used for forward secret OpenVPN ciphers. You only need to do this once.
-
- workstation$ leap cert dh
-
-Feel free to erase the resulting DH file and regenerate it as you please.
-
-Deploy to the node
---------------------
-
-Now you should deploy to your node. This may take a while.
-
- workstation$ leap deploy
-
-If the deploy was not successful, try to run it again.
-
-Test it out
----------------------------------
-
-First, run:
-
- workstation$ leap test
-
-Then fire up the Bitmask client, register a new user with your provider, and turn on the VPN connection.
-
-Alternately, you can also manually connect to your VPN gateway using OpenVPN on the command line:
-
- workstation$ sudo apt install openvpn
- workstation$ leap test init
- workstation$ sudo openvpn --config test/openvpn/default_unlimited.ovpn
-
-Make sure that Bitmask is not connected to the VPN when you run that command.
-
-The name of the test configuration might differ depending on your setup. The test configuration created by `leap test init` includes a client certificate that will expire, so you may need to re-run `leap test init` if it has been a while since you last generated the test configuration.
-
-What do do next
---------------------------------
-
-A VPN provider with a single gateway is kind of limited. You can add as many nodes with service [[openvpn]] as you like. There is no communication among the VPN gateways or with the [[webapp]] or [[couchdb]] nodes, so there is no issue with scaling out the number of gateways.
-
-For example, add some more nodes:
-
- workstation$ leap node add giraffe ip_address:1.1.1.2 services:openvpn openvpn.gateway_address:2.2.2.3
- workstation$ leap node add rhino ip_address:1.1.1.3 services:openvpn openvpn.gateway_address:2.2.2.4
- workstation$ leap node init giraffe rhino
- workstation$ leap deploy
-
-Now you have three VPN gateways.
-
-One consideration is that you should tag each VPN gateway with a [[location => nodes#locations]]. This helps the client determine which VPN gateway it should connect to by default and will allow the user to choose among gateways based on location.
diff --git a/doc/tutorials/vagrant.md b/doc/tutorials/vagrant.md
deleted file mode 100644
index 710c2664..00000000
--- a/doc/tutorials/vagrant.md
+++ /dev/null
@@ -1,471 +0,0 @@
-@title = 'Vagrant and the LEAP Platform'
-@nav_title = 'Vagrant'
-@summary = 'Running a local provider with Vagrant'
-
-What is Vagrant?
-========================================
-
-[[Vagrant => https://www.vagrantup.com]] is a tool to make it easier to manage virtual machines running on your desktop computer (typically for testing or development purposes). You can use Vagrant to create virtual machines and deploy the LEAP platform locally.
-
-Vagrant can be a pain to get working initially, but this page should help you get through the process. Please make sure you have at least Vagrant v1.5 installed.
-
-There are two ways you can setup LEAP platform using Vagrant.
-
-1. use the `leap` command: this will allow you to create multiple virtual machines.
-2. use static Vagrantfile: there is a static Vagrantfile that is distributed with the `leap_platform.git`. This only supports a single, pre-configured virtual machine, but can get you started more quickly.
-
-Install Vagrant
-========================================
-
-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.
-
-*Debian & Ubuntu*
-
-Install core prerequisites:
-
- sudo apt-get install git ruby ruby-dev rsync openssh-client openssl rake make
-
-Install Vagrant:
-
- sudo apt-get install vagrant virtualbox
-
-If you want to use libvirt instead of virtualbox, you don't need to install virtualbox. See [support for libvirt](#support-for-libvirt).
-
-*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
-
-Vagrant with leap command
-=======================================
-
-If you have not done so, install `leap` command line tool:
-
- gem install leap_cli
-
-Creating local nodes
-----------------------------------
-
-When you create a service provider, your servers are called "nodes". When a node is virtual and exists only locally using vagrant, this type of node is called a "local node".
-
-If you do not have a provider already, you will need to create one and configure it before continuing (see the [Quick Start](quick-start) guide).
-
-These commands, for example, will create an initial provider directory "myprovider":
-
- $ leap new --domain example.org --name Example myprovider
- $ cd myprovider
- $ leap add-user --self
- $ leap cert ca
- $ leap cert csr
-
-To create local nodes, add the flag `--local` to the `leap node add` command. For example:
-
- $ 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 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 troubleshooting section below.
-
- $ leap local start web1
- = created test/
- = created test/Vagrantfile
- = installing vagrant plugin 'sahara'
- Bringing machine 'web1' up with 'virtualbox' provider...
- [web1] Box 'leap-jessie' 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-jessie'...
- 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-jessie'...
- [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 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.
-
-
-2. Vagrant with static Vagrantfile
-==================================================
-
-You can use the static Vagrantfile if you want to get up a running with a pre-canned test provider.
-
-It will install a single node mail server in the default configuration with one single command.
-
-Clone the platform with
-
- git clone --recursive -b develop 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.
-
- vagrant ssh
-
-On the host, run the tests to check if everything is working as expected:
-
- cd /home/vagrant/leap/configuration/
- leap test
-
-Use the bitmask client to do an initial soledad sync
--------------------------------------------------------------
-
-Copy the self-signed CA certificate from the host.
-The easiest way is to use the [vagrant-scp plugin](https://github.com/invernizzi/vagrant-scp):
-
- vagrant scp :/home/vagrant/leap/configuration/files/ca/ca.crt /tmp/example.org.ca.crt
-
- vagrant@node1:~/leap/configuration$ cat files/ca/ca.crt
-
-and write it into a file, needed by the bitmask client:
-
- bitmask --ca-cert-file /tmp/example.org.ca.crt
-
-On the first run, bitmask is creating a gpg keypair. This is
-needed for delivering and encrypting incoming mails.
-
-Testing email
--------------
-
- sudo apt install swaks
- swaks -f test22@leap.se -t test22@example.org -s example.org
-
-check the logs:
-
- sudo less /var/log/mail.log
- sudo less /var/log/leap/mx.log
-
-if an error occurs, see if the mail is still laying in the mailspool dir:
-
- sudo ls /var/mail/leap-mx/Maildir/new
-
-Re-run bitmask client to sync your mail
----------------------------------------
-
- bitmask --ca-cert-file /tmp/example.org.ca.crt
-
-Now, connect your favorite mail client to the imap and smtp proxy
-started by the bitmask client:
-
- https://bitmask.net/en/help/email
-
-Happy testing !
-
-Using the Webapp
-----------------
-
-There are 2 users preconfigured:
-
-. `testuser` with pw `hallo123`
-. `testadmin` with pw `hallo123`
-
-login as `testadmin` to access the webapp with admin priviledges.
-
-
-Support for libvirt
-=======================================
-
-Install libvirt plugin
--------------------------------------
-
-By default, Vagrant will use VirtualBox to create the virtual machines, but this is how you can use libvirt. Using libvirt is more efficient, but VirtualBox is more stable and easier to set up.
-
-*For debian/ubuntu:*
-
- sudo apt-get install libvirt-bin libvirt-dev
-
- # to build the vagrant-libvirt plugin you need the following packages:
- sudo apt-get install ruby-dev libxslt-dev libxml2-dev libvirt-dev
-
- # install the required plugins
- vagrant plugin install vagrant-libvirt fog fog-libvirt sahara
-
-Log out and then log back in.
-
-Note: if running ubuntu 15.10 as the host OS, you will probably need to run the following commands before "vagrant plugin install vagrant-libvirt" will work:
-
- ln -sf /usr/lib/liblzma.so.5 /opt/vagrant/embedded/lib
- ln -sf /usr/lib/liblzma.so.5.0.0 /opt/vagrant/embedded/lib
-
-Create libvirt pool
------------------------------------------
-
-Next, you must create the libvirt image pool. The "default" pool uses `/var/lib/libvirt/images`, but Vagrant will not download base boxes there. Instead, create a libvirt pool called "vagrant", like so:
-
- virsh pool-define-as vagrant dir - - - - /home/$USER/.vagrant.d/boxes
- virsh pool-start vagrant
- virsh pool-autostart vagrant
-
-If you want to use a name different than "vagrant" for the pool, you can change the name in `Leapfile` by setting the `@vagrant_libvirt_pool` variable:
-
- @vagrant_libvirt_pool = "vagrant"
-
-Force use of libvirt
---------------------------------------------
-
-Finally, you need to tell Vagrant to use libvirt instead of VirtualBox. If using vagrant with leap_cli, modify your `Leapfile` or `.leaprc` file and add this line:
-
- @vagrant_provider = "libvirt"
-
-Alternately, if using the static Vagrantfile, you must run this in your shell instead:
-
- export VAGRANT_DEFAULT_PROVIDER=libvirt
-
-
-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/jessie
-
-You can also run vagrant with --debug for full logging.
-
-Known issues
-------------------------
-
-* You may need to undefine the default libvirt pool:
- sudo virsh pool-undefine default
-* `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> libvirtd' 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,
-
-Useful commands
-------------------------
-
-Force re-download of image, in case something goes wrong:
-
- vagrant box add leap/jessie --force --provider libvirt
-
-Shared folder 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
-
-if you are wanting this disabled for all the leap vagrant integration, you can add this to ~/.leaprc:
-
- @custom_vagrant_vm_line = 'config.vm.synced_folder "src/", "/srv/website", disabled: true'
-
-
-Verify vagrantboxes
-===============================================
-
-When you run vagrant, it goes out to the internet and downloads an initial image for the virtual machine. If you want to verify that authenticity of these images, follow these steps.
-
-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/SHA256SUMS.sign
- wget https://downloads.leap.se/platform/SHA256SUMS
-
-and verify the signature against your local imported LEAP archive signing pubkey
-
- gpg --verify SHA256SUMS.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 SHA256SUMS 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/jessie/$version/$provider.box otherwise it's probably located within ~/.vagrant.d/.
-
- wget https://atlas.hashicorp.com/LEAP/boxes/jessie/versions/1.1.0/providers/libvirt.box
- sha215sum libvirt.box
- cat SHA215SUMS
-
-Troubleshooting
-=======================
-
-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 special 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)
-
-Additional notes
-====================
-
-Some 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.
-
-Limitations
------------------------
-
-Please consult the known issues for vagrant, see the [Known Issues](known-issues), section *Special Environments*
-
-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 Wily 15.10
-
-* libvirt with vagrant 1.7.2, from standard Ubuntu packages.
-
-Mac OS X 10.9
-
-* `VirtualBox 4.3.10` from virtualbox.org and `vagrant 1.5.4` from vagrantup.com
-
-
-Issue reporting
----------------
-
-When you encounter any bugs, please [check first](https://leap.se/code/search) on our bugtracker if it's something already known. Reporting bugs is the first [step in fixing them](https://leap.se/code/projects/report-issues). Please include all the relevant details: platform branch, version of leap_cli, past upgrades.
diff --git a/doc/upgrading/en.haml b/doc/upgrading/en.haml
deleted file mode 100644
index efa0d7c5..00000000
--- a/doc/upgrading/en.haml
+++ /dev/null
@@ -1,5 +0,0 @@
-- @nav_title = "Upgrading"
-- @title = "Upgrading from prior LEAP platform releases"
-- @summary = ""
-
-= child_summaries \ No newline at end of file
diff --git a/doc/upgrading/upgrade-0-8.md b/doc/upgrading/upgrade-0-8.md
deleted file mode 100644
index 84e9cee2..00000000
--- a/doc/upgrading/upgrade-0-8.md
+++ /dev/null
@@ -1,141 +0,0 @@
-@title = 'Upgrade to 0.8'
-@toc = false
-
-LEAP Platform release 0.8 introduces several major changes that need do get taken into account while upgrading:
-
-* Dropping Debian Wheezy support. You need to upgrade your nodes to jessie before deploying a platform upgrade.
-* Dropping BigCouch support. LEAP Platform now requires CouchDB and therefore you need to migrate all your data from BigCouch to CouchDB.
-
-Upgrading to Platform 0.8
----------------------------------------------
-
-### Step 1: Get new leap_platform and leap_cli
-
- workstation$ gem install leap_cli --version 1.8
- workstation$ cd leap_platform
- workstation$ git pull
- workstation$ git checkout 0.8.0
-
-### Step 2: Prepare to migrate from BigCouch to CouchDB
-
-<%= render :partial => 'docs/platform/common/bigcouch_migration_begin.md' %>
-
-### Step 3: Upgrade from Debian Wheezy to Jessie
-
-There are the [Debian release notes on how to upgrade from wheezy to jessie](https://www.debian.org/releases/stable/amd64/release-notes/ch-upgrading.html). Here are the steps that worked for us, but please keep in mind that there is no bullet-proof method that will work in every situation.
-
-**USE AT YOUR OWN RISK.**
-
-For each one of your nodes, login to it and do the following process:
-
- # keep a log of the progress:
- screen
- script -t 2>~/leap_upgrade-jessiestep.time -a ~/upgrade-jessiestep.script
-
- # ensure you have a good wheezy install:
- export DEBIAN_FRONTEND=noninteractive
- apt-get autoremove --yes
- apt-get update
- apt-get -y -o DPkg::Options::=--force-confold dist-upgrade
-
- # if either of these return anything, you will need to resolve them before continuing:
- dpkg --audit
- dpkg --get-selections | grep 'hold$'
-
- # switch sources to jessie
- sed -i 's/wheezy/jessie/g' /etc/apt/sources.list
- rm /etc/apt/sources.list.d/*
- echo "deb http://deb.leap.se/0.8 jessie main" > /etc/apt/sources.list.d/leap.list
-
- # remove pinnings to wheezy
- rm /etc/apt/preferences
- rm /etc/apt/preferences.d/*
-
- # get jessie package lists
- apt-get update
-
- # clean out old package files
- apt-get clean
-
- # test to see if you have enough space to upgrade, the following will alert
- # you if you do not have enough space, it will not do the actual upgrade
- apt-get -o APT::Get::Trivial-Only=true dist-upgrade
-
- # do first stage upgrade
- apt-get -y -o DPkg::Options::=--force-confold upgrade
-
- # repeat the following until it makes no more changes:
- apt-get -y -o DPkg::Options::=--force-confold dist-upgrade
-
- # resolve any apt issues if there are some
- apt-get -y -o DPkg::Options::=--force-confold -f install
-
- # clean up extra packages
- apt-get autoremove --yes
-
- reboot
-
-
-Potential Jessie Upgrade Issues
--------------------------------
-
-**W: Ignoring Provides line with DepCompareOp for package python-cffi-backend-api-max**
-
-You can ignore these warnings, they will be resolved on upgrade.
-
-**E: Unable to fetch some archives, maybe run apt-get update or try with --fix-missing?**
-
-If you get this error, run `apt-get update` and then re-run the command.
-
-**Unmet dependencies. Try using -f.**
-
-Sometimes you might get an error similar to this (although the package names may be different):
-
- You might want to run 'apt-get -f install' to correct these.
- The following packages have unmet dependencies:
- lsof : Depends: libperl4-corelibs-perl but it is not installed or
- perl (< 5.12.3-7) but 5.20.2-3+deb8u4 is installed
-
-If this happens, run `apt-get -f install` to resolve it, and then re-do the previous upgrade command
-you did when this happened.
-
-**Failure restarting some services for OpenSSL upgrade**
-
-If you get this warning:
-
- The following services could not be restarted for the OpenSSL library upgrade:
- postfix
- You will need to start these manually by running '/etc/init.d/<service> start'.
-
-Just ignore it, it should be fixed on reboot/deploy.
-
-### Step 4: Deploy LEAP Platform 0.8 to the Couch node
-
-You will need to deploy the 0.8 version of LEAP Platform to the couch node before continuing.
-
-1. deploy to the couch node:
-
- ```
- workstation$ leap deploy <couchdb-node>
- ```
-
- If you used the iptables method of blocking access to couchdb, you need to run it again because the deploy just overwrote all the iptables rules:
-
- ```
- server# iptables -A INPUT -p tcp --dport 5984 --jump REJECT
- ```
-
-### Step 5: Import Data into CouchDB
-
-<%= render :partial => 'docs/platform/common/bigcouch_migration_end.md' %>
-
-### Step 6: Deploy everything
-
-Now that you've upgraded all nodes to Jessie, and migrated to CouchDB, you are ready to deploy LEAP Platform 0.8 to the rest of the nodes:
-
- workstation$ cd <provider directory>
- workstation$ leap deploy
-
-### Step 7: Test and cleanup
-
-<%= render :partial => 'docs/platform/common/bigcouch_migration_finish.md' %>
diff --git a/docs/en/commands.html b/docs/en/commands.html
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/docs/en/commands.html
diff --git a/docs/en/details.html b/docs/en/details.html
new file mode 100644
index 00000000..f0e15e8f
--- /dev/null
+++ b/docs/en/details.html
@@ -0,0 +1,138 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Details - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class='active level0'>
+<a class='' href='details.html'>Details</a>
+</li>
+<li class=' level1'>
+<a class='' href='details/faq.html'>FAQ</a>
+</li>
+<li class=' level1'>
+<a class='' href='details/development.html'>Development</a>
+</li>
+<li class=' level1'>
+<a class='' href='details/ports.html'>Ports</a>
+</li>
+<li class=' level1'>
+<a class='' href='details/under-the-hood.html'>Under the hood</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Platform Details</h1>
+
+<div id='summary'>The LEAP Platform is set of complementary packages and server recipes to automate the maintenance of LEAP services in a hardened Debian environment.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+</ol></div>
+
+<div class=' page-summary'>
+ <h2>
+ <a href='details/faq.html'>FAQ</a>
+ </h2>
+ <div class='summary'>Frequently Asked Questions</div>
+</div>
+<div class=' page-summary'>
+ <h2>
+ <a href='details/development.html'>Development</a>
+ </h2>
+ <div class='summary'>Getting started with making changes to the LEAP platform</div>
+</div>
+<div class=' page-summary'>
+ <h2>
+ <a href='details/ports.html'>Ports</a>
+ </h2>
+ <div class='summary'>The required open ports for different services.</div>
+</div>
+<div class=' page-summary'>
+ <h2>
+ <a href='details/under-the-hood.html'>Under the hood</a>
+ </h2>
+ <div class='summary'>Various implementation details.</div>
+</div>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/details/development.html b/docs/en/details/development.html
new file mode 100644
index 00000000..a21d426b
--- /dev/null
+++ b/docs/en/details/development.html
@@ -0,0 +1,226 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Development - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+<li class=' level1'>
+<a class='' href='faq.html'>FAQ</a>
+</li>
+<li class='active level1'>
+<a class='' href='development.html'>Development</a>
+</li>
+<li class=' level1'>
+<a class='' href='ports.html'>Ports</a>
+</li>
+<li class=' level1'>
+<a class='' href='under-the-hood.html'>Under the hood</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Development</h1>
+
+<div id='summary'>Getting started with making changes to the LEAP platform</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="development/index.html#installing-leap_cli">Installing leap_cli</a>
+ <ol>
+ <li>
+ <a href="development/index.html#from-gem-for-a-single-user">From gem, for a single user</a>
+ </li>
+ <li>
+ <a href="development/index.html#from-gem-system-wide">From gem, system wide</a>
+ </li>
+ <li>
+ <a href="development/index.html#as-a-gem-built-from-source">As a gem, built from source</a>
+ </li>
+ <li>
+ <a href="development/index.html#the-develop-branch-from-source-for-a-single-user">The “develop” branch from source, for a single user</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="development/index.html#running-different-leap_cli-versions">Running different leap_cli versions</a>
+ <ol>
+ <li>
+ <a href="development/index.html#if-installed-as-a-gem">If installed as a gem</a>
+ </li>
+ <li>
+ <a href="development/index.html#if-running-from-source">If running from source</a>
+ </li>
+ </ol>
+ </li>
+</ol></div>
+
+<h2><a name="installing-leap_cli"></a>Installing leap_cli</h2>
+
+<h3><a name="from-gem-for-a-single-user"></a>From gem, for a single user</h3>
+
+<p>Install the latest:</p>
+
+<pre><code>gem install --user-install leap_cli
+</code></pre>
+
+<p>Or install a particular version:</p>
+
+<pre><code>gem install --version 1.8 --user-install leap_cli
+</code></pre>
+
+<p>Add the &ndash;user-install directory to your path:</p>
+
+<pre><code>[ $(which ruby) ] &amp;&amp; PATH="$PATH:$(ruby -e 'puts Gem.user_dir')/bin"
+</code></pre>
+
+<h3><a name="from-gem-system-wide"></a>From gem, system wide</h3>
+
+<p>Install the latest:</p>
+
+<pre><code>sudo gem install leap_cli
+</code></pre>
+
+<p>Install a particular version:</p>
+
+<pre><code>sudo gem install leap_cli --version 1.8
+</code></pre>
+
+<h3><a name="as-a-gem-built-from-source"></a>As a gem, built from source</h3>
+
+<pre><code>sudo apt-get install ruby ruby-dev rake
+git clone https://leap.se/git/leap_cli.git
+cd leap_cli
+git checkout develop
+rake build
+sudo rake install
+</code></pre>
+
+<h3><a name="the-develop-branch-from-source-for-a-single-user"></a>The “develop” branch from source, for a single user</h3>
+
+<pre><code>sudo apt-get install ruby ruby-dev rake
+git clone https://leap.se/git/leap_cli.git
+cd leap_cli
+git checkout develop
+</code></pre>
+
+<p>Then do one of the following to be able to run <code>leap</code> command:</p>
+
+<pre><code>cd leap_cli
+PATH=$PATH:`pwd`/bin
+alias leap="`pwd`/bin/leap"
+ln -s `pwd`/bin/leap ~/bin/leap
+</code></pre>
+
+<p>In practice, of course, you would put aliases or PATH modifications in a shell startup file.</p>
+
+<p>You can also clone from <a href="https://github.com/leap/leap_cli">https://github.com/leap/leap_cli</a></p>
+
+<h2><a name="running-different-leap_cli-versions"></a>Running different leap_cli versions</h2>
+
+<h3><a name="if-installed-as-a-gem"></a>If installed as a gem</h3>
+
+<p>With rubygems, you can always specify the gem version as the first argument to any executable installed by rubygems. For example:</p>
+
+<pre><code>sudo gem install leap_cli --version 1.7.2
+sudo gem install leap_cli --version 1.8
+leap _1.7.2_ --version
+=&gt; leap 1.7.2, ruby 2.1.2
+leap _1.8_ --version
+=&gt; leap 1.8, ruby 2.1.2
+</code></pre>
+
+<h3><a name="if-running-from-source"></a>If running from source</h3>
+
+<p>Alternately, if you are running from source, you can alias different commands:</p>
+
+<pre><code>git clone https://leap.se/git/leap_cli.git
+cd leap_cli
+git checkout develop
+alias leap_develop="`pwd`/bin/leap`
+</code></pre>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/details/development/index.html b/docs/en/details/development/index.html
new file mode 100644
index 00000000..92506b09
--- /dev/null
+++ b/docs/en/details/development/index.html
@@ -0,0 +1,226 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Development - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../../details.html'>Details</a>
+</li>
+<li class=' level1'>
+<a class='' href='../faq.html'>FAQ</a>
+</li>
+<li class='active level1'>
+<a class='' href='../development.html'>Development</a>
+</li>
+<li class=' level1'>
+<a class='' href='../ports.html'>Ports</a>
+</li>
+<li class=' level1'>
+<a class='' href='../under-the-hood.html'>Under the hood</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Development</h1>
+
+<div id='summary'>Getting started with making changes to the LEAP platform</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="index.html#installing-leap_cli">Installing leap_cli</a>
+ <ol>
+ <li>
+ <a href="index.html#from-gem-for-a-single-user">From gem, for a single user</a>
+ </li>
+ <li>
+ <a href="index.html#from-gem-system-wide">From gem, system wide</a>
+ </li>
+ <li>
+ <a href="index.html#as-a-gem-built-from-source">As a gem, built from source</a>
+ </li>
+ <li>
+ <a href="index.html#the-develop-branch-from-source-for-a-single-user">The “develop” branch from source, for a single user</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#running-different-leap_cli-versions">Running different leap_cli versions</a>
+ <ol>
+ <li>
+ <a href="index.html#if-installed-as-a-gem">If installed as a gem</a>
+ </li>
+ <li>
+ <a href="index.html#if-running-from-source">If running from source</a>
+ </li>
+ </ol>
+ </li>
+</ol></div>
+
+<h2><a name="installing-leap_cli"></a>Installing leap_cli</h2>
+
+<h3><a name="from-gem-for-a-single-user"></a>From gem, for a single user</h3>
+
+<p>Install the latest:</p>
+
+<pre><code>gem install --user-install leap_cli
+</code></pre>
+
+<p>Or install a particular version:</p>
+
+<pre><code>gem install --version 1.8 --user-install leap_cli
+</code></pre>
+
+<p>Add the &ndash;user-install directory to your path:</p>
+
+<pre><code>[ $(which ruby) ] &amp;&amp; PATH="$PATH:$(ruby -e 'puts Gem.user_dir')/bin"
+</code></pre>
+
+<h3><a name="from-gem-system-wide"></a>From gem, system wide</h3>
+
+<p>Install the latest:</p>
+
+<pre><code>sudo gem install leap_cli
+</code></pre>
+
+<p>Install a particular version:</p>
+
+<pre><code>sudo gem install leap_cli --version 1.8
+</code></pre>
+
+<h3><a name="as-a-gem-built-from-source"></a>As a gem, built from source</h3>
+
+<pre><code>sudo apt-get install ruby ruby-dev rake
+git clone https://leap.se/git/leap_cli.git
+cd leap_cli
+git checkout develop
+rake build
+sudo rake install
+</code></pre>
+
+<h3><a name="the-develop-branch-from-source-for-a-single-user"></a>The “develop” branch from source, for a single user</h3>
+
+<pre><code>sudo apt-get install ruby ruby-dev rake
+git clone https://leap.se/git/leap_cli.git
+cd leap_cli
+git checkout develop
+</code></pre>
+
+<p>Then do one of the following to be able to run <code>leap</code> command:</p>
+
+<pre><code>cd leap_cli
+PATH=$PATH:`pwd`/bin
+alias leap="`pwd`/bin/leap"
+ln -s `pwd`/bin/leap ~/bin/leap
+</code></pre>
+
+<p>In practice, of course, you would put aliases or PATH modifications in a shell startup file.</p>
+
+<p>You can also clone from <a href="https://github.com/leap/leap_cli">https://github.com/leap/leap_cli</a></p>
+
+<h2><a name="running-different-leap_cli-versions"></a>Running different leap_cli versions</h2>
+
+<h3><a name="if-installed-as-a-gem"></a>If installed as a gem</h3>
+
+<p>With rubygems, you can always specify the gem version as the first argument to any executable installed by rubygems. For example:</p>
+
+<pre><code>sudo gem install leap_cli --version 1.7.2
+sudo gem install leap_cli --version 1.8
+leap _1.7.2_ --version
+=&gt; leap 1.7.2, ruby 2.1.2
+leap _1.8_ --version
+=&gt; leap 1.8, ruby 2.1.2
+</code></pre>
+
+<h3><a name="if-running-from-source"></a>If running from source</h3>
+
+<p>Alternately, if you are running from source, you can alias different commands:</p>
+
+<pre><code>git clone https://leap.se/git/leap_cli.git
+cd leap_cli
+git checkout develop
+alias leap_develop="`pwd`/bin/leap`
+</code></pre>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/details/faq.html b/docs/en/details/faq.html
new file mode 100644
index 00000000..331e1968
--- /dev/null
+++ b/docs/en/details/faq.html
@@ -0,0 +1,213 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+FAQ - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+<li class='active level1'>
+<a class='' href='faq.html'>FAQ</a>
+</li>
+<li class=' level1'>
+<a class='' href='development.html'>Development</a>
+</li>
+<li class=' level1'>
+<a class='' href='ports.html'>Ports</a>
+</li>
+<li class=' level1'>
+<a class='' href='under-the-hood.html'>Under the hood</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Frequently asked questions</h1>
+
+<div id='summary'>Frequently Asked Questions</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="faq/index.html#apt">APT</a>
+ <ol>
+ <li>
+ <a href="faq/index.html#what-do-i-do-when-unattended-upgrades-fail">What do I do when unattended upgrades fail?</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="faq/index.html#puppet">Puppet</a>
+ <ol>
+ <li>
+ <a href="faq/index.html#where-do-i-find-the-time-a-server-was-last-deployed">Where do i find the time a server was last deployed ?</a>
+ </li>
+ <li>
+ <a href="faq/index.html#what-resources-are-touched-by-puppetleap_platform-servicespackagesfiles-etc">What resources are touched by puppet/leap_platform (services/packages/files etc.) ?</a>
+ </li>
+ <li>
+ <a href="faq/index.html#how-can-i-customize-the-leap_platform-puppet-manifests">How can i customize the leap_platform puppet manifests ?</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="faq/index.html#facter">Facter</a>
+ <ol>
+ <li>
+ <a href="faq/index.html#how-can-i-see-custom-facts-distributed-by-leap_platform-on-a-node">How can i see custom facts distributed by leap_platform on a node ?</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="faq/index.html#etc">Etc</a>
+ <ol>
+ <li>
+ <a href="faq/index.html#how-do-i-change-the-domain-of-my-provider">How do i change the domain of my provider ?</a>
+ </li>
+ </ol>
+ </li>
+</ol></div>
+
+<h1><a name="apt"></a>APT</h1>
+
+<h2><a name="what-do-i-do-when-unattended-upgrades-fail"></a>What do I do when unattended upgrades fail?</h2>
+
+<p>When you receive notification e-mails with a subject of &lsquo;unattended-upgrades result for $machinename&rsquo;, that means that some package couldn&rsquo;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 <code>apt-get dist-upgrade</code>.</p>
+
+<h1><a name="puppet"></a>Puppet</h1>
+
+<h2><a name="where-do-i-find-the-time-a-server-was-last-deployed"></a>Where do i find the time a server was last deployed ?</h2>
+
+<p>Run:</p>
+
+<pre><code>leap history FILTER
+</code></pre>
+
+<p>This will tail the log file <code>/var/log/leap/deploy-summary.log</code>.</p>
+
+<p>If that command fails, you can manually check the puppet state file on the node indicates the last puppetrun:</p>
+
+<pre><code>ls -la /var/lib/puppet/state/state.yaml
+</code></pre>
+
+<h2><a name="what-resources-are-touched-by-puppetleap_platform-servicespackagesfiles-etc"></a>What resources are touched by puppet/leap_platform (services/packages/files etc.) ?</h2>
+
+<p>Log into your server and issue:</p>
+
+<pre><code>grep -v '!ruby/sym' /var/lib/puppet/state/state.yaml | sed 's/\"//' | sort
+</code></pre>
+
+<h2><a name="how-can-i-customize-the-leap_platform-puppet-manifests"></a>How can i customize the leap_platform puppet manifests ?</h2>
+
+<p>You can create custom puppet modules under <code>files/puppet</code>.
+The custom puppet entry point is in class &lsquo;custom&rsquo; which can be put into
+<code>files/puppet/modules/custom/manifests/init.pp</code>. This class gets automatically included
+by site_config::default, which is applied to all nodes.</p>
+
+<p>Of cause you can also create a different git branch and change whatever you want, if you are
+familiar wit git.</p>
+
+<h1><a name="facter"></a>Facter</h1>
+
+<h2><a name="how-can-i-see-custom-facts-distributed-by-leap_platform-on-a-node"></a>How can i see custom facts distributed by leap_platform on a node ?</h2>
+
+<p>On the server, export the FACTERLIB env. variable to include the path of the custom fact in question:</p>
+
+<pre><code>export FACTERLIB=/var/lib/puppet/lib/facter:/srv/leap/puppet/modules/stdlib/lib/facter/
+facter
+</code></pre>
+
+<h1><a name="etc"></a>Etc</h1>
+
+<h2><a name="how-do-i-change-the-domain-of-my-provider"></a>How do i change the domain of my provider ?</h2>
+
+<ul>
+<li>First of all, you need to have access to the nameserver config of your new domain.</li>
+<li>Update domain in provider.json</li>
+<li>remove all ca and cert files: <code>rm files/cert/* files/ca/*</code></li>
+<li>create ca, csr and certs : <code>leap cert ca; leap cert csr; leap cert dh; leap cert update</code></li>
+<li>deploy</li>
+</ul>
+
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/details/faq/index.html b/docs/en/details/faq/index.html
new file mode 100644
index 00000000..9db1398e
--- /dev/null
+++ b/docs/en/details/faq/index.html
@@ -0,0 +1,213 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+FAQ - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../../details.html'>Details</a>
+</li>
+<li class='active level1'>
+<a class='' href='../faq.html'>FAQ</a>
+</li>
+<li class=' level1'>
+<a class='' href='../development.html'>Development</a>
+</li>
+<li class=' level1'>
+<a class='' href='../ports.html'>Ports</a>
+</li>
+<li class=' level1'>
+<a class='' href='../under-the-hood.html'>Under the hood</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Frequently asked questions</h1>
+
+<div id='summary'>Frequently Asked Questions</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="index.html#apt">APT</a>
+ <ol>
+ <li>
+ <a href="index.html#what-do-i-do-when-unattended-upgrades-fail">What do I do when unattended upgrades fail?</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#puppet">Puppet</a>
+ <ol>
+ <li>
+ <a href="index.html#where-do-i-find-the-time-a-server-was-last-deployed">Where do i find the time a server was last deployed ?</a>
+ </li>
+ <li>
+ <a href="index.html#what-resources-are-touched-by-puppetleap_platform-servicespackagesfiles-etc">What resources are touched by puppet/leap_platform (services/packages/files etc.) ?</a>
+ </li>
+ <li>
+ <a href="index.html#how-can-i-customize-the-leap_platform-puppet-manifests">How can i customize the leap_platform puppet manifests ?</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#facter">Facter</a>
+ <ol>
+ <li>
+ <a href="index.html#how-can-i-see-custom-facts-distributed-by-leap_platform-on-a-node">How can i see custom facts distributed by leap_platform on a node ?</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#etc">Etc</a>
+ <ol>
+ <li>
+ <a href="index.html#how-do-i-change-the-domain-of-my-provider">How do i change the domain of my provider ?</a>
+ </li>
+ </ol>
+ </li>
+</ol></div>
+
+<h1><a name="apt"></a>APT</h1>
+
+<h2><a name="what-do-i-do-when-unattended-upgrades-fail"></a>What do I do when unattended upgrades fail?</h2>
+
+<p>When you receive notification e-mails with a subject of &lsquo;unattended-upgrades result for $machinename&rsquo;, that means that some package couldn&rsquo;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 <code>apt-get dist-upgrade</code>.</p>
+
+<h1><a name="puppet"></a>Puppet</h1>
+
+<h2><a name="where-do-i-find-the-time-a-server-was-last-deployed"></a>Where do i find the time a server was last deployed ?</h2>
+
+<p>Run:</p>
+
+<pre><code>leap history FILTER
+</code></pre>
+
+<p>This will tail the log file <code>/var/log/leap/deploy-summary.log</code>.</p>
+
+<p>If that command fails, you can manually check the puppet state file on the node indicates the last puppetrun:</p>
+
+<pre><code>ls -la /var/lib/puppet/state/state.yaml
+</code></pre>
+
+<h2><a name="what-resources-are-touched-by-puppetleap_platform-servicespackagesfiles-etc"></a>What resources are touched by puppet/leap_platform (services/packages/files etc.) ?</h2>
+
+<p>Log into your server and issue:</p>
+
+<pre><code>grep -v '!ruby/sym' /var/lib/puppet/state/state.yaml | sed 's/\"//' | sort
+</code></pre>
+
+<h2><a name="how-can-i-customize-the-leap_platform-puppet-manifests"></a>How can i customize the leap_platform puppet manifests ?</h2>
+
+<p>You can create custom puppet modules under <code>files/puppet</code>.
+The custom puppet entry point is in class &lsquo;custom&rsquo; which can be put into
+<code>files/puppet/modules/custom/manifests/init.pp</code>. This class gets automatically included
+by site_config::default, which is applied to all nodes.</p>
+
+<p>Of cause you can also create a different git branch and change whatever you want, if you are
+familiar wit git.</p>
+
+<h1><a name="facter"></a>Facter</h1>
+
+<h2><a name="how-can-i-see-custom-facts-distributed-by-leap_platform-on-a-node"></a>How can i see custom facts distributed by leap_platform on a node ?</h2>
+
+<p>On the server, export the FACTERLIB env. variable to include the path of the custom fact in question:</p>
+
+<pre><code>export FACTERLIB=/var/lib/puppet/lib/facter:/srv/leap/puppet/modules/stdlib/lib/facter/
+facter
+</code></pre>
+
+<h1><a name="etc"></a>Etc</h1>
+
+<h2><a name="how-do-i-change-the-domain-of-my-provider"></a>How do i change the domain of my provider ?</h2>
+
+<ul>
+<li>First of all, you need to have access to the nameserver config of your new domain.</li>
+<li>Update domain in provider.json</li>
+<li>remove all ca and cert files: <code>rm files/cert/* files/ca/*</code></li>
+<li>create ca, csr and certs : <code>leap cert ca; leap cert csr; leap cert dh; leap cert update</code></li>
+<li>deploy</li>
+</ul>
+
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/details/ports.html b/docs/en/details/ports.html
new file mode 100644
index 00000000..273781e0
--- /dev/null
+++ b/docs/en/details/ports.html
@@ -0,0 +1,209 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Ports - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+<li class=' level1'>
+<a class='' href='faq.html'>FAQ</a>
+</li>
+<li class=' level1'>
+<a class='' href='development.html'>Development</a>
+</li>
+<li class='active level1'>
+<a class='' href='ports.html'>Ports</a>
+</li>
+<li class=' level1'>
+<a class='' href='under-the-hood.html'>Under the hood</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Ports</h1>
+
+<div id='summary'>The required open ports for different services.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="ports/index.html#publicly-open-ports">Publicly open ports</a>
+ </li>
+ <li>
+ <a href="ports/index.html#privately-open-ports">Privately open ports</a>
+ </li>
+</ol></div>
+
+<p>There are many different ports that must be open in order for the LEAP platform to work. Some ports must be <em>publicly open</em>, meaning that these should be accessible from the public internet. Other ports are <em>privately open</em>, meaning that they must be accessible to sysadmins or to the other nodes in the provider&rsquo;s infrastructure.</p>
+
+<p>Every node already includes a host-based firewall. However, if your network has its own firewall, you need to make sure that these ports are not blocked.</p>
+
+<h2><a name="publicly-open-ports"></a>Publicly open ports</h2>
+
+<table class="table table-striped">
+<tr>
+ <th>Name</th>
+ <th>Node Type</th>
+ <th>Default</th>
+ <th>Notes</th>
+</tr>
+<tr>
+ <td>SMTP</td>
+ <td>mx</td>
+ <td>25</td>
+ <td>This is required for all server-to-server SMTP email relay. This is not configurable.</td>
+</tr>
+<tr>
+ <td>HTTP</td>
+ <td>webapp</td>
+ <td>80</td>
+ <td>Although no actual services are available over port 80, it should be unblocked so that the web app can redirect to port 443. This is not configurable.</td>
+</tr>
+<tr>
+ <td>HTTPS</td>
+ <td>webapp</td>
+ <td>443</td>
+ <td>The web application is available over this port. This is not configurable.</td>
+</tr>
+<tr>
+ <td>SMTPS</td>
+ <td>mx</td>
+ <td>465</td>
+ <td>The client uses this port to submit outgoing email messages via SMTP over TLS. There is no easy way to change this, although you can create a custom <code>files/service-definitions/v1/smtp-service.json.erb</code> to do so. This will be changed to port 443 in the future.</td>
+</tr>
+<tr>
+ <td>Soledad</td>
+ <td>soledad</td>
+ <td>2323</td>
+ <td>The client uses this port to synchronize its storage data. This can be changed via the configuration property <code>soledad.port</code>. This will be changed to port 443 in the future.</td>
+</tr>
+<tr>
+ <td>Nicknym</td>
+ <td>webapp</td>
+ <td>6425</td>
+ <td>The client uses this port for discovering public keys. This can be changed via the configuration property <code>nickserver.port</code>. This will be changed to port 443 in the future.</td>
+</tr>
+<tr>
+ <td>OpenVPN</td>
+ <td>openvpn</td>
+ <td>80, 443, 53, 1194</td>
+ <td>By default, OpenVPN gateways will listen on all those ports. This can be changed via the configuration property <code>openvpn.ports</code>. Note that these ports must be open for <code>openvpn.gateway_address</code>, not for <code>ip_address</code>.</td>
+</tr>
+<tr>
+ <td>API</td>
+ <td>webapp</td>
+ <td>4430</td>
+ <td>Currently, the provider API is accessible via this port. In the future, the default will be changed to 443. For now, this can be changed via the configuration property <code>api.port</code>.</td>
+</tr>
+</table>
+
+
+<h2><a name="privately-open-ports"></a>Privately open ports</h2>
+
+<table class="table table-striped">
+<tr>
+ <th>Name</th>
+ <th>Node Type</th>
+ <th>Default</th>
+ <th>Notes</th>
+</tr>
+<tr>
+ <td>SSH</td>
+ <td>all</td>
+ <td>22</td>
+ <td>This is the port that the sshd is bound to for the node. You can modify this using the configuration property <code>ssh.port</code>. It is important that this port is never blocked, or you will lose access to deploy to this node.</td>
+</tr>
+<tr>
+ <td>Stunnel</td>
+ <td>all</td>
+ <td>10000-20000</td>
+ <td>This is the range of ports that might be used for the encrypted stunnel connections between two nodes. These port numbers are automatically generated, but will fall somewhere in the specified range.</td>
+</tr>
+</table>
+
+
+
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/details/ports/index.html b/docs/en/details/ports/index.html
new file mode 100644
index 00000000..8f738e77
--- /dev/null
+++ b/docs/en/details/ports/index.html
@@ -0,0 +1,209 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Ports - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../../details.html'>Details</a>
+</li>
+<li class=' level1'>
+<a class='' href='../faq.html'>FAQ</a>
+</li>
+<li class=' level1'>
+<a class='' href='../development.html'>Development</a>
+</li>
+<li class='active level1'>
+<a class='' href='../ports.html'>Ports</a>
+</li>
+<li class=' level1'>
+<a class='' href='../under-the-hood.html'>Under the hood</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Ports</h1>
+
+<div id='summary'>The required open ports for different services.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="index.html#publicly-open-ports">Publicly open ports</a>
+ </li>
+ <li>
+ <a href="index.html#privately-open-ports">Privately open ports</a>
+ </li>
+</ol></div>
+
+<p>There are many different ports that must be open in order for the LEAP platform to work. Some ports must be <em>publicly open</em>, meaning that these should be accessible from the public internet. Other ports are <em>privately open</em>, meaning that they must be accessible to sysadmins or to the other nodes in the provider&rsquo;s infrastructure.</p>
+
+<p>Every node already includes a host-based firewall. However, if your network has its own firewall, you need to make sure that these ports are not blocked.</p>
+
+<h2><a name="publicly-open-ports"></a>Publicly open ports</h2>
+
+<table class="table table-striped">
+<tr>
+ <th>Name</th>
+ <th>Node Type</th>
+ <th>Default</th>
+ <th>Notes</th>
+</tr>
+<tr>
+ <td>SMTP</td>
+ <td>mx</td>
+ <td>25</td>
+ <td>This is required for all server-to-server SMTP email relay. This is not configurable.</td>
+</tr>
+<tr>
+ <td>HTTP</td>
+ <td>webapp</td>
+ <td>80</td>
+ <td>Although no actual services are available over port 80, it should be unblocked so that the web app can redirect to port 443. This is not configurable.</td>
+</tr>
+<tr>
+ <td>HTTPS</td>
+ <td>webapp</td>
+ <td>443</td>
+ <td>The web application is available over this port. This is not configurable.</td>
+</tr>
+<tr>
+ <td>SMTPS</td>
+ <td>mx</td>
+ <td>465</td>
+ <td>The client uses this port to submit outgoing email messages via SMTP over TLS. There is no easy way to change this, although you can create a custom <code>files/service-definitions/v1/smtp-service.json.erb</code> to do so. This will be changed to port 443 in the future.</td>
+</tr>
+<tr>
+ <td>Soledad</td>
+ <td>soledad</td>
+ <td>2323</td>
+ <td>The client uses this port to synchronize its storage data. This can be changed via the configuration property <code>soledad.port</code>. This will be changed to port 443 in the future.</td>
+</tr>
+<tr>
+ <td>Nicknym</td>
+ <td>webapp</td>
+ <td>6425</td>
+ <td>The client uses this port for discovering public keys. This can be changed via the configuration property <code>nickserver.port</code>. This will be changed to port 443 in the future.</td>
+</tr>
+<tr>
+ <td>OpenVPN</td>
+ <td>openvpn</td>
+ <td>80, 443, 53, 1194</td>
+ <td>By default, OpenVPN gateways will listen on all those ports. This can be changed via the configuration property <code>openvpn.ports</code>. Note that these ports must be open for <code>openvpn.gateway_address</code>, not for <code>ip_address</code>.</td>
+</tr>
+<tr>
+ <td>API</td>
+ <td>webapp</td>
+ <td>4430</td>
+ <td>Currently, the provider API is accessible via this port. In the future, the default will be changed to 443. For now, this can be changed via the configuration property <code>api.port</code>.</td>
+</tr>
+</table>
+
+
+<h2><a name="privately-open-ports"></a>Privately open ports</h2>
+
+<table class="table table-striped">
+<tr>
+ <th>Name</th>
+ <th>Node Type</th>
+ <th>Default</th>
+ <th>Notes</th>
+</tr>
+<tr>
+ <td>SSH</td>
+ <td>all</td>
+ <td>22</td>
+ <td>This is the port that the sshd is bound to for the node. You can modify this using the configuration property <code>ssh.port</code>. It is important that this port is never blocked, or you will lose access to deploy to this node.</td>
+</tr>
+<tr>
+ <td>Stunnel</td>
+ <td>all</td>
+ <td>10000-20000</td>
+ <td>This is the range of ports that might be used for the encrypted stunnel connections between two nodes. These port numbers are automatically generated, but will fall somewhere in the specified range.</td>
+</tr>
+</table>
+
+
+
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/details/under-the-hood.html b/docs/en/details/under-the-hood.html
new file mode 100644
index 00000000..9b7853e3
--- /dev/null
+++ b/docs/en/details/under-the-hood.html
@@ -0,0 +1,168 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Under the hood - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+<li class=' level1'>
+<a class='' href='faq.html'>FAQ</a>
+</li>
+<li class=' level1'>
+<a class='' href='development.html'>Development</a>
+</li>
+<li class=' level1'>
+<a class='' href='ports.html'>Ports</a>
+</li>
+<li class='active level1'>
+<a class='' href='under-the-hood.html'>Under the hood</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Under the hood</h1>
+
+<div id='summary'>Various implementation details.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="under-the-hood/index.html#puppet-details">Puppet Details</a>
+ <ol>
+ <li>
+ <a href="under-the-hood/index.html#tags">Tags</a>
+ <ol>
+ <li>
+ <a href="under-the-hood/index.html#doing-faster-partial-deploys">Doing faster partial deploys</a>
+ </li>
+ </ol>
+ </li>
+ </ol>
+ </li>
+</ol></div>
+
+<p>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.</p>
+
+<h1><a name="puppet-details"></a>Puppet Details</h1>
+
+<h2><a name="tags"></a>Tags</h2>
+
+<p>Tags are beeing used to deploy different classes.</p>
+
+<ul>
+<li>leap_base: site_config::default (configure hostname + resolver, sshd, )</li>
+<li>leap_slow: site_config::slow (slow: apt-get update, apt-get dist-upgrade)</li>
+<li>leap_service: cofigure platform service (openvpn, couchdb, etc.)</li>
+</ul>
+
+
+<p>You can pass any combination of tags, i.e. use</p>
+
+<ul>
+<li>&ldquo;&ndash;tags leap_base,leap_slow,leap_service&rdquo; (DEFAULT): Deploy all</li>
+<li>&ldquo;&ndash;tags leap_service&rdquo;: Only deploy service(s) (useful for debugging/development)</li>
+<li>&ldquo;&ndash;tags leap_base&rdquo;: Only deploy basic configuration (again, useful for debugging/development)</li>
+</ul>
+
+
+<h3><a name="doing-faster-partial-deploys"></a>Doing faster partial deploys</h3>
+
+<p>If you only change a tiny bit on the platform puppet recipes, you could achieve a
+<em>much</em> faster deploy specifying the resource tag you changed.
+i.e. you changed the way rsyslog config snippets for LEAP logfiles are created
+in <code>puppet/modules/leap/manifests/logfile.pp</code>. This <code>define</code> resource will get tagged
+automatically with <code>leap::logfile</code> and you can deploy the change with:</p>
+
+<pre><code>leap deploy *NODE* --fast --tags=leap::logfile
+</code></pre>
+
+<p>or, if you just want</p>
+
+<pre><code>leap deploy --tags=dist_upgrade
+</code></pre>
+
+<p>See <a href="http://docs.puppetlabs.com/puppet/2.7/reference/lang_tags.html">http://docs.puppetlabs.com/puppet/2.7/reference/lang_tags.html</a> for puppet tag usage.</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/details/under-the-hood/index.html b/docs/en/details/under-the-hood/index.html
new file mode 100644
index 00000000..e75503f5
--- /dev/null
+++ b/docs/en/details/under-the-hood/index.html
@@ -0,0 +1,168 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Under the hood - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../../details.html'>Details</a>
+</li>
+<li class=' level1'>
+<a class='' href='../faq.html'>FAQ</a>
+</li>
+<li class=' level1'>
+<a class='' href='../development.html'>Development</a>
+</li>
+<li class=' level1'>
+<a class='' href='../ports.html'>Ports</a>
+</li>
+<li class='active level1'>
+<a class='' href='../under-the-hood.html'>Under the hood</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Under the hood</h1>
+
+<div id='summary'>Various implementation details.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="index.html#puppet-details">Puppet Details</a>
+ <ol>
+ <li>
+ <a href="index.html#tags">Tags</a>
+ <ol>
+ <li>
+ <a href="index.html#doing-faster-partial-deploys">Doing faster partial deploys</a>
+ </li>
+ </ol>
+ </li>
+ </ol>
+ </li>
+</ol></div>
+
+<p>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.</p>
+
+<h1><a name="puppet-details"></a>Puppet Details</h1>
+
+<h2><a name="tags"></a>Tags</h2>
+
+<p>Tags are beeing used to deploy different classes.</p>
+
+<ul>
+<li>leap_base: site_config::default (configure hostname + resolver, sshd, )</li>
+<li>leap_slow: site_config::slow (slow: apt-get update, apt-get dist-upgrade)</li>
+<li>leap_service: cofigure platform service (openvpn, couchdb, etc.)</li>
+</ul>
+
+
+<p>You can pass any combination of tags, i.e. use</p>
+
+<ul>
+<li>&ldquo;&ndash;tags leap_base,leap_slow,leap_service&rdquo; (DEFAULT): Deploy all</li>
+<li>&ldquo;&ndash;tags leap_service&rdquo;: Only deploy service(s) (useful for debugging/development)</li>
+<li>&ldquo;&ndash;tags leap_base&rdquo;: Only deploy basic configuration (again, useful for debugging/development)</li>
+</ul>
+
+
+<h3><a name="doing-faster-partial-deploys"></a>Doing faster partial deploys</h3>
+
+<p>If you only change a tiny bit on the platform puppet recipes, you could achieve a
+<em>much</em> faster deploy specifying the resource tag you changed.
+i.e. you changed the way rsyslog config snippets for LEAP logfiles are created
+in <code>puppet/modules/leap/manifests/logfile.pp</code>. This <code>define</code> resource will get tagged
+automatically with <code>leap::logfile</code> and you can deploy the change with:</p>
+
+<pre><code>leap deploy *NODE* --fast --tags=leap::logfile
+</code></pre>
+
+<p>or, if you just want</p>
+
+<pre><code>leap deploy --tags=dist_upgrade
+</code></pre>
+
+<p>See <a href="http://docs.puppetlabs.com/puppet/2.7/reference/lang_tags.html">http://docs.puppetlabs.com/puppet/2.7/reference/lang_tags.html</a> for puppet tag usage.</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/guide.html b/docs/en/guide.html
new file mode 100644
index 00000000..b79582d7
--- /dev/null
+++ b/docs/en/guide.html
@@ -0,0 +1,192 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Guide - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../index.html'>Home</a>
+</li>
+<li class='active level0'>
+<a class='' href='guide.html'>Guide</a>
+</li>
+<li class=' level1'>
+<a class='' href='guide/getting-started.html'>Getting Started</a>
+</li>
+<li class=' level1'>
+<a class='' href='guide/config.html'>Configuration Files</a>
+</li>
+<li class=' level1'>
+<a class='' href='guide/nodes.html'>Nodes</a>
+</li>
+<li class=' level1'>
+<a class='' href='guide/keys-and-certificates.html'>Keys and Certificates</a>
+</li>
+<li class=' level1'>
+<a class='' href='guide/domains.html'>Domains</a>
+</li>
+<li class=' level1'>
+<a class='' href='guide/provider-configuration.html'>Provider Configuration</a>
+</li>
+<li class=' level1'>
+<a class='' href='guide/environments.html'>Environments</a>
+</li>
+<li class=' level1'>
+<a class='' href='guide/virtual-machines.html'>Virtual Machines</a>
+</li>
+<li class=' level1'>
+<a class='' href='guide/miscellaneous.html'>Miscellaneous</a>
+</li>
+<li class=' level1'>
+<a class='' href='guide/commands.html'>Command Line Reference</a>
+</li>
+<li class=' level0'>
+<a class='' href='tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Platform Guide</h1>
+
+<div id='summary'>The LEAP Platform is set of complementary packages and server recipes to automate the maintenance of LEAP services in a hardened Debian environment.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+</ol></div>
+
+<div class=' page-summary'>
+ <h2>
+ <a href='guide/getting-started.html'>Getting Started</a>
+ </h2>
+ <div class='summary'>An overview of the LEAP Platform</div>
+</div>
+<div class=' page-summary'>
+ <h2>
+ <a href='guide/config.html'>Configuration Files</a>
+ </h2>
+ <div class='summary'>Understanding and editing the configuration files.</div>
+</div>
+<div class=' page-summary'>
+ <h2>
+ <a href='guide/nodes.html'>Nodes</a>
+ </h2>
+ <div class='summary'>Working with nodes, services, tags, and locations.</div>
+</div>
+<div class=' page-summary'>
+ <h2>
+ <a href='guide/keys-and-certificates.html'>Keys and Certificates</a>
+ </h2>
+ <div class='summary'>Working with SSH keys, secrets, and X.509 certificates.</div>
+</div>
+<div class=' page-summary'>
+ <h2>
+ <a href='guide/domains.html'>Domains</a>
+ </h2>
+ <div class='summary'>How to handle domain names and integrating LEAP with existing services.</div>
+</div>
+<div class=' page-summary'>
+ <h2>
+ <a href='guide/provider-configuration.html'>Provider Configuration</a>
+ </h2>
+ <div class='summary'>Explore how to configure your provider.</div>
+</div>
+<div class=' page-summary'>
+ <h2>
+ <a href='guide/environments.html'>Environments</a>
+ </h2>
+ <div class='summary'>How to partition the nodes into separate environments.</div>
+</div>
+<div class=' page-summary'>
+ <h2>
+ <a href='guide/virtual-machines.html'>Virtual Machines</a>
+ </h2>
+ <div class='summary'>Running LEAP platform on remote virtual machines</div>
+</div>
+<div class=' page-summary'>
+ <h2>
+ <a href='guide/miscellaneous.html'>Miscellaneous</a>
+ </h2>
+ <div class='summary'>Miscellaneous commands you may need to know.</div>
+</div>
+<div class=' page-summary'>
+ <h2>
+ <a href='guide/commands.html'>Command Line Reference</a>
+ </h2>
+ <div class='summary'>A copy of leap --help</div>
+</div>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/guide/commands.html b/docs/en/guide/commands.html
new file mode 100644
index 00000000..8685c6dc
--- /dev/null
+++ b/docs/en/guide/commands.html
@@ -0,0 +1,977 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Command Line Reference - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class=' level1'>
+<a class='' href='getting-started.html'>Getting Started</a>
+</li>
+<li class=' level1'>
+<a class='' href='config.html'>Configuration Files</a>
+</li>
+<li class=' level1'>
+<a class='' href='nodes.html'>Nodes</a>
+</li>
+<li class=' level1'>
+<a class='' href='keys-and-certificates.html'>Keys and Certificates</a>
+</li>
+<li class=' level1'>
+<a class='' href='domains.html'>Domains</a>
+</li>
+<li class=' level1'>
+<a class='' href='provider-configuration.html'>Provider Configuration</a>
+</li>
+<li class=' level1'>
+<a class='' href='environments.html'>Environments</a>
+</li>
+<li class=' level1'>
+<a class='' href='virtual-machines.html'>Virtual Machines</a>
+</li>
+<li class=' level1'>
+<a class='' href='miscellaneous.html'>Miscellaneous</a>
+</li>
+<li class='active level1'>
+<a class='' href='commands.html'>Command Line Reference</a>
+</li>
+<li class=' level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Command Line Reference</h1>
+
+<div id='summary'>A copy of leap --help</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="commands/index.html#global-options">Global Options</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-add-user">leap add-user</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-cert">leap cert</a>
+ <ol>
+ <li>
+ <a href="commands/index.html#leap-cert-ca">leap cert ca</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-cert-csr-domain">leap cert csr DOMAIN</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-cert-dh">leap cert dh</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-cert-register">leap cert register</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-cert-renew-domain">leap cert renew DOMAIN</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-cert-update-filter">leap cert update FILTER</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-clean">leap clean</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-compile">leap compile</a>
+ <ol>
+ <li>
+ <a href="commands/index.html#leap-compile-all-environment">leap compile all [ENVIRONMENT]</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-compile-firewall">leap compile firewall</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-compile-hosts">leap compile hosts</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-compile-providerjson">leap compile provider.json</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-compile-zone">leap compile zone</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-db">leap db</a>
+ <ol>
+ <li>
+ <a href="commands/index.html#leap-db-destroy-filter">leap db destroy [FILTER]</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-deploy-filter">leap deploy FILTER</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-env">leap env</a>
+ <ol>
+ <li>
+ <a href="commands/index.html#leap-env-ls-environment">leap env ls [ENVIRONMENT]</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-env-pin-environment">leap env pin ENVIRONMENT</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-env-unpin">leap env unpin</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-facts">leap facts</a>
+ <ol>
+ <li>
+ <a href="commands/index.html#leap-facts-update-filter">leap facts update FILTER</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-help-command">leap help command</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-history-filter">leap history FILTER</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-info-filter">leap info FILTER</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-inspect-file">leap inspect FILE</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-list-filter">leap list [FILTER]</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-local">leap local</a>
+ <ol>
+ <li>
+ <a href="commands/index.html#leap-local-ls-filter">leap local ls [FILTER]</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-local-reset-filter">leap local reset [FILTER]</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-local-rm-filter">leap local rm [FILTER]</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-local-save-filter">leap local save [FILTER]</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-local-start-filter">leap local start [FILTER]</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-local-stop-filter">leap local stop [FILTER]</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-mosh-name">leap mosh NAME</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-new-directory">leap new DIRECTORY</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-node">leap node</a>
+ <ol>
+ <li>
+ <a href="commands/index.html#leap-node-add-name-seed">leap node add NAME [SEED]</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-node-init-filter">leap node init FILTER</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-node-mv-old_name-new_name">leap node mv OLD_NAME NEW_NAME</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-node-rm-name">leap node rm NAME</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-open-name">leap open NAME</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-run-command-filter">leap run COMMAND FILTER</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-scp-file1-file2">leap scp FILE1 FILE2</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-ssh-name">leap ssh NAME</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-test">leap test</a>
+ <ol>
+ <li>
+ <a href="commands/index.html#leap-test-init">leap test init</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-test-run-filter">leap test run [FILTER]</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-tunnel-local_portnameremote_port">leap tunnel [LOCAL_PORT:]NAME:REMOTE_PORT</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-user">leap user</a>
+ <ol>
+ <li>
+ <a href="commands/index.html#leap-user-add-username">leap user add USERNAME</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-user-ls">leap user ls</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-user-rm-username">leap user rm USERNAME</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-vm">leap vm</a>
+ <ol>
+ <li>
+ <a href="commands/index.html#leap-vm-add-node_name-seed">leap vm add NODE_NAME [SEED]</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-vm-bind-node_name-instance_id">leap vm bind NODE_NAME INSTANCE_ID</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-vm-key-list">leap vm key-list</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-vm-key-register">leap vm key-register</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-vm-rm-filter">leap vm rm [FILTER]</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-vm-start-filter">leap vm start [FILTER]</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-vm-status-filter">leap vm status [FILTER]</a>
+ </li>
+ <li>
+ <a href="commands/index.html#leap-vm-stop-filter">leap vm stop [FILTER]</a>
+ </li>
+ </ol>
+ </li>
+</ol></div>
+
+<p>The command &ldquo;leap&rdquo; can be used to manage a bevy of servers running the LEAP platform from the comfort of your own home.</p>
+
+<h1><a name="global-options"></a>Global Options</h1>
+
+<ul>
+<li><p><code>--log FILE</code>
+Override default log file.
+Default Value: None</p></li>
+<li><p><code>-v|--verbose LEVEL</code>
+Verbosity level 0..5
+Default Value: 1</p></li>
+<li><p><code>--[no-]color</code>
+Disable colors in output.</p></li>
+<li><p><code>-d|--debug</code>
+Print full stack trace for exceptions and load <code>debugger</code> gem if installed.</p></li>
+<li><p><code>--force</code>
+Like &ndash;yes, but also skip prompts that are potentially dangerous to skip.</p></li>
+<li><p><code>--help</code>
+Show this message</p></li>
+<li><p><code>--version</code>
+Display version number and exit.</p></li>
+<li><p><code>--yes</code>
+Skip prompts and assume &ldquo;yes&rdquo;.</p></li>
+</ul>
+
+
+<h1><a name="leap-add-user"></a>leap add-user</h1>
+
+<p>Manage trusted sysadmins (DEPRECATED)</p>
+
+<p>Use <code>leap user add</code> instead</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--pgp-pub-key arg</code>
+OpenPGP public key file for this new user
+Default Value: None</p></li>
+<li><p><code>--ssh-pub-key arg</code>
+SSH public key file for this new user
+Default Value: None</p></li>
+<li><p><code>--self</code>
+Add yourself as a trusted sysadmin by choosing among the public keys available for the current user.</p></li>
+</ul>
+
+
+<h1><a name="leap-cert"></a>leap cert</h1>
+
+<p>Manage X.509 certificates</p>
+
+<h2><a name="leap-cert-ca"></a>leap cert ca</h2>
+
+<p>Creates two Certificate Authorities (one for validating servers and one for validating clients).</p>
+
+<p>See see what values are used in the generation of the certificates (like name and key size), run <code>leap inspect provider</code> and look for the &ldquo;ca&rdquo; property. To see the details of the created certs, run <code>leap inspect &lt;file&gt;</code>.</p>
+
+<h2><a name="leap-cert-csr-domain"></a>leap cert csr DOMAIN</h2>
+
+<p>Creates a CSR for use in buying a commercial X.509 certificate.</p>
+
+<p>Unless specified, the CSR is created for the provider&rsquo;s primary domain. The properties used for this CSR come from <code>provider.ca.server_certificates</code>, but may be overridden here.</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--bits BITS</code>
+Override default certificate bit length
+Default Value: None</p></li>
+<li><p><code>--country|-C COUNTRY</code>
+Set C in distinguished name.
+Default Value: None</p></li>
+<li><p><code>--digest DIGEST</code>
+Override default signature digest
+Default Value: None</p></li>
+<li><p><code>--domain DOMAIN</code>
+Specify what domain to create the CSR for.
+Unless specified, the CSR is created for the provider&rsquo;s primary domain. The properties used for this CSR come from <code>provider.ca.server_certificates</code>, but may be overridden here.
+Default Value: None</p></li>
+<li><p><code>--email EMAIL</code>
+Set emailAddress in distinguished name.
+Default Value: None</p></li>
+<li><p><code>--locality|-L LOCALITY</code>
+Set L in distinguished name.
+Default Value: None</p></li>
+<li><p><code>--organization|-O ORGANIZATION</code>
+Override default O in distinguished name.
+Default Value: None</p></li>
+<li><p><code>--state|--ST STATE</code>
+Set ST in distinguished name.
+Default Value: None</p></li>
+<li><p><code>--unit|--OU UNIT</code>
+Set OU in distinguished name.
+Default Value: None</p></li>
+</ul>
+
+
+<h2><a name="leap-cert-dh"></a>leap cert dh</h2>
+
+<p>Creates a Diffie-Hellman parameter file, needed for forward secret OpenVPN ciphers.</p>
+
+<h2><a name="leap-cert-register"></a>leap cert register</h2>
+
+<p>Register an authorization key with the CA letsencrypt.org</p>
+
+<p>This only needs to be done once.</p>
+
+<h2><a name="leap-cert-renew-domain"></a>leap cert renew DOMAIN</h2>
+
+<p>Renews a certificate using the CA letsencrypt.org</p>
+
+<h2><a name="leap-cert-update-filter"></a>leap cert update FILTER</h2>
+
+<p>Creates or renews a X.509 certificate/key pair for a single node or all nodes, but only if needed.</p>
+
+<p>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 &ndash;force. If <node-filter> is empty, this command will apply to all nodes.</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><code>--force</code>
+Always generate new certificates</li>
+</ul>
+
+
+<h1><a name="leap-clean"></a>leap clean</h1>
+
+<p>Removes all files generated with the &ldquo;compile&rdquo; command.</p>
+
+<h1><a name="leap-compile"></a>leap compile</h1>
+
+<p>Compile generated files.</p>
+
+<h2><a name="leap-compile-all-environment"></a>leap compile all [ENVIRONMENT]</h2>
+
+<p>Compiles node configuration files into hiera files used for deployment.</p>
+
+<h2><a name="leap-compile-firewall"></a>leap compile firewall</h2>
+
+<p>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.</p>
+
+<h2><a name="leap-compile-hosts"></a>leap compile hosts</h2>
+
+<p>Print entries suitable for an /etc/hosts file, useful for testing your provider.</p>
+
+<h2><a name="leap-compile-providerjson"></a>leap compile provider.json</h2>
+
+<p>Compile provider.json bootstrap files for your provider.</p>
+
+<h2><a name="leap-compile-zone"></a>leap compile zone</h2>
+
+<p>Prints a DNS zone file for your provider.</p>
+
+<p>Default Command: all</p>
+
+<h1><a name="leap-db"></a>leap db</h1>
+
+<p>Database commands.</p>
+
+<h2><a name="leap-db-destroy-filter"></a>leap db destroy [FILTER]</h2>
+
+<p>Destroy one or more databases. If present, limit to FILTER nodes. For example <code>leap db destroy --db sessions,tokens testing</code>.</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--db DATABASES</code>
+Comma separated list of databases to destroy (no space). Use &ldquo;&ndash;db all&rdquo; to destroy all databases.
+Default Value: None</p></li>
+<li><p><code>--user USERS</code>
+Comma separated list of usernames. The storage databases for these user(s) will be destroyed.
+Default Value: None</p></li>
+</ul>
+
+
+<h1><a name="leap-deploy-filter"></a>leap deploy FILTER</h1>
+
+<p>Apply recipes to a node or set of nodes.</p>
+
+<p>The FILTER can be the name of a node, service, or tag.</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--ip IPADDRESS</code>
+Override the default SSH IP address.
+Default Value: None</p></li>
+<li><p><code>--port PORT</code>
+Override the default SSH port.
+Default Value: None</p></li>
+<li><p><code>--tags TAG[,TAG]</code>
+Specify tags to pass through to puppet (overriding the default).
+Default Value: None</p></li>
+<li><p><code>--dev</code>
+Development mode: don&rsquo;t run &lsquo;git submodule update&rsquo; before deploy.</p></li>
+<li><p><code>--downgrade</code>
+Allows deploy to run with an older platform version.</p></li>
+<li><p><code>--fast</code>
+Makes the deploy command faster by skipping some slow steps. A &ldquo;fast&rdquo; deploy can be used safely if you recently completed a normal deploy.</p></li>
+<li><p><code>--force</code>
+Deploy even if there is a lockfile.</p></li>
+<li><p><code>--sync</code>
+Sync files, but don&rsquo;t actually apply recipes.</p></li>
+</ul>
+
+
+<h1><a name="leap-env"></a>leap env</h1>
+
+<p>Manipulate and query environment information.</p>
+
+<p>The &lsquo;environment&rsquo; 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&rsquo;t apply if you move the directory)</p>
+
+<h2><a name="leap-env-ls-environment"></a>leap env ls [ENVIRONMENT]</h2>
+
+<p>List the available environments. The pinned environment, if any, will be marked with &lsquo;*&rsquo;. Will also set the pin if run with an environment argument.</p>
+
+<h2><a name="leap-env-pin-environment"></a>leap env pin ENVIRONMENT</h2>
+
+<p>Pin the environment to ENVIRONMENT. All subsequent commands will only apply to nodes in this environment.</p>
+
+<h2><a name="leap-env-unpin"></a>leap env unpin</h2>
+
+<p>Unpin the environment. All subsequent commands will apply to all nodes.</p>
+
+<p>Default Command: ls</p>
+
+<h1><a name="leap-facts"></a>leap facts</h1>
+
+<p>Gather information on nodes.</p>
+
+<h2><a name="leap-facts-update-filter"></a>leap facts update FILTER</h2>
+
+<p>Query servers to update facts.json.</p>
+
+<p>Queries every node included in FILTER and saves the important information to facts.json</p>
+
+<h1><a name="leap-help-command"></a>leap help command</h1>
+
+<p>Shows a list of commands or help for one command</p>
+
+<p>Gets help for the application or its commands. Can also list the commands in a way helpful to creating a bash-style completion function</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><code>-c</code>
+List commands one per line, to assist with shell completion</li>
+</ul>
+
+
+<h1><a name="leap-history-filter"></a>leap history FILTER</h1>
+
+<p>Display recent deployment history for a set of nodes.</p>
+
+<p>The FILTER can be the name of a node, service, or tag.</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--ip IPADDRESS</code>
+Override the default SSH IP address.
+Default Value: None</p></li>
+<li><p><code>--port PORT</code>
+Override the default SSH port.
+Default Value: None</p></li>
+<li><p><code>--last</code>
+Show last deploy only</p></li>
+</ul>
+
+
+<h1><a name="leap-info-filter"></a>leap info FILTER</h1>
+
+<p>Prints information regarding facts, history, and running processes for a node or nodes.</p>
+
+<p>The FILTER can be the name of a node, service, or tag.</p>
+
+<h1><a name="leap-inspect-file"></a>leap inspect FILE</h1>
+
+<p>Prints details about a file. Alternately, the argument FILE can be the name of a node, service or tag.</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><code>--base</code>
+Inspect the FILE from the provider_base (i.e. without local inheritance).</li>
+</ul>
+
+
+<h1><a name="leap-list-filter"></a>leap list [FILTER]</h1>
+
+<p>List nodes and their classifications</p>
+
+<p>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:</p>
+
+<p><code>leap list node1 node2</code> matches all nodes named &ldquo;node1&rdquo; OR &ldquo;node2&rdquo;</p>
+
+<p><code>leap list openvpn +local</code> matches all nodes with service &ldquo;openvpn&rdquo; AND tag &ldquo;local&rdquo;</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--print arg</code>
+What attributes to print (optional)
+Default Value: None</p></li>
+<li><p><code>--disabled</code>
+Include disabled nodes in the list.</p></li>
+</ul>
+
+
+<h1><a name="leap-local"></a>leap local</h1>
+
+<p>Manage local virtual machines.</p>
+
+<p>This command provides a convenient 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 &lsquo;test/Vagrantfile&rsquo;. If you want to run vagrant commands manually, cd to &lsquo;test&rsquo;.</p>
+
+<h2><a name="leap-local-ls-filter"></a>leap local ls [FILTER]</h2>
+
+<p>Print the status of local virtual machine(s)</p>
+
+<h2><a name="leap-local-reset-filter"></a>leap local reset [FILTER]</h2>
+
+<p>Resets virtual machine(s) to the last saved snapshot</p>
+
+<h2><a name="leap-local-rm-filter"></a>leap local rm [FILTER]</h2>
+
+<p>Destroys the virtual machine(s), reclaiming the disk space</p>
+
+<h2><a name="leap-local-save-filter"></a>leap local save [FILTER]</h2>
+
+<p>Saves the current state of the virtual machine as a new snapshot</p>
+
+<h2><a name="leap-local-start-filter"></a>leap local start [FILTER]</h2>
+
+<p>Starts up the virtual machine(s)</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><code>--basebox BASEBOX</code>
+The basebox to use. This value is passed to vagrant as the <code>config.vm.box</code> option. The value here should be the name of an installed box or a shorthand name of a box in HashiCorp&rsquo;s Atlas.
+Default Value: LEAP/jessie</li>
+</ul>
+
+
+<h2><a name="leap-local-stop-filter"></a>leap local stop [FILTER]</h2>
+
+<p>Shuts down the virtual machine(s)</p>
+
+<h1><a name="leap-mosh-name"></a>leap mosh NAME</h1>
+
+<p>Log in to the specified node with an interactive shell using mosh (requires node to have mosh.enabled set to true).</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--port SSH_PORT</code>
+Override default SSH port used when trying to connect to the server. Same as <code>--ssh "-p SSH_PORT"</code>.
+Default Value: None</p></li>
+<li><p><code>--ssh arg</code>
+Pass through raw options to ssh (e.g. <code>--ssh '-F ~/sshconfig'</code>).
+Default Value: None</p></li>
+</ul>
+
+
+<h1><a name="leap-new-directory"></a>leap new DIRECTORY</h1>
+
+<p>Creates a new provider instance in the specified directory, creating it if necessary.</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--contacts arg</code>
+Default email address contacts.
+Default Value: None</p></li>
+<li><p><code>--domain arg</code>
+The primary domain of the provider.
+Default Value: None</p></li>
+<li><p><code>--name arg</code>
+The name of the provider.
+Default Value: None</p></li>
+<li><p><code>--platform arg</code>
+File path of the leap_platform directory.
+Default Value: None</p></li>
+</ul>
+
+
+<h1><a name="leap-node"></a>leap node</h1>
+
+<p>Node management</p>
+
+<h2><a name="leap-node-add-name-seed"></a>leap node add NAME [SEED]</h2>
+
+<p>Create a new configuration file for a node named NAME.</p>
+
+<p>If specified, the optional argument SEED can be used to seed values in the node configuration file.</p>
+
+<p>The format is property_name:value.</p>
+
+<p>For example: <code>leap node add web1 ip_address:1.2.3.4 services:webapp</code>.</p>
+
+<p>To set nested properties, property name can contain &lsquo;.&rsquo;, like so: <code>leap node add web1 ssh.port:44</code></p>
+
+<p>Separate multiple values for a single property with a comma, like so: <code>leap node add mynode services:webapp,dns</code></p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--local</code>
+Make a local testing node (by assigning the next available local IP address). Local nodes are run as virtual machines on your computer.</p></li>
+<li><p><code>--vm</code>
+Make a remote virtual machine for this node. Requires a valid cloud.json configuration.</p></li>
+</ul>
+
+
+<h2><a name="leap-node-init-filter"></a>leap node init FILTER</h2>
+
+<p>Bootstraps a node or nodes, setting up SSH keys and installing prerequisite packages</p>
+
+<p>This command prepares a server to be used with the LEAP Platform by saving the server&rsquo;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.</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--ip IPADDRESS</code>
+Override the default SSH IP address.
+Default Value: None</p></li>
+<li><p><code>--port PORT</code>
+Override the default SSH port.
+This command prepares a server to be used with the LEAP Platform by saving the server&rsquo;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.
+Default Value: None</p></li>
+</ul>
+
+
+<h2><a name="leap-node-mv-old_name-new_name"></a>leap node mv OLD_NAME NEW_NAME</h2>
+
+<p>Renames a node file, and all its related files.</p>
+
+<h2><a name="leap-node-rm-name"></a>leap node rm NAME</h2>
+
+<p>Removes all the files related to the node named NAME.</p>
+
+<h1><a name="leap-open-name"></a>leap open NAME</h1>
+
+<p>Opens useful URLs in a web browser.</p>
+
+<p>NAME can be one or more of: monitor, web, docs, bug</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--env ENVIRONMENT</code>
+Which environment to use (optional).
+Default Value: None</p></li>
+<li><p><code>--[no-]ip</code>
+To get around HSTS or DNS, open the URL using the IP address instead of the domain (optional).</p></li>
+</ul>
+
+
+<h1><a name="leap-run-command-filter"></a>leap run COMMAND FILTER</h1>
+
+<p>Run a shell command remotely</p>
+
+<p>Runs the specified command COMMAND on each node in the FILTER set. For example, <code>leap run 'uname -a' webapp</code></p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--port SSH_PORT</code>
+Override default SSH port used when trying to connect to the server.
+Default Value: None</p></li>
+<li><p><code>--[no-]stream</code>
+If set, stream the output as it arrives. (default: &ndash;stream for a single node, &ndash;no-stream for multiple nodes)</p></li>
+</ul>
+
+
+<h1><a name="leap-scp-file1-file2"></a>leap scp FILE1 FILE2</h1>
+
+<p>Secure copy from FILE1 to FILE2. Files are specified as NODE_NAME:FILE_PATH. For local paths, omit &ldquo;NODE_NAME:&rdquo;.</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><code>-r</code>
+Copy recursively</li>
+</ul>
+
+
+<h1><a name="leap-ssh-name"></a>leap ssh NAME</h1>
+
+<p>Log in to the specified node with an interactive shell.</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--port SSH_PORT</code>
+Override default SSH port used when trying to connect to the server. Same as <code>--ssh "-p SSH_PORT"</code>.
+Default Value: None</p></li>
+<li><p><code>--ssh arg</code>
+Pass through raw options to ssh (e.g. <code>--ssh '-F ~/sshconfig'</code>).
+Default Value: None</p></li>
+</ul>
+
+
+<h1><a name="leap-test"></a>leap test</h1>
+
+<p>Run tests.</p>
+
+<h2><a name="leap-test-init"></a>leap test init</h2>
+
+<p>Creates files needed to run tests.</p>
+
+<h2><a name="leap-test-run-filter"></a>leap test run [FILTER]</h2>
+
+<p>Run the test suit on FILTER nodes.</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><code>--[no-]continue</code>
+Continue over errors and failures (default is &ndash;no-continue).</li>
+</ul>
+
+
+<p>Default Command: run</p>
+
+<h1><a name="leap-tunnel-local_portnameremote_port"></a>leap tunnel [LOCAL_PORT:]NAME:REMOTE_PORT</h1>
+
+<p>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: <code>leap tunnel couch1:5984</code>.</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--port SSH_PORT</code>
+Override default SSH port used when trying to connect to the server. Same as <code>--ssh "-p SSH_PORT"</code>.
+Default Value: None</p></li>
+<li><p><code>--ssh arg</code>
+Pass through raw options to ssh (e.g. &ndash;ssh &lsquo;-F ~/sshconfig&rsquo;).
+Default Value: None</p></li>
+</ul>
+
+
+<h1><a name="leap-user"></a>leap user</h1>
+
+<p>Manage trusted sysadmins</p>
+
+<p>Manage the trusted sysadmins that are configured in the &lsquo;users&rsquo; directory.</p>
+
+<h2><a name="leap-user-add-username"></a>leap user add USERNAME</h2>
+
+<p>Adds a new trusted sysadmin</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--pgp-pub-key arg</code>
+OpenPGP public key file for this new user
+Default Value: None</p></li>
+<li><p><code>--ssh-pub-key arg</code>
+SSH public key file for this new user
+Default Value: None</p></li>
+<li><p><code>--self</code>
+Add yourself as a trusted sysadmin by choosing among the public keys available for the current user.</p></li>
+</ul>
+
+
+<h2><a name="leap-user-ls"></a>leap user ls</h2>
+
+<p>Lists the configured sysadmins</p>
+
+<h2><a name="leap-user-rm-username"></a>leap user rm USERNAME</h2>
+
+<p>Removes a trusted sysadmin</p>
+
+<h1><a name="leap-vm"></a>leap vm</h1>
+
+<p>Manage remote virtual machines (VMs).</p>
+
+<p>This command provides a convenient way to manage virtual machines. FILTER may be a node filter or the ID of a virtual machine.</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--auth AUTH</code>
+Choose which authentication credentials to use from the file cloud.json. If omitted, will default to the node&rsquo;s <code>vm.auth</code> property, or the first credentials in cloud.json
+Default Value: None</p></li>
+<li><p><code>--[no-]mock</code>
+Run as simulation, without actually connecting to a cloud provider. If set, &ndash;auth is ignored.</p></li>
+<li><p><code>--[no-]wait</code>
+Wait for servers to start/stop before continuing.</p></li>
+</ul>
+
+
+<h2><a name="leap-vm-add-node_name-seed"></a>leap vm add NODE_NAME [SEED]</h2>
+
+<p>Allocates a new VM and/or associates it with node NAME.</p>
+
+<p>If node configuration file does not yet exist, it is created with the optional SEED values. You can run this command when the virtual machine already exists in order to update the node&rsquo;s <code>vm.id</code> property.</p>
+
+<h2><a name="leap-vm-bind-node_name-instance_id"></a>leap vm bind NODE_NAME INSTANCE_ID</h2>
+
+<p>Binds a running VM instance to a node configuration.</p>
+
+<p>Afterwards, the VM will be assigned a label matching the node name, and the node config will be updated with the instance ID.</p>
+
+<h2><a name="leap-vm-key-list"></a>leap vm key-list</h2>
+
+<p>Lists the registered SSH public keys for a particular VM provider.</p>
+
+<h2><a name="leap-vm-key-register"></a>leap vm key-register</h2>
+
+<p>Registers a SSH public key for use when creating new VMs.</p>
+
+<p>Note that only people who are creating new VM instances need to have their key registered.</p>
+
+<h2><a name="leap-vm-rm-filter"></a>leap vm rm [FILTER]</h2>
+
+<p>Destroys one or more VMs</p>
+
+<h2><a name="leap-vm-start-filter"></a>leap vm start [FILTER]</h2>
+
+<p>Starts one or more VMs</p>
+
+<h2><a name="leap-vm-status-filter"></a>leap vm status [FILTER]</h2>
+
+<p>Print the status of all VMs</p>
+
+<h2><a name="leap-vm-stop-filter"></a>leap vm stop [FILTER]</h2>
+
+<p>Shuts down one or more VMs</p>
+
+<p>This keeps the storage allocated. To save resources, run <code>leap vm rm</code> instead.</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/guide/commands/index.html b/docs/en/guide/commands/index.html
new file mode 100644
index 00000000..fbb77d87
--- /dev/null
+++ b/docs/en/guide/commands/index.html
@@ -0,0 +1,977 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Command Line Reference - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../../index.html'>Home</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../../guide.html'>Guide</a>
+</li>
+<li class=' level1'>
+<a class='' href='../getting-started.html'>Getting Started</a>
+</li>
+<li class=' level1'>
+<a class='' href='../config.html'>Configuration Files</a>
+</li>
+<li class=' level1'>
+<a class='' href='../nodes.html'>Nodes</a>
+</li>
+<li class=' level1'>
+<a class='' href='../keys-and-certificates.html'>Keys and Certificates</a>
+</li>
+<li class=' level1'>
+<a class='' href='../domains.html'>Domains</a>
+</li>
+<li class=' level1'>
+<a class='' href='../provider-configuration.html'>Provider Configuration</a>
+</li>
+<li class=' level1'>
+<a class='' href='../environments.html'>Environments</a>
+</li>
+<li class=' level1'>
+<a class='' href='../virtual-machines.html'>Virtual Machines</a>
+</li>
+<li class=' level1'>
+<a class='' href='../miscellaneous.html'>Miscellaneous</a>
+</li>
+<li class='active level1'>
+<a class='' href='../commands.html'>Command Line Reference</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Command Line Reference</h1>
+
+<div id='summary'>A copy of leap --help</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="index.html#global-options">Global Options</a>
+ </li>
+ <li>
+ <a href="index.html#leap-add-user">leap add-user</a>
+ </li>
+ <li>
+ <a href="index.html#leap-cert">leap cert</a>
+ <ol>
+ <li>
+ <a href="index.html#leap-cert-ca">leap cert ca</a>
+ </li>
+ <li>
+ <a href="index.html#leap-cert-csr-domain">leap cert csr DOMAIN</a>
+ </li>
+ <li>
+ <a href="index.html#leap-cert-dh">leap cert dh</a>
+ </li>
+ <li>
+ <a href="index.html#leap-cert-register">leap cert register</a>
+ </li>
+ <li>
+ <a href="index.html#leap-cert-renew-domain">leap cert renew DOMAIN</a>
+ </li>
+ <li>
+ <a href="index.html#leap-cert-update-filter">leap cert update FILTER</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#leap-clean">leap clean</a>
+ </li>
+ <li>
+ <a href="index.html#leap-compile">leap compile</a>
+ <ol>
+ <li>
+ <a href="index.html#leap-compile-all-environment">leap compile all [ENVIRONMENT]</a>
+ </li>
+ <li>
+ <a href="index.html#leap-compile-firewall">leap compile firewall</a>
+ </li>
+ <li>
+ <a href="index.html#leap-compile-hosts">leap compile hosts</a>
+ </li>
+ <li>
+ <a href="index.html#leap-compile-providerjson">leap compile provider.json</a>
+ </li>
+ <li>
+ <a href="index.html#leap-compile-zone">leap compile zone</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#leap-db">leap db</a>
+ <ol>
+ <li>
+ <a href="index.html#leap-db-destroy-filter">leap db destroy [FILTER]</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#leap-deploy-filter">leap deploy FILTER</a>
+ </li>
+ <li>
+ <a href="index.html#leap-env">leap env</a>
+ <ol>
+ <li>
+ <a href="index.html#leap-env-ls-environment">leap env ls [ENVIRONMENT]</a>
+ </li>
+ <li>
+ <a href="index.html#leap-env-pin-environment">leap env pin ENVIRONMENT</a>
+ </li>
+ <li>
+ <a href="index.html#leap-env-unpin">leap env unpin</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#leap-facts">leap facts</a>
+ <ol>
+ <li>
+ <a href="index.html#leap-facts-update-filter">leap facts update FILTER</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#leap-help-command">leap help command</a>
+ </li>
+ <li>
+ <a href="index.html#leap-history-filter">leap history FILTER</a>
+ </li>
+ <li>
+ <a href="index.html#leap-info-filter">leap info FILTER</a>
+ </li>
+ <li>
+ <a href="index.html#leap-inspect-file">leap inspect FILE</a>
+ </li>
+ <li>
+ <a href="index.html#leap-list-filter">leap list [FILTER]</a>
+ </li>
+ <li>
+ <a href="index.html#leap-local">leap local</a>
+ <ol>
+ <li>
+ <a href="index.html#leap-local-ls-filter">leap local ls [FILTER]</a>
+ </li>
+ <li>
+ <a href="index.html#leap-local-reset-filter">leap local reset [FILTER]</a>
+ </li>
+ <li>
+ <a href="index.html#leap-local-rm-filter">leap local rm [FILTER]</a>
+ </li>
+ <li>
+ <a href="index.html#leap-local-save-filter">leap local save [FILTER]</a>
+ </li>
+ <li>
+ <a href="index.html#leap-local-start-filter">leap local start [FILTER]</a>
+ </li>
+ <li>
+ <a href="index.html#leap-local-stop-filter">leap local stop [FILTER]</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#leap-mosh-name">leap mosh NAME</a>
+ </li>
+ <li>
+ <a href="index.html#leap-new-directory">leap new DIRECTORY</a>
+ </li>
+ <li>
+ <a href="index.html#leap-node">leap node</a>
+ <ol>
+ <li>
+ <a href="index.html#leap-node-add-name-seed">leap node add NAME [SEED]</a>
+ </li>
+ <li>
+ <a href="index.html#leap-node-init-filter">leap node init FILTER</a>
+ </li>
+ <li>
+ <a href="index.html#leap-node-mv-old_name-new_name">leap node mv OLD_NAME NEW_NAME</a>
+ </li>
+ <li>
+ <a href="index.html#leap-node-rm-name">leap node rm NAME</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#leap-open-name">leap open NAME</a>
+ </li>
+ <li>
+ <a href="index.html#leap-run-command-filter">leap run COMMAND FILTER</a>
+ </li>
+ <li>
+ <a href="index.html#leap-scp-file1-file2">leap scp FILE1 FILE2</a>
+ </li>
+ <li>
+ <a href="index.html#leap-ssh-name">leap ssh NAME</a>
+ </li>
+ <li>
+ <a href="index.html#leap-test">leap test</a>
+ <ol>
+ <li>
+ <a href="index.html#leap-test-init">leap test init</a>
+ </li>
+ <li>
+ <a href="index.html#leap-test-run-filter">leap test run [FILTER]</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#leap-tunnel-local_portnameremote_port">leap tunnel [LOCAL_PORT:]NAME:REMOTE_PORT</a>
+ </li>
+ <li>
+ <a href="index.html#leap-user">leap user</a>
+ <ol>
+ <li>
+ <a href="index.html#leap-user-add-username">leap user add USERNAME</a>
+ </li>
+ <li>
+ <a href="index.html#leap-user-ls">leap user ls</a>
+ </li>
+ <li>
+ <a href="index.html#leap-user-rm-username">leap user rm USERNAME</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#leap-vm">leap vm</a>
+ <ol>
+ <li>
+ <a href="index.html#leap-vm-add-node_name-seed">leap vm add NODE_NAME [SEED]</a>
+ </li>
+ <li>
+ <a href="index.html#leap-vm-bind-node_name-instance_id">leap vm bind NODE_NAME INSTANCE_ID</a>
+ </li>
+ <li>
+ <a href="index.html#leap-vm-key-list">leap vm key-list</a>
+ </li>
+ <li>
+ <a href="index.html#leap-vm-key-register">leap vm key-register</a>
+ </li>
+ <li>
+ <a href="index.html#leap-vm-rm-filter">leap vm rm [FILTER]</a>
+ </li>
+ <li>
+ <a href="index.html#leap-vm-start-filter">leap vm start [FILTER]</a>
+ </li>
+ <li>
+ <a href="index.html#leap-vm-status-filter">leap vm status [FILTER]</a>
+ </li>
+ <li>
+ <a href="index.html#leap-vm-stop-filter">leap vm stop [FILTER]</a>
+ </li>
+ </ol>
+ </li>
+</ol></div>
+
+<p>The command &ldquo;leap&rdquo; can be used to manage a bevy of servers running the LEAP platform from the comfort of your own home.</p>
+
+<h1><a name="global-options"></a>Global Options</h1>
+
+<ul>
+<li><p><code>--log FILE</code>
+Override default log file.
+Default Value: None</p></li>
+<li><p><code>-v|--verbose LEVEL</code>
+Verbosity level 0..5
+Default Value: 1</p></li>
+<li><p><code>--[no-]color</code>
+Disable colors in output.</p></li>
+<li><p><code>-d|--debug</code>
+Print full stack trace for exceptions and load <code>debugger</code> gem if installed.</p></li>
+<li><p><code>--force</code>
+Like &ndash;yes, but also skip prompts that are potentially dangerous to skip.</p></li>
+<li><p><code>--help</code>
+Show this message</p></li>
+<li><p><code>--version</code>
+Display version number and exit.</p></li>
+<li><p><code>--yes</code>
+Skip prompts and assume &ldquo;yes&rdquo;.</p></li>
+</ul>
+
+
+<h1><a name="leap-add-user"></a>leap add-user</h1>
+
+<p>Manage trusted sysadmins (DEPRECATED)</p>
+
+<p>Use <code>leap user add</code> instead</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--pgp-pub-key arg</code>
+OpenPGP public key file for this new user
+Default Value: None</p></li>
+<li><p><code>--ssh-pub-key arg</code>
+SSH public key file for this new user
+Default Value: None</p></li>
+<li><p><code>--self</code>
+Add yourself as a trusted sysadmin by choosing among the public keys available for the current user.</p></li>
+</ul>
+
+
+<h1><a name="leap-cert"></a>leap cert</h1>
+
+<p>Manage X.509 certificates</p>
+
+<h2><a name="leap-cert-ca"></a>leap cert ca</h2>
+
+<p>Creates two Certificate Authorities (one for validating servers and one for validating clients).</p>
+
+<p>See see what values are used in the generation of the certificates (like name and key size), run <code>leap inspect provider</code> and look for the &ldquo;ca&rdquo; property. To see the details of the created certs, run <code>leap inspect &lt;file&gt;</code>.</p>
+
+<h2><a name="leap-cert-csr-domain"></a>leap cert csr DOMAIN</h2>
+
+<p>Creates a CSR for use in buying a commercial X.509 certificate.</p>
+
+<p>Unless specified, the CSR is created for the provider&rsquo;s primary domain. The properties used for this CSR come from <code>provider.ca.server_certificates</code>, but may be overridden here.</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--bits BITS</code>
+Override default certificate bit length
+Default Value: None</p></li>
+<li><p><code>--country|-C COUNTRY</code>
+Set C in distinguished name.
+Default Value: None</p></li>
+<li><p><code>--digest DIGEST</code>
+Override default signature digest
+Default Value: None</p></li>
+<li><p><code>--domain DOMAIN</code>
+Specify what domain to create the CSR for.
+Unless specified, the CSR is created for the provider&rsquo;s primary domain. The properties used for this CSR come from <code>provider.ca.server_certificates</code>, but may be overridden here.
+Default Value: None</p></li>
+<li><p><code>--email EMAIL</code>
+Set emailAddress in distinguished name.
+Default Value: None</p></li>
+<li><p><code>--locality|-L LOCALITY</code>
+Set L in distinguished name.
+Default Value: None</p></li>
+<li><p><code>--organization|-O ORGANIZATION</code>
+Override default O in distinguished name.
+Default Value: None</p></li>
+<li><p><code>--state|--ST STATE</code>
+Set ST in distinguished name.
+Default Value: None</p></li>
+<li><p><code>--unit|--OU UNIT</code>
+Set OU in distinguished name.
+Default Value: None</p></li>
+</ul>
+
+
+<h2><a name="leap-cert-dh"></a>leap cert dh</h2>
+
+<p>Creates a Diffie-Hellman parameter file, needed for forward secret OpenVPN ciphers.</p>
+
+<h2><a name="leap-cert-register"></a>leap cert register</h2>
+
+<p>Register an authorization key with the CA letsencrypt.org</p>
+
+<p>This only needs to be done once.</p>
+
+<h2><a name="leap-cert-renew-domain"></a>leap cert renew DOMAIN</h2>
+
+<p>Renews a certificate using the CA letsencrypt.org</p>
+
+<h2><a name="leap-cert-update-filter"></a>leap cert update FILTER</h2>
+
+<p>Creates or renews a X.509 certificate/key pair for a single node or all nodes, but only if needed.</p>
+
+<p>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 &ndash;force. If <node-filter> is empty, this command will apply to all nodes.</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><code>--force</code>
+Always generate new certificates</li>
+</ul>
+
+
+<h1><a name="leap-clean"></a>leap clean</h1>
+
+<p>Removes all files generated with the &ldquo;compile&rdquo; command.</p>
+
+<h1><a name="leap-compile"></a>leap compile</h1>
+
+<p>Compile generated files.</p>
+
+<h2><a name="leap-compile-all-environment"></a>leap compile all [ENVIRONMENT]</h2>
+
+<p>Compiles node configuration files into hiera files used for deployment.</p>
+
+<h2><a name="leap-compile-firewall"></a>leap compile firewall</h2>
+
+<p>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.</p>
+
+<h2><a name="leap-compile-hosts"></a>leap compile hosts</h2>
+
+<p>Print entries suitable for an /etc/hosts file, useful for testing your provider.</p>
+
+<h2><a name="leap-compile-providerjson"></a>leap compile provider.json</h2>
+
+<p>Compile provider.json bootstrap files for your provider.</p>
+
+<h2><a name="leap-compile-zone"></a>leap compile zone</h2>
+
+<p>Prints a DNS zone file for your provider.</p>
+
+<p>Default Command: all</p>
+
+<h1><a name="leap-db"></a>leap db</h1>
+
+<p>Database commands.</p>
+
+<h2><a name="leap-db-destroy-filter"></a>leap db destroy [FILTER]</h2>
+
+<p>Destroy one or more databases. If present, limit to FILTER nodes. For example <code>leap db destroy --db sessions,tokens testing</code>.</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--db DATABASES</code>
+Comma separated list of databases to destroy (no space). Use &ldquo;&ndash;db all&rdquo; to destroy all databases.
+Default Value: None</p></li>
+<li><p><code>--user USERS</code>
+Comma separated list of usernames. The storage databases for these user(s) will be destroyed.
+Default Value: None</p></li>
+</ul>
+
+
+<h1><a name="leap-deploy-filter"></a>leap deploy FILTER</h1>
+
+<p>Apply recipes to a node or set of nodes.</p>
+
+<p>The FILTER can be the name of a node, service, or tag.</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--ip IPADDRESS</code>
+Override the default SSH IP address.
+Default Value: None</p></li>
+<li><p><code>--port PORT</code>
+Override the default SSH port.
+Default Value: None</p></li>
+<li><p><code>--tags TAG[,TAG]</code>
+Specify tags to pass through to puppet (overriding the default).
+Default Value: None</p></li>
+<li><p><code>--dev</code>
+Development mode: don&rsquo;t run &lsquo;git submodule update&rsquo; before deploy.</p></li>
+<li><p><code>--downgrade</code>
+Allows deploy to run with an older platform version.</p></li>
+<li><p><code>--fast</code>
+Makes the deploy command faster by skipping some slow steps. A &ldquo;fast&rdquo; deploy can be used safely if you recently completed a normal deploy.</p></li>
+<li><p><code>--force</code>
+Deploy even if there is a lockfile.</p></li>
+<li><p><code>--sync</code>
+Sync files, but don&rsquo;t actually apply recipes.</p></li>
+</ul>
+
+
+<h1><a name="leap-env"></a>leap env</h1>
+
+<p>Manipulate and query environment information.</p>
+
+<p>The &lsquo;environment&rsquo; 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&rsquo;t apply if you move the directory)</p>
+
+<h2><a name="leap-env-ls-environment"></a>leap env ls [ENVIRONMENT]</h2>
+
+<p>List the available environments. The pinned environment, if any, will be marked with &lsquo;*&rsquo;. Will also set the pin if run with an environment argument.</p>
+
+<h2><a name="leap-env-pin-environment"></a>leap env pin ENVIRONMENT</h2>
+
+<p>Pin the environment to ENVIRONMENT. All subsequent commands will only apply to nodes in this environment.</p>
+
+<h2><a name="leap-env-unpin"></a>leap env unpin</h2>
+
+<p>Unpin the environment. All subsequent commands will apply to all nodes.</p>
+
+<p>Default Command: ls</p>
+
+<h1><a name="leap-facts"></a>leap facts</h1>
+
+<p>Gather information on nodes.</p>
+
+<h2><a name="leap-facts-update-filter"></a>leap facts update FILTER</h2>
+
+<p>Query servers to update facts.json.</p>
+
+<p>Queries every node included in FILTER and saves the important information to facts.json</p>
+
+<h1><a name="leap-help-command"></a>leap help command</h1>
+
+<p>Shows a list of commands or help for one command</p>
+
+<p>Gets help for the application or its commands. Can also list the commands in a way helpful to creating a bash-style completion function</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><code>-c</code>
+List commands one per line, to assist with shell completion</li>
+</ul>
+
+
+<h1><a name="leap-history-filter"></a>leap history FILTER</h1>
+
+<p>Display recent deployment history for a set of nodes.</p>
+
+<p>The FILTER can be the name of a node, service, or tag.</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--ip IPADDRESS</code>
+Override the default SSH IP address.
+Default Value: None</p></li>
+<li><p><code>--port PORT</code>
+Override the default SSH port.
+Default Value: None</p></li>
+<li><p><code>--last</code>
+Show last deploy only</p></li>
+</ul>
+
+
+<h1><a name="leap-info-filter"></a>leap info FILTER</h1>
+
+<p>Prints information regarding facts, history, and running processes for a node or nodes.</p>
+
+<p>The FILTER can be the name of a node, service, or tag.</p>
+
+<h1><a name="leap-inspect-file"></a>leap inspect FILE</h1>
+
+<p>Prints details about a file. Alternately, the argument FILE can be the name of a node, service or tag.</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><code>--base</code>
+Inspect the FILE from the provider_base (i.e. without local inheritance).</li>
+</ul>
+
+
+<h1><a name="leap-list-filter"></a>leap list [FILTER]</h1>
+
+<p>List nodes and their classifications</p>
+
+<p>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:</p>
+
+<p><code>leap list node1 node2</code> matches all nodes named &ldquo;node1&rdquo; OR &ldquo;node2&rdquo;</p>
+
+<p><code>leap list openvpn +local</code> matches all nodes with service &ldquo;openvpn&rdquo; AND tag &ldquo;local&rdquo;</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--print arg</code>
+What attributes to print (optional)
+Default Value: None</p></li>
+<li><p><code>--disabled</code>
+Include disabled nodes in the list.</p></li>
+</ul>
+
+
+<h1><a name="leap-local"></a>leap local</h1>
+
+<p>Manage local virtual machines.</p>
+
+<p>This command provides a convenient 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 &lsquo;test/Vagrantfile&rsquo;. If you want to run vagrant commands manually, cd to &lsquo;test&rsquo;.</p>
+
+<h2><a name="leap-local-ls-filter"></a>leap local ls [FILTER]</h2>
+
+<p>Print the status of local virtual machine(s)</p>
+
+<h2><a name="leap-local-reset-filter"></a>leap local reset [FILTER]</h2>
+
+<p>Resets virtual machine(s) to the last saved snapshot</p>
+
+<h2><a name="leap-local-rm-filter"></a>leap local rm [FILTER]</h2>
+
+<p>Destroys the virtual machine(s), reclaiming the disk space</p>
+
+<h2><a name="leap-local-save-filter"></a>leap local save [FILTER]</h2>
+
+<p>Saves the current state of the virtual machine as a new snapshot</p>
+
+<h2><a name="leap-local-start-filter"></a>leap local start [FILTER]</h2>
+
+<p>Starts up the virtual machine(s)</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><code>--basebox BASEBOX</code>
+The basebox to use. This value is passed to vagrant as the <code>config.vm.box</code> option. The value here should be the name of an installed box or a shorthand name of a box in HashiCorp&rsquo;s Atlas.
+Default Value: LEAP/jessie</li>
+</ul>
+
+
+<h2><a name="leap-local-stop-filter"></a>leap local stop [FILTER]</h2>
+
+<p>Shuts down the virtual machine(s)</p>
+
+<h1><a name="leap-mosh-name"></a>leap mosh NAME</h1>
+
+<p>Log in to the specified node with an interactive shell using mosh (requires node to have mosh.enabled set to true).</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--port SSH_PORT</code>
+Override default SSH port used when trying to connect to the server. Same as <code>--ssh "-p SSH_PORT"</code>.
+Default Value: None</p></li>
+<li><p><code>--ssh arg</code>
+Pass through raw options to ssh (e.g. <code>--ssh '-F ~/sshconfig'</code>).
+Default Value: None</p></li>
+</ul>
+
+
+<h1><a name="leap-new-directory"></a>leap new DIRECTORY</h1>
+
+<p>Creates a new provider instance in the specified directory, creating it if necessary.</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--contacts arg</code>
+Default email address contacts.
+Default Value: None</p></li>
+<li><p><code>--domain arg</code>
+The primary domain of the provider.
+Default Value: None</p></li>
+<li><p><code>--name arg</code>
+The name of the provider.
+Default Value: None</p></li>
+<li><p><code>--platform arg</code>
+File path of the leap_platform directory.
+Default Value: None</p></li>
+</ul>
+
+
+<h1><a name="leap-node"></a>leap node</h1>
+
+<p>Node management</p>
+
+<h2><a name="leap-node-add-name-seed"></a>leap node add NAME [SEED]</h2>
+
+<p>Create a new configuration file for a node named NAME.</p>
+
+<p>If specified, the optional argument SEED can be used to seed values in the node configuration file.</p>
+
+<p>The format is property_name:value.</p>
+
+<p>For example: <code>leap node add web1 ip_address:1.2.3.4 services:webapp</code>.</p>
+
+<p>To set nested properties, property name can contain &lsquo;.&rsquo;, like so: <code>leap node add web1 ssh.port:44</code></p>
+
+<p>Separate multiple values for a single property with a comma, like so: <code>leap node add mynode services:webapp,dns</code></p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--local</code>
+Make a local testing node (by assigning the next available local IP address). Local nodes are run as virtual machines on your computer.</p></li>
+<li><p><code>--vm</code>
+Make a remote virtual machine for this node. Requires a valid cloud.json configuration.</p></li>
+</ul>
+
+
+<h2><a name="leap-node-init-filter"></a>leap node init FILTER</h2>
+
+<p>Bootstraps a node or nodes, setting up SSH keys and installing prerequisite packages</p>
+
+<p>This command prepares a server to be used with the LEAP Platform by saving the server&rsquo;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.</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--ip IPADDRESS</code>
+Override the default SSH IP address.
+Default Value: None</p></li>
+<li><p><code>--port PORT</code>
+Override the default SSH port.
+This command prepares a server to be used with the LEAP Platform by saving the server&rsquo;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.
+Default Value: None</p></li>
+</ul>
+
+
+<h2><a name="leap-node-mv-old_name-new_name"></a>leap node mv OLD_NAME NEW_NAME</h2>
+
+<p>Renames a node file, and all its related files.</p>
+
+<h2><a name="leap-node-rm-name"></a>leap node rm NAME</h2>
+
+<p>Removes all the files related to the node named NAME.</p>
+
+<h1><a name="leap-open-name"></a>leap open NAME</h1>
+
+<p>Opens useful URLs in a web browser.</p>
+
+<p>NAME can be one or more of: monitor, web, docs, bug</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--env ENVIRONMENT</code>
+Which environment to use (optional).
+Default Value: None</p></li>
+<li><p><code>--[no-]ip</code>
+To get around HSTS or DNS, open the URL using the IP address instead of the domain (optional).</p></li>
+</ul>
+
+
+<h1><a name="leap-run-command-filter"></a>leap run COMMAND FILTER</h1>
+
+<p>Run a shell command remotely</p>
+
+<p>Runs the specified command COMMAND on each node in the FILTER set. For example, <code>leap run 'uname -a' webapp</code></p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--port SSH_PORT</code>
+Override default SSH port used when trying to connect to the server.
+Default Value: None</p></li>
+<li><p><code>--[no-]stream</code>
+If set, stream the output as it arrives. (default: &ndash;stream for a single node, &ndash;no-stream for multiple nodes)</p></li>
+</ul>
+
+
+<h1><a name="leap-scp-file1-file2"></a>leap scp FILE1 FILE2</h1>
+
+<p>Secure copy from FILE1 to FILE2. Files are specified as NODE_NAME:FILE_PATH. For local paths, omit &ldquo;NODE_NAME:&rdquo;.</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><code>-r</code>
+Copy recursively</li>
+</ul>
+
+
+<h1><a name="leap-ssh-name"></a>leap ssh NAME</h1>
+
+<p>Log in to the specified node with an interactive shell.</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--port SSH_PORT</code>
+Override default SSH port used when trying to connect to the server. Same as <code>--ssh "-p SSH_PORT"</code>.
+Default Value: None</p></li>
+<li><p><code>--ssh arg</code>
+Pass through raw options to ssh (e.g. <code>--ssh '-F ~/sshconfig'</code>).
+Default Value: None</p></li>
+</ul>
+
+
+<h1><a name="leap-test"></a>leap test</h1>
+
+<p>Run tests.</p>
+
+<h2><a name="leap-test-init"></a>leap test init</h2>
+
+<p>Creates files needed to run tests.</p>
+
+<h2><a name="leap-test-run-filter"></a>leap test run [FILTER]</h2>
+
+<p>Run the test suit on FILTER nodes.</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><code>--[no-]continue</code>
+Continue over errors and failures (default is &ndash;no-continue).</li>
+</ul>
+
+
+<p>Default Command: run</p>
+
+<h1><a name="leap-tunnel-local_portnameremote_port"></a>leap tunnel [LOCAL_PORT:]NAME:REMOTE_PORT</h1>
+
+<p>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: <code>leap tunnel couch1:5984</code>.</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--port SSH_PORT</code>
+Override default SSH port used when trying to connect to the server. Same as <code>--ssh "-p SSH_PORT"</code>.
+Default Value: None</p></li>
+<li><p><code>--ssh arg</code>
+Pass through raw options to ssh (e.g. &ndash;ssh &lsquo;-F ~/sshconfig&rsquo;).
+Default Value: None</p></li>
+</ul>
+
+
+<h1><a name="leap-user"></a>leap user</h1>
+
+<p>Manage trusted sysadmins</p>
+
+<p>Manage the trusted sysadmins that are configured in the &lsquo;users&rsquo; directory.</p>
+
+<h2><a name="leap-user-add-username"></a>leap user add USERNAME</h2>
+
+<p>Adds a new trusted sysadmin</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--pgp-pub-key arg</code>
+OpenPGP public key file for this new user
+Default Value: None</p></li>
+<li><p><code>--ssh-pub-key arg</code>
+SSH public key file for this new user
+Default Value: None</p></li>
+<li><p><code>--self</code>
+Add yourself as a trusted sysadmin by choosing among the public keys available for the current user.</p></li>
+</ul>
+
+
+<h2><a name="leap-user-ls"></a>leap user ls</h2>
+
+<p>Lists the configured sysadmins</p>
+
+<h2><a name="leap-user-rm-username"></a>leap user rm USERNAME</h2>
+
+<p>Removes a trusted sysadmin</p>
+
+<h1><a name="leap-vm"></a>leap vm</h1>
+
+<p>Manage remote virtual machines (VMs).</p>
+
+<p>This command provides a convenient way to manage virtual machines. FILTER may be a node filter or the ID of a virtual machine.</p>
+
+<p><strong>Options</strong></p>
+
+<ul>
+<li><p><code>--auth AUTH</code>
+Choose which authentication credentials to use from the file cloud.json. If omitted, will default to the node&rsquo;s <code>vm.auth</code> property, or the first credentials in cloud.json
+Default Value: None</p></li>
+<li><p><code>--[no-]mock</code>
+Run as simulation, without actually connecting to a cloud provider. If set, &ndash;auth is ignored.</p></li>
+<li><p><code>--[no-]wait</code>
+Wait for servers to start/stop before continuing.</p></li>
+</ul>
+
+
+<h2><a name="leap-vm-add-node_name-seed"></a>leap vm add NODE_NAME [SEED]</h2>
+
+<p>Allocates a new VM and/or associates it with node NAME.</p>
+
+<p>If node configuration file does not yet exist, it is created with the optional SEED values. You can run this command when the virtual machine already exists in order to update the node&rsquo;s <code>vm.id</code> property.</p>
+
+<h2><a name="leap-vm-bind-node_name-instance_id"></a>leap vm bind NODE_NAME INSTANCE_ID</h2>
+
+<p>Binds a running VM instance to a node configuration.</p>
+
+<p>Afterwards, the VM will be assigned a label matching the node name, and the node config will be updated with the instance ID.</p>
+
+<h2><a name="leap-vm-key-list"></a>leap vm key-list</h2>
+
+<p>Lists the registered SSH public keys for a particular VM provider.</p>
+
+<h2><a name="leap-vm-key-register"></a>leap vm key-register</h2>
+
+<p>Registers a SSH public key for use when creating new VMs.</p>
+
+<p>Note that only people who are creating new VM instances need to have their key registered.</p>
+
+<h2><a name="leap-vm-rm-filter"></a>leap vm rm [FILTER]</h2>
+
+<p>Destroys one or more VMs</p>
+
+<h2><a name="leap-vm-start-filter"></a>leap vm start [FILTER]</h2>
+
+<p>Starts one or more VMs</p>
+
+<h2><a name="leap-vm-status-filter"></a>leap vm status [FILTER]</h2>
+
+<p>Print the status of all VMs</p>
+
+<h2><a name="leap-vm-stop-filter"></a>leap vm stop [FILTER]</h2>
+
+<p>Shuts down one or more VMs</p>
+
+<p>This keeps the storage allocated. To save resources, run <code>leap vm rm</code> instead.</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/guide/config.html b/docs/en/guide/config.html
new file mode 100644
index 00000000..558f6940
--- /dev/null
+++ b/docs/en/guide/config.html
@@ -0,0 +1,584 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Configuration Files - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class=' level1'>
+<a class='' href='getting-started.html'>Getting Started</a>
+</li>
+<li class='active level1'>
+<a class='' href='config.html'>Configuration Files</a>
+</li>
+<li class=' level1'>
+<a class='' href='nodes.html'>Nodes</a>
+</li>
+<li class=' level1'>
+<a class='' href='keys-and-certificates.html'>Keys and Certificates</a>
+</li>
+<li class=' level1'>
+<a class='' href='domains.html'>Domains</a>
+</li>
+<li class=' level1'>
+<a class='' href='provider-configuration.html'>Provider Configuration</a>
+</li>
+<li class=' level1'>
+<a class='' href='environments.html'>Environments</a>
+</li>
+<li class=' level1'>
+<a class='' href='virtual-machines.html'>Virtual Machines</a>
+</li>
+<li class=' level1'>
+<a class='' href='miscellaneous.html'>Miscellaneous</a>
+</li>
+<li class=' level1'>
+<a class='' href='commands.html'>Command Line Reference</a>
+</li>
+<li class=' level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Configuration Files</h1>
+
+<div id='summary'>Understanding and editing the configuration files.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="config/index.html#files">Files</a>
+ </li>
+ <li>
+ <a href="config/index.html#leapfile">Leapfile</a>
+ </li>
+ <li>
+ <a href="config/index.html#json-format">JSON format</a>
+ </li>
+ <li>
+ <a href="config/index.html#node-inheritance">Node inheritance</a>
+ <ol>
+ <li>
+ <a href="config/index.html#inheritance-rules">Inheritance rules</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="config/index.html#common-configuration-options">Common configuration options</a>
+ </li>
+ <li>
+ <a href="config/index.html#macros">Macros</a>
+ </li>
+ <li>
+ <a href="config/index.html#hash-tables">Hash tables</a>
+ </li>
+</ol></div>
+
+<h2><a name="files"></a>Files</h2>
+
+<p>Here are a list of some of the common files that make up a provider. Except for <code>Leapfile</code> and <code>provider.json</code>, the files are optional. Unless otherwise specified, all file names are relative to the &lsquo;provider directory&rsquo; root (where the Leapfile is).</p>
+
+<table class="table table-striped">
+<tr>
+ <td><code>Leapfile</code></td>
+ <td>If present, this file tells <code>leap</code> that the directory is a provider directory. This file is usually empty, but can contain global options.</td>
+</tr>
+<tr>
+ <td><code>~/.leaprc</code></td>
+ <td>Evaluated the same as Leapfile, but not committed to source control.</td>
+</tr>
+<tr>
+ <td><code>provider.json</code></td>
+ <td>Global options related to this provider. See <a href="provider-configuration.html">Provider Configuration</a>.</td>
+</tr>
+<tr>
+ <td><code>provider.ENVIRONMENT.json</code></td>
+ <td>Global options for the provider that are applied to only a single environment.</td>
+</tr>
+<tr>
+ <td><code>nodes/NAME.json</code></td>
+ <td>The configuration file for node called NAME.</td>
+</tr>
+<tr>
+ <td><code>common.json</code></td>
+ <td>All nodes inherit from this file. In other words, any options that appear in <code>common.json</code> will be added as default values to each node configuration, value that can be locally overridden.</td>
+</tr>
+<tr>
+ <td><code>services/SERVICE.json</code></td>
+ <td>The properties in this configuration file are applied to any node that includes SERVICE in its <code>services</code> property.</td>
+</tr>
+<tr>
+ <td><code>services/SERVICE.ENVIRONMENT.json</code></td>
+ <td>The properties in this configuration file are applied to any node that includes SERVICE in its services and has environment equal to ENVIRONMENT.</td>
+</tr>
+<tr>
+ <td><code>tags/TAG.json</code></td>
+ <td>The properties in this configuration file are applied to any node that has includes TAG in its <code>tags</code> property.</td>
+</tr>
+<tr>
+ <td><code>tags/TAG.ENVIRONMENT.json</code></td>
+ <td>The properties in this configuration file are applied to any node that has includes TAG in its <code>tags</code> property and has <code>environment</code> property equal to ENVIRONMENT.</td>
+</tr>
+<tr>
+ <td><code>secrets.json </code></td>
+ <td>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 <code>leap compile</code> or <code>leap deploy</code>. 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.</td>
+</tr>
+<tr>
+ <td><code>facts.json</code></td>
+ <td>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 <code>leap facts update</code> to periodically regenerate the facts.json file.</td>
+</tr>
+<tr>
+ <td><code>files/*</code></td>
+ <td>Various static files used by the platform (e.g. keys, certificates, webapp customization, etc). In general, only generated files and files used to customize the provider (such as images) live in the <code>files</code> directory.</td>
+</tr>
+<tr>
+ <td><code>users/USER/</code></td>
+ <td>A directory that stores the public keys of the sysadmin with name USER. This person will have root access to all the servers.</td>
+</tr>
+</table>
+
+
+<h2><a name="leapfile"></a>Leapfile</h2>
+
+<p>A <code>Leapfile</code> defines options for the <code>leap</code> command and lives at the root of your provider directory. <code>Leapfile</code> 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:</p>
+
+<pre><code>@platform_directory_path = '../leap_platform'
+@log = '/var/log/leap.log'
+</code></pre>
+
+<p>Additionally, you can create a <code>~/.leaprc</code> file that is loaded after <code>Leapfile</code> and is evaluated the same way.</p>
+
+<p>Platform options:</p>
+
+<ul>
+<li><code>@platform_directory_path</code> (required). This must be set to the path where <code>leap_platform</code> lives. The path may be relative.</li>
+</ul>
+
+
+<p>Vagrant options:</p>
+
+<ul>
+<li><code>@vagrant_provider</code>. Changes the default vagrant provider (&ldquo;virtualbox&rdquo;). For example, <code>@vagrant_provider = "libvirt"</code>.</li>
+<li><code>@vagrant_network</code>. Allows you to override the default network used for local nodes. It should include a netmask like <code>@vagrant_network = '10.0.0.0/24'</code>.</li>
+<li><code>@custom_vagrant_vm_line</code>. Insert arbitrary text into the auto-generated Vagrantfile. For example, <code>@custom_vagrant_vm_line = "config.vm.boot_mode = :gui"</code>.</li>
+<li><code>@vagrant_basebox</code> allows specifying a different basebox as the default one. For example, <code>@vagrant_basebox = "LEAP/jessie"</code>.</li>
+</ul>
+
+
+<p>Logging options:</p>
+
+<ul>
+<li><code>@log</code>. If set, all command invocation and results are logged to the specified file. This is the same as the switch <code>--log FILE</code>, except that the command line switch will override the value in the Leapfile.</li>
+</ul>
+
+
+<h2><a name="json-format"></a>JSON format</h2>
+
+<p>All configuration files, other than <code>Leapfile</code>, are in the JSON format. For example:</p>
+
+<pre><code>{
+ "key1": "value1",
+ "key2": "value2"
+}
+</code></pre>
+
+<p>Keys should match <code>/[a-z0-9_]/</code> and must be in double quotes.</p>
+
+<p>Unlike traditional JSON, comments are allowed. If the first non-whitespace characters are <code>//</code> then the line is treated as a comment.</p>
+
+<pre><code>// this is a comment
+{
+ // this is a comment
+ "key": "value" // this is an error
+}
+</code></pre>
+
+<p>Options in the configuration files might be nested hashes, arrays, numbers, strings, or boolean. Numbers and boolean values should <strong>not</strong> be quoted. For example:</p>
+
+<pre><code>{
+ "openvpn": {
+ "ip_address": "1.1.1.1",
+ "protocols": ["tcp", "udp"],
+ "ports": [80, 53],
+ "options": {
+ "public_ip": false,
+ "adblock": true
+ }
+ }
+}
+</code></pre>
+
+<p>If the value string is prefixed with an &lsquo;=&rsquo; character, the result is evaluated as ruby. For example:</p>
+
+<pre><code>{
+ "domain": {
+ "public": "domain.org"
+ }
+ "api_domain": "= 'api.' + domain.public"
+}
+</code></pre>
+
+<p>In this case, the property &ldquo;api_domain&rdquo; will be set to &ldquo;api.domain.org&rdquo;. So long as you do not create unresolvable circular dependencies, you can reference other properties in evaluated ruby that are themselves evaluated ruby.</p>
+
+<p>See &ldquo;Macros&rdquo; below for information on the special macros available to the evaluated ruby.</p>
+
+<p>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 &ldquo;=>&rdquo; instead of &ldquo;=&rdquo;.</p>
+
+<h2><a name="node-inheritance"></a>Node inheritance</h2>
+
+<p>Every node inherits from common.json and also any of the services or tags attached to the node. Additionally, the <code>leap_platform</code> contains a directory <code>provider_base</code> that defines the default values for tags, services and common.json.</p>
+
+<p>Suppose you have a node configuration for <code>bitmask/nodes/willamette.json</code> like so:</p>
+
+<pre><code>{
+ "services": "webapp",
+ "tags": ["production", "northwest-us"],
+ "ip_address": "1.1.1.1"
+}
+</code></pre>
+
+<p>This node will have hostname &ldquo;willamette&rdquo; and it will inherit from the following files (in this order):</p>
+
+<ol>
+<li>common.json
+
+<ul>
+<li>load defaults: <code>provider_base/common.json</code></li>
+<li>load provider: <code>bitmask/common.json</code></li>
+</ul>
+</li>
+<li>service &ldquo;webapp&rdquo;
+
+<ul>
+<li>load defaults: <code>provider_base/services/webapp.json</code></li>
+<li>load provider: <code>bitmask/services/webapp.json</code></li>
+</ul>
+</li>
+<li>tag &ldquo;production&rdquo;
+
+<ul>
+<li>load defaults: <code>provider_base/tags/production.json</code></li>
+<li>load provider: <code>bitmask/tags/production.json</code></li>
+</ul>
+</li>
+<li>tag &ldquo;northwest-us&rdquo;
+
+<ul>
+<li>load: <code>bitmask/tags/northwest-us.json</code></li>
+</ul>
+</li>
+<li>finally, load node &ldquo;willamette&rdquo;
+
+<ul>
+<li>load: <code>bitmask/nodes/willamette.json</code></li>
+</ul>
+</li>
+</ol>
+
+
+<p>The <code>provider_base</code> directory is under the <code>leap_platform</code> specified in the file <code>Leapfile</code>.</p>
+
+<p>To see all the variables a node has inherited, you could run <code>leap inspect willamette</code>.</p>
+
+<h3><a name="inheritance-rules"></a>Inheritance rules</h3>
+
+<p>Suppose you have a node configuration <code>mynode.json</code>:</p>
+
+<pre><code>{
+ "tags": "production",
+ "simple_value": 100,
+ "replaced_array": ["dolphin", "kangaroo"],
+ "+add_array": ["red", "black"],
+ "-subtract_array": ["bitter"],
+ "converted_to_array": "not_array_element",
+ "!override": ["insist on this value"],
+ "hash": {
+ "key1": 1,
+ "key2": 2
+ }
+}
+</code></pre>
+
+<p>And a file <code>tags/production.json</code>:</p>
+
+<pre><code>{
+ "simple_value": 99999,
+ "replaced_array": ["zebra"],
+ "add_array": ["green],
+ "subtract_array": ["bitter", "sweet", "salty"],
+ "converted_to_array": ["array_element"],
+ "override": "this value will be overridden",
+ "hash": {
+ "key1": "one"
+ }
+}
+</code></pre>
+
+<p>In this scenario, <code>mynode.json</code> will inherit from <code>production.json</code>. The output of this inheritance will be:</p>
+
+<pre><code>{
+ "tags": "production",
+ "simple_value": 100,
+ "replaced_array": ["dolphin", "kangaroo"],
+ "add_array": ["red", "black", "green"],
+ "subtract_array": ["sweet", "salty"],
+ "converted_to_array": ["not_array_element", "array_element"],
+ "override": ["insist on this value"],
+ "hash": {
+ "key1": 1,
+ "key2": 2
+ }
+</code></pre>
+
+<p>The rules for inheritance (where &lsquo;old&rsquo; refers to the parent, and &lsquo;new&rsquo; refers to the child):</p>
+
+<ul>
+<li>Simple values (strings, numbers, boolean):
+
+<ul>
+<li>Replace the old value with the new value.</li>
+</ul>
+</li>
+<li>Array values:
+
+<ul>
+<li>Two arrays: replace the old array with the new array.</li>
+<li>One array and one simple value: add the simple value to the array.</li>
+<li>If property name is prefixed with &ldquo;+&rdquo;: merge the old and new arrays.</li>
+<li>If property name is prefixed with &ldquo;-&rdquo;: subtract new array from old array.</li>
+</ul>
+</li>
+<li>Hash values:
+
+<ul>
+<li>Hashes are always merged (the result includes the keys of both hashes). If there is a key in common, the new one overrides the old one.</li>
+</ul>
+</li>
+<li>Mismatch:
+
+<ul>
+<li>Although you can mix arrays and simple values, you cannot mix arrays with hashes or hashes with simple values. If you attempt to do so, it will fail to compile and give you an error message.</li>
+</ul>
+</li>
+<li>Override:
+
+<ul>
+<li>If property name is prefixed with &ldquo;!&rdquo;: then ensure that new value is always used, regardless of old value. In this case, the override takes precedence over type checking, so you will never get a type mismatch.</li>
+</ul>
+</li>
+</ul>
+
+
+<p>NOTE: special property name prefixes, like &ldquo;+&rdquo;, &ldquo;-&rdquo;, or &ldquo;!&rdquo;, are not included in the property name. These prefixes determine the merge strategy, but are stripped out when compiling the resulting JSON file.</p>
+
+<h2><a name="common-configuration-options"></a>Common configuration options</h2>
+
+<p>You can use the command <code>leap inspect</code> to see what options are available for a provider, node, service, or tag configuration. For example:</p>
+
+<ul>
+<li><code>leap inspect common</code> &ndash; show the options inherited by all nodes.</li>
+<li><code>leap inspect --base common</code> &ndash; show the common.json from <code>provider_base</code> without the local <code>common.json</code> inheritance applied.</li>
+<li><code>leap inspect webapp</code> &ndash; show all the options available for the service <code>webapp</code>.</li>
+</ul>
+
+
+<p>Here are some of the more important options you should be aware of:</p>
+
+<ul>
+<li><code>ip_address</code> &ndash; Required for all nodes, no default.</li>
+<li><code>ssh.port</code> &ndash; The SSH port you want the node&rsquo;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 <code>--port</code> to override the <code>ssh.port</code> configuration value.</li>
+<li><code>mosh.enabled</code> &ndash; If set to <code>true</code>, then mosh will be installed on the server. The default is <code>false</code>.</li>
+</ul>
+
+
+<h2><a name="macros"></a>Macros</h2>
+
+<p>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 <code>self</code>).</p>
+
+<p>The following methods are available to the evaluated ruby:</p>
+
+<p><code>variable.variable</code></p>
+
+<blockquote><p>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. <code>['domain']['public']</code> or <code>domain.public</code>). 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, <code>global.services['openvpn'].x509.dh</code>.</p></blockquote>
+
+<p><code>nodes</code></p>
+
+<blockquote><p>A hash of all nodes. This list can be filtered.</p></blockquote>
+
+<p><code>nodes_like_me</code></p>
+
+<blockquote><p>A hash of nodes that have the same deployment tags as the current node (e.g. &lsquo;production&rsquo; or &lsquo;local&rsquo;).</p></blockquote>
+
+<p><code>global.services</code></p>
+
+<blockquote><p>A hash of all services, e.g. <code>global.services['openvpn']</code> would return the &ldquo;openvpn&rdquo; service.</p></blockquote>
+
+<p><code>global.tags</code></p>
+
+<blockquote><p>A hash of all tags, e.g. <code>global.tags['production']</code> would return the &ldquo;production&rdquo; tag.</p></blockquote>
+
+<p> <code>global.provider</code></p>
+
+<blockquote><p>Can be used to access variables defined in <code>provider.json</code>, e.g. <code>global.provider.contacts.default</code>.</p></blockquote>
+
+<p><code>file(filename)</code></p>
+
+<blockquote><p>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 &ldquo;files&rdquo; directory in your provider instance. E.g, <code>file :ca_cert</code> or <code>files 'ca/ca.crt'</code>.</p></blockquote>
+
+<p><code>file_path(filename)</code></p>
+
+<blockquote><p> Ensures that the file will get rsynced to the node as an individual file. The value returned by <code>file_path</code> is the full path where this file will ultimately live when deploy to the node. e.g. <code>file_path :ca_cert</code> or <code>file_path 'branding/images/logo.png'</code>.</p></blockquote>
+
+<p><code>secret(:symbol)</code></p>
+
+<blockquote><p>Returns the value of a secret in secrets.json (or creates it if necessary). E.g. <code>secret :couch_admin_password</code></p></blockquote>
+
+<p><code>hosts_file</code></p>
+
+<blockquote><p>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.</p></blockquote>
+
+<p><code>known_hosts_file</code></p>
+
+<blockquote><p>Returns the lines needed in a SSH <code>known_hosts</code> file.</p></blockquote>
+
+<p><code>stunnel_client(node_list, port, options={})</code></p>
+
+<blockquote><p>Returns a stunnel configuration data structure for the client side. Argument <code>node_list</code> is an <code>ObjectList</code> of nodes running stunnel servers. Argument <code>port</code> is the real port of the ultimate service running on the servers that the client wants to connect to.</p></blockquote>
+
+<p><code>stunnel_server(port)</code></p>
+
+<blockquote><p>Generates a stunnel server entry. The <code>port</code> is the real port targeted service.</p></blockquote>
+
+<h2><a name="hash-tables"></a>Hash tables</h2>
+
+<p>The macros <code>nodes</code>, <code>nodes_like_me</code>, <code>global.services</code>, and <code>global.tags</code> all return a hash table of configuration objects (either nodes, services, or tags). There are several ways to filter and process these hash tables:</p>
+
+<p>Access an element by name:</p>
+
+<pre><code>nodes['vpn1'] # returns node named 'vpn1'
+global.services['openvpn'] # returns service named 'openvpn'
+</code></pre>
+
+<p>Create a new hash table by applying filters:</p>
+
+<pre><code>nodes[:public_dns =&gt; true] # all nodes where public_dns == true
+nodes[:services =&gt; 'openvpn', 'location.country_code' =&gt; 'US'] # openvpn service OR in the US.
+nodes[[:services, 'openvpn'], [:services, 'tor']] # services equal to openvpn OR tor
+nodes[:services =&gt; 'openvpn'][:tags =&gt; 'production'] # openvpn AND production
+nodes[:name =&gt; "!bob"] # all nodes that are NOT named "bob"
+</code></pre>
+
+<p>Create an array of values by selecting a single field:</p>
+
+<pre><code>nodes.field('location.name')
+==&gt; ['seattle', 'istanbul']
+</code></pre>
+
+<p>Create an array of hashes by selecting multiple fields:</p>
+
+<pre><code>nodes.fields('domain.full', 'ip_address')
+==&gt; [
+ {'domain_full' =&gt; 'red.bitmask.net', 'ip_address' =&gt; '1.1.1.1'},
+ {'domain_full' =&gt; 'blue.bitmask.net', 'ip_address' =&gt; '1.1.1.2'},
+]
+</code></pre>
+
+<p>Create a new hash table of hashes, with only certain fields:</p>
+
+<pre><code>nodes.pick_fields('domain.full', 'ip_address')
+==&gt; {
+ "red" =&gt; {'domain_full' =&gt; 'red.bitmask.net', 'ip_address' =&gt; '1.1.1.1'},
+ "blue =&gt; {'domain_full' =&gt; 'blue.bitmask.net', 'ip_address' =&gt; '1.1.1.2'},
+}
+</code></pre>
+
+<p>With <code>pick_fields</code>, if there is only one field, it will generate a simple hash table:</p>
+
+<pre><code>nodes.pick_fields('ip_address')
+==&gt; {
+ "red" =&gt; '1.1.1.1',
+ "blue =&gt; '1.1.1.2',
+}
+</code></pre>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/guide/config/index.html b/docs/en/guide/config/index.html
new file mode 100644
index 00000000..23e162d0
--- /dev/null
+++ b/docs/en/guide/config/index.html
@@ -0,0 +1,584 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Configuration Files - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../../index.html'>Home</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../../guide.html'>Guide</a>
+</li>
+<li class=' level1'>
+<a class='' href='../getting-started.html'>Getting Started</a>
+</li>
+<li class='active level1'>
+<a class='' href='../config.html'>Configuration Files</a>
+</li>
+<li class=' level1'>
+<a class='' href='../nodes.html'>Nodes</a>
+</li>
+<li class=' level1'>
+<a class='' href='../keys-and-certificates.html'>Keys and Certificates</a>
+</li>
+<li class=' level1'>
+<a class='' href='../domains.html'>Domains</a>
+</li>
+<li class=' level1'>
+<a class='' href='../provider-configuration.html'>Provider Configuration</a>
+</li>
+<li class=' level1'>
+<a class='' href='../environments.html'>Environments</a>
+</li>
+<li class=' level1'>
+<a class='' href='../virtual-machines.html'>Virtual Machines</a>
+</li>
+<li class=' level1'>
+<a class='' href='../miscellaneous.html'>Miscellaneous</a>
+</li>
+<li class=' level1'>
+<a class='' href='../commands.html'>Command Line Reference</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Configuration Files</h1>
+
+<div id='summary'>Understanding and editing the configuration files.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="index.html#files">Files</a>
+ </li>
+ <li>
+ <a href="index.html#leapfile">Leapfile</a>
+ </li>
+ <li>
+ <a href="index.html#json-format">JSON format</a>
+ </li>
+ <li>
+ <a href="index.html#node-inheritance">Node inheritance</a>
+ <ol>
+ <li>
+ <a href="index.html#inheritance-rules">Inheritance rules</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#common-configuration-options">Common configuration options</a>
+ </li>
+ <li>
+ <a href="index.html#macros">Macros</a>
+ </li>
+ <li>
+ <a href="index.html#hash-tables">Hash tables</a>
+ </li>
+</ol></div>
+
+<h2><a name="files"></a>Files</h2>
+
+<p>Here are a list of some of the common files that make up a provider. Except for <code>Leapfile</code> and <code>provider.json</code>, the files are optional. Unless otherwise specified, all file names are relative to the &lsquo;provider directory&rsquo; root (where the Leapfile is).</p>
+
+<table class="table table-striped">
+<tr>
+ <td><code>Leapfile</code></td>
+ <td>If present, this file tells <code>leap</code> that the directory is a provider directory. This file is usually empty, but can contain global options.</td>
+</tr>
+<tr>
+ <td><code>~/.leaprc</code></td>
+ <td>Evaluated the same as Leapfile, but not committed to source control.</td>
+</tr>
+<tr>
+ <td><code>provider.json</code></td>
+ <td>Global options related to this provider. See <a href="../provider-configuration.html">Provider Configuration</a>.</td>
+</tr>
+<tr>
+ <td><code>provider.ENVIRONMENT.json</code></td>
+ <td>Global options for the provider that are applied to only a single environment.</td>
+</tr>
+<tr>
+ <td><code>nodes/NAME.json</code></td>
+ <td>The configuration file for node called NAME.</td>
+</tr>
+<tr>
+ <td><code>common.json</code></td>
+ <td>All nodes inherit from this file. In other words, any options that appear in <code>common.json</code> will be added as default values to each node configuration, value that can be locally overridden.</td>
+</tr>
+<tr>
+ <td><code>services/SERVICE.json</code></td>
+ <td>The properties in this configuration file are applied to any node that includes SERVICE in its <code>services</code> property.</td>
+</tr>
+<tr>
+ <td><code>services/SERVICE.ENVIRONMENT.json</code></td>
+ <td>The properties in this configuration file are applied to any node that includes SERVICE in its services and has environment equal to ENVIRONMENT.</td>
+</tr>
+<tr>
+ <td><code>tags/TAG.json</code></td>
+ <td>The properties in this configuration file are applied to any node that has includes TAG in its <code>tags</code> property.</td>
+</tr>
+<tr>
+ <td><code>tags/TAG.ENVIRONMENT.json</code></td>
+ <td>The properties in this configuration file are applied to any node that has includes TAG in its <code>tags</code> property and has <code>environment</code> property equal to ENVIRONMENT.</td>
+</tr>
+<tr>
+ <td><code>secrets.json </code></td>
+ <td>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 <code>leap compile</code> or <code>leap deploy</code>. 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.</td>
+</tr>
+<tr>
+ <td><code>facts.json</code></td>
+ <td>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 <code>leap facts update</code> to periodically regenerate the facts.json file.</td>
+</tr>
+<tr>
+ <td><code>files/*</code></td>
+ <td>Various static files used by the platform (e.g. keys, certificates, webapp customization, etc). In general, only generated files and files used to customize the provider (such as images) live in the <code>files</code> directory.</td>
+</tr>
+<tr>
+ <td><code>users/USER/</code></td>
+ <td>A directory that stores the public keys of the sysadmin with name USER. This person will have root access to all the servers.</td>
+</tr>
+</table>
+
+
+<h2><a name="leapfile"></a>Leapfile</h2>
+
+<p>A <code>Leapfile</code> defines options for the <code>leap</code> command and lives at the root of your provider directory. <code>Leapfile</code> 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:</p>
+
+<pre><code>@platform_directory_path = '../leap_platform'
+@log = '/var/log/leap.log'
+</code></pre>
+
+<p>Additionally, you can create a <code>~/.leaprc</code> file that is loaded after <code>Leapfile</code> and is evaluated the same way.</p>
+
+<p>Platform options:</p>
+
+<ul>
+<li><code>@platform_directory_path</code> (required). This must be set to the path where <code>leap_platform</code> lives. The path may be relative.</li>
+</ul>
+
+
+<p>Vagrant options:</p>
+
+<ul>
+<li><code>@vagrant_provider</code>. Changes the default vagrant provider (&ldquo;virtualbox&rdquo;). For example, <code>@vagrant_provider = "libvirt"</code>.</li>
+<li><code>@vagrant_network</code>. Allows you to override the default network used for local nodes. It should include a netmask like <code>@vagrant_network = '10.0.0.0/24'</code>.</li>
+<li><code>@custom_vagrant_vm_line</code>. Insert arbitrary text into the auto-generated Vagrantfile. For example, <code>@custom_vagrant_vm_line = "config.vm.boot_mode = :gui"</code>.</li>
+<li><code>@vagrant_basebox</code> allows specifying a different basebox as the default one. For example, <code>@vagrant_basebox = "LEAP/jessie"</code>.</li>
+</ul>
+
+
+<p>Logging options:</p>
+
+<ul>
+<li><code>@log</code>. If set, all command invocation and results are logged to the specified file. This is the same as the switch <code>--log FILE</code>, except that the command line switch will override the value in the Leapfile.</li>
+</ul>
+
+
+<h2><a name="json-format"></a>JSON format</h2>
+
+<p>All configuration files, other than <code>Leapfile</code>, are in the JSON format. For example:</p>
+
+<pre><code>{
+ "key1": "value1",
+ "key2": "value2"
+}
+</code></pre>
+
+<p>Keys should match <code>/[a-z0-9_]/</code> and must be in double quotes.</p>
+
+<p>Unlike traditional JSON, comments are allowed. If the first non-whitespace characters are <code>//</code> then the line is treated as a comment.</p>
+
+<pre><code>// this is a comment
+{
+ // this is a comment
+ "key": "value" // this is an error
+}
+</code></pre>
+
+<p>Options in the configuration files might be nested hashes, arrays, numbers, strings, or boolean. Numbers and boolean values should <strong>not</strong> be quoted. For example:</p>
+
+<pre><code>{
+ "openvpn": {
+ "ip_address": "1.1.1.1",
+ "protocols": ["tcp", "udp"],
+ "ports": [80, 53],
+ "options": {
+ "public_ip": false,
+ "adblock": true
+ }
+ }
+}
+</code></pre>
+
+<p>If the value string is prefixed with an &lsquo;=&rsquo; character, the result is evaluated as ruby. For example:</p>
+
+<pre><code>{
+ "domain": {
+ "public": "domain.org"
+ }
+ "api_domain": "= 'api.' + domain.public"
+}
+</code></pre>
+
+<p>In this case, the property &ldquo;api_domain&rdquo; will be set to &ldquo;api.domain.org&rdquo;. So long as you do not create unresolvable circular dependencies, you can reference other properties in evaluated ruby that are themselves evaluated ruby.</p>
+
+<p>See &ldquo;Macros&rdquo; below for information on the special macros available to the evaluated ruby.</p>
+
+<p>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 &ldquo;=>&rdquo; instead of &ldquo;=&rdquo;.</p>
+
+<h2><a name="node-inheritance"></a>Node inheritance</h2>
+
+<p>Every node inherits from common.json and also any of the services or tags attached to the node. Additionally, the <code>leap_platform</code> contains a directory <code>provider_base</code> that defines the default values for tags, services and common.json.</p>
+
+<p>Suppose you have a node configuration for <code>bitmask/nodes/willamette.json</code> like so:</p>
+
+<pre><code>{
+ "services": "webapp",
+ "tags": ["production", "northwest-us"],
+ "ip_address": "1.1.1.1"
+}
+</code></pre>
+
+<p>This node will have hostname &ldquo;willamette&rdquo; and it will inherit from the following files (in this order):</p>
+
+<ol>
+<li>common.json
+
+<ul>
+<li>load defaults: <code>provider_base/common.json</code></li>
+<li>load provider: <code>bitmask/common.json</code></li>
+</ul>
+</li>
+<li>service &ldquo;webapp&rdquo;
+
+<ul>
+<li>load defaults: <code>provider_base/services/webapp.json</code></li>
+<li>load provider: <code>bitmask/services/webapp.json</code></li>
+</ul>
+</li>
+<li>tag &ldquo;production&rdquo;
+
+<ul>
+<li>load defaults: <code>provider_base/tags/production.json</code></li>
+<li>load provider: <code>bitmask/tags/production.json</code></li>
+</ul>
+</li>
+<li>tag &ldquo;northwest-us&rdquo;
+
+<ul>
+<li>load: <code>bitmask/tags/northwest-us.json</code></li>
+</ul>
+</li>
+<li>finally, load node &ldquo;willamette&rdquo;
+
+<ul>
+<li>load: <code>bitmask/nodes/willamette.json</code></li>
+</ul>
+</li>
+</ol>
+
+
+<p>The <code>provider_base</code> directory is under the <code>leap_platform</code> specified in the file <code>Leapfile</code>.</p>
+
+<p>To see all the variables a node has inherited, you could run <code>leap inspect willamette</code>.</p>
+
+<h3><a name="inheritance-rules"></a>Inheritance rules</h3>
+
+<p>Suppose you have a node configuration <code>mynode.json</code>:</p>
+
+<pre><code>{
+ "tags": "production",
+ "simple_value": 100,
+ "replaced_array": ["dolphin", "kangaroo"],
+ "+add_array": ["red", "black"],
+ "-subtract_array": ["bitter"],
+ "converted_to_array": "not_array_element",
+ "!override": ["insist on this value"],
+ "hash": {
+ "key1": 1,
+ "key2": 2
+ }
+}
+</code></pre>
+
+<p>And a file <code>tags/production.json</code>:</p>
+
+<pre><code>{
+ "simple_value": 99999,
+ "replaced_array": ["zebra"],
+ "add_array": ["green],
+ "subtract_array": ["bitter", "sweet", "salty"],
+ "converted_to_array": ["array_element"],
+ "override": "this value will be overridden",
+ "hash": {
+ "key1": "one"
+ }
+}
+</code></pre>
+
+<p>In this scenario, <code>mynode.json</code> will inherit from <code>production.json</code>. The output of this inheritance will be:</p>
+
+<pre><code>{
+ "tags": "production",
+ "simple_value": 100,
+ "replaced_array": ["dolphin", "kangaroo"],
+ "add_array": ["red", "black", "green"],
+ "subtract_array": ["sweet", "salty"],
+ "converted_to_array": ["not_array_element", "array_element"],
+ "override": ["insist on this value"],
+ "hash": {
+ "key1": 1,
+ "key2": 2
+ }
+</code></pre>
+
+<p>The rules for inheritance (where &lsquo;old&rsquo; refers to the parent, and &lsquo;new&rsquo; refers to the child):</p>
+
+<ul>
+<li>Simple values (strings, numbers, boolean):
+
+<ul>
+<li>Replace the old value with the new value.</li>
+</ul>
+</li>
+<li>Array values:
+
+<ul>
+<li>Two arrays: replace the old array with the new array.</li>
+<li>One array and one simple value: add the simple value to the array.</li>
+<li>If property name is prefixed with &ldquo;+&rdquo;: merge the old and new arrays.</li>
+<li>If property name is prefixed with &ldquo;-&rdquo;: subtract new array from old array.</li>
+</ul>
+</li>
+<li>Hash values:
+
+<ul>
+<li>Hashes are always merged (the result includes the keys of both hashes). If there is a key in common, the new one overrides the old one.</li>
+</ul>
+</li>
+<li>Mismatch:
+
+<ul>
+<li>Although you can mix arrays and simple values, you cannot mix arrays with hashes or hashes with simple values. If you attempt to do so, it will fail to compile and give you an error message.</li>
+</ul>
+</li>
+<li>Override:
+
+<ul>
+<li>If property name is prefixed with &ldquo;!&rdquo;: then ensure that new value is always used, regardless of old value. In this case, the override takes precedence over type checking, so you will never get a type mismatch.</li>
+</ul>
+</li>
+</ul>
+
+
+<p>NOTE: special property name prefixes, like &ldquo;+&rdquo;, &ldquo;-&rdquo;, or &ldquo;!&rdquo;, are not included in the property name. These prefixes determine the merge strategy, but are stripped out when compiling the resulting JSON file.</p>
+
+<h2><a name="common-configuration-options"></a>Common configuration options</h2>
+
+<p>You can use the command <code>leap inspect</code> to see what options are available for a provider, node, service, or tag configuration. For example:</p>
+
+<ul>
+<li><code>leap inspect common</code> &ndash; show the options inherited by all nodes.</li>
+<li><code>leap inspect --base common</code> &ndash; show the common.json from <code>provider_base</code> without the local <code>common.json</code> inheritance applied.</li>
+<li><code>leap inspect webapp</code> &ndash; show all the options available for the service <code>webapp</code>.</li>
+</ul>
+
+
+<p>Here are some of the more important options you should be aware of:</p>
+
+<ul>
+<li><code>ip_address</code> &ndash; Required for all nodes, no default.</li>
+<li><code>ssh.port</code> &ndash; The SSH port you want the node&rsquo;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 <code>--port</code> to override the <code>ssh.port</code> configuration value.</li>
+<li><code>mosh.enabled</code> &ndash; If set to <code>true</code>, then mosh will be installed on the server. The default is <code>false</code>.</li>
+</ul>
+
+
+<h2><a name="macros"></a>Macros</h2>
+
+<p>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 <code>self</code>).</p>
+
+<p>The following methods are available to the evaluated ruby:</p>
+
+<p><code>variable.variable</code></p>
+
+<blockquote><p>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. <code>['domain']['public']</code> or <code>domain.public</code>). 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, <code>global.services['openvpn'].x509.dh</code>.</p></blockquote>
+
+<p><code>nodes</code></p>
+
+<blockquote><p>A hash of all nodes. This list can be filtered.</p></blockquote>
+
+<p><code>nodes_like_me</code></p>
+
+<blockquote><p>A hash of nodes that have the same deployment tags as the current node (e.g. &lsquo;production&rsquo; or &lsquo;local&rsquo;).</p></blockquote>
+
+<p><code>global.services</code></p>
+
+<blockquote><p>A hash of all services, e.g. <code>global.services['openvpn']</code> would return the &ldquo;openvpn&rdquo; service.</p></blockquote>
+
+<p><code>global.tags</code></p>
+
+<blockquote><p>A hash of all tags, e.g. <code>global.tags['production']</code> would return the &ldquo;production&rdquo; tag.</p></blockquote>
+
+<p> <code>global.provider</code></p>
+
+<blockquote><p>Can be used to access variables defined in <code>provider.json</code>, e.g. <code>global.provider.contacts.default</code>.</p></blockquote>
+
+<p><code>file(filename)</code></p>
+
+<blockquote><p>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 &ldquo;files&rdquo; directory in your provider instance. E.g, <code>file :ca_cert</code> or <code>files 'ca/ca.crt'</code>.</p></blockquote>
+
+<p><code>file_path(filename)</code></p>
+
+<blockquote><p> Ensures that the file will get rsynced to the node as an individual file. The value returned by <code>file_path</code> is the full path where this file will ultimately live when deploy to the node. e.g. <code>file_path :ca_cert</code> or <code>file_path 'branding/images/logo.png'</code>.</p></blockquote>
+
+<p><code>secret(:symbol)</code></p>
+
+<blockquote><p>Returns the value of a secret in secrets.json (or creates it if necessary). E.g. <code>secret :couch_admin_password</code></p></blockquote>
+
+<p><code>hosts_file</code></p>
+
+<blockquote><p>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.</p></blockquote>
+
+<p><code>known_hosts_file</code></p>
+
+<blockquote><p>Returns the lines needed in a SSH <code>known_hosts</code> file.</p></blockquote>
+
+<p><code>stunnel_client(node_list, port, options={})</code></p>
+
+<blockquote><p>Returns a stunnel configuration data structure for the client side. Argument <code>node_list</code> is an <code>ObjectList</code> of nodes running stunnel servers. Argument <code>port</code> is the real port of the ultimate service running on the servers that the client wants to connect to.</p></blockquote>
+
+<p><code>stunnel_server(port)</code></p>
+
+<blockquote><p>Generates a stunnel server entry. The <code>port</code> is the real port targeted service.</p></blockquote>
+
+<h2><a name="hash-tables"></a>Hash tables</h2>
+
+<p>The macros <code>nodes</code>, <code>nodes_like_me</code>, <code>global.services</code>, and <code>global.tags</code> all return a hash table of configuration objects (either nodes, services, or tags). There are several ways to filter and process these hash tables:</p>
+
+<p>Access an element by name:</p>
+
+<pre><code>nodes['vpn1'] # returns node named 'vpn1'
+global.services['openvpn'] # returns service named 'openvpn'
+</code></pre>
+
+<p>Create a new hash table by applying filters:</p>
+
+<pre><code>nodes[:public_dns =&gt; true] # all nodes where public_dns == true
+nodes[:services =&gt; 'openvpn', 'location.country_code' =&gt; 'US'] # openvpn service OR in the US.
+nodes[[:services, 'openvpn'], [:services, 'tor']] # services equal to openvpn OR tor
+nodes[:services =&gt; 'openvpn'][:tags =&gt; 'production'] # openvpn AND production
+nodes[:name =&gt; "!bob"] # all nodes that are NOT named "bob"
+</code></pre>
+
+<p>Create an array of values by selecting a single field:</p>
+
+<pre><code>nodes.field('location.name')
+==&gt; ['seattle', 'istanbul']
+</code></pre>
+
+<p>Create an array of hashes by selecting multiple fields:</p>
+
+<pre><code>nodes.fields('domain.full', 'ip_address')
+==&gt; [
+ {'domain_full' =&gt; 'red.bitmask.net', 'ip_address' =&gt; '1.1.1.1'},
+ {'domain_full' =&gt; 'blue.bitmask.net', 'ip_address' =&gt; '1.1.1.2'},
+]
+</code></pre>
+
+<p>Create a new hash table of hashes, with only certain fields:</p>
+
+<pre><code>nodes.pick_fields('domain.full', 'ip_address')
+==&gt; {
+ "red" =&gt; {'domain_full' =&gt; 'red.bitmask.net', 'ip_address' =&gt; '1.1.1.1'},
+ "blue =&gt; {'domain_full' =&gt; 'blue.bitmask.net', 'ip_address' =&gt; '1.1.1.2'},
+}
+</code></pre>
+
+<p>With <code>pick_fields</code>, if there is only one field, it will generate a simple hash table:</p>
+
+<pre><code>nodes.pick_fields('ip_address')
+==&gt; {
+ "red" =&gt; '1.1.1.1',
+ "blue =&gt; '1.1.1.2',
+}
+</code></pre>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/guide/domains.html b/docs/en/guide/domains.html
new file mode 100644
index 00000000..eb3331ff
--- /dev/null
+++ b/docs/en/guide/domains.html
@@ -0,0 +1,298 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Domains - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class=' level1'>
+<a class='' href='getting-started.html'>Getting Started</a>
+</li>
+<li class=' level1'>
+<a class='' href='config.html'>Configuration Files</a>
+</li>
+<li class=' level1'>
+<a class='' href='nodes.html'>Nodes</a>
+</li>
+<li class=' level1'>
+<a class='' href='keys-and-certificates.html'>Keys and Certificates</a>
+</li>
+<li class='active level1'>
+<a class='' href='domains.html'>Domains</a>
+</li>
+<li class=' level1'>
+<a class='' href='provider-configuration.html'>Provider Configuration</a>
+</li>
+<li class=' level1'>
+<a class='' href='environments.html'>Environments</a>
+</li>
+<li class=' level1'>
+<a class='' href='virtual-machines.html'>Virtual Machines</a>
+</li>
+<li class=' level1'>
+<a class='' href='miscellaneous.html'>Miscellaneous</a>
+</li>
+<li class=' level1'>
+<a class='' href='commands.html'>Command Line Reference</a>
+</li>
+<li class=' level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Domains</h1>
+
+<div id='summary'>How to handle domain names and integrating LEAP with existing services.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="domains/index.html#overview">Overview</a>
+ <ol>
+ <li>
+ <a href="domains/index.html#definitions">Definitions</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="domains/index.html#generating-a-zone-file">Generating a zone file</a>
+ </li>
+ <li>
+ <a href="domains/index.html#a-single-domain">A single domain</a>
+ </li>
+ <li>
+ <a href="domains/index.html#a-separate-domain-for-the-webapp">A separate domain for the webapp</a>
+ <ol>
+ <li>
+ <a href="domains/index.html#step-1-configuring-webappdomain">Step 1. Configuring <code>webapp.domain</code></a>
+ </li>
+ <li>
+ <a href="domains/index.html#step-2-putting-the-compiled-providerjson-in-place">Step 2. Putting the compiled <code>provider.json</code> in place</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="domains/index.html#integrating-with-existing-email-system">Integrating with existing email system</a>
+ <ol>
+ <li>
+ <a href="domains/index.html#step-1-modify-leap-webapp">Step 1. Modify LEAP webapp</a>
+ </li>
+ <li>
+ <a href="domains/index.html#step-2-configure-mx-servers">Step 2. Configure MX servers</a>
+ </li>
+ </ol>
+ </li>
+</ol></div>
+
+<h2><a name="overview"></a>Overview</h2>
+
+<p>Deploying LEAP can start to get very tricky when you need to integrate LEAP services with an existing domain that you already use or which already has users. Most of this complexity is unavoidable, although there are a few things we plan to do in the future to make this a little less painful.</p>
+
+<p>Because integration with legacy systems is an advanced topic, we recommend that you begin with a new domain. Once everything works and you are comfortable with your LEAP-powered infrastructure, you can then contemplate integrating with your existing domain.</p>
+
+<h3><a name="definitions"></a>Definitions</h3>
+
+<p><strong>provider domain</strong></p>
+
+<p>This is the main domain used to identify the provider. The <strong>provider domain</strong> is what the user enters in the Bitmask client. e.g. <code>example.org</code>. The full host name of every node in your provider infrastructure will use the <strong>provider domain</strong> (e.g. <code>dbnode.example.org</code>).</p>
+
+<p>In order for the Bitmask client to get configured for use with a provider, it must be able to find the <code>provider.json</code> bootstrap file at the root of the <strong>provider domain</strong>. This is not needed if the Bitmask client is &ldquo;pre-seeded&rdquo; with the provider&rsquo;s information (these providers show up in a the initial list of available providers).</p>
+
+<p><strong>webapp domain</strong></p>
+
+<p>This is the domain that runs the leap_web application that allows users to register accounts, create help tickets, etc. e.g. <code>example.org</code> or <code>user.example.org</code>. The <strong>webapp domain</strong> defaults to the <strong>provider domain</strong> unless it is explicitly configured separately.</p>
+
+<p><strong>API domain</strong></p>
+
+<p>This is the domain that the provider API runs on. Typically, this is set automatically and you never need to configure it. The user should never be aware of this domain. e.g. <code>api.example.org</code>. The Bitmask client discovers this API domain by reading it from the <code>provider.json</code> file it grabs from the <strong>provider domain</strong>.</p>
+
+<p><strong>mail domain</strong></p>
+
+<p>This is the domain used for mail accounts, e.g. <code>username@example.org</code>. Currently, this is always the <strong>provider domain</strong>, but it may be independently configurable in the future.</p>
+
+<h2><a name="generating-a-zone-file"></a>Generating a zone file</h2>
+
+<p>Currently, the platform does not include a dedicated <code>dns</code> service type, so you need to have your own setup for DNS. You can generate the appropriate configuration options with this command:</p>
+
+<pre><code>leap compile zone
+</code></pre>
+
+<h2><a name="a-single-domain"></a>A single domain</h2>
+
+<p>The easy approach is to use a single domain for <strong>provider domain</strong>, <strong>webapp domain</strong>, and <strong>email domain</strong>. This will install the webapp on the <strong>provider domain</strong>, which means that this domain must be a new one that you are not currently using for anything.</p>
+
+<p>To configure a single domain, just set the domain in <code>provider.json</code>:</p>
+
+<pre><code>{
+ "domain": "example.org"
+}
+</code></pre>
+
+<p>If you have multiple environments, you can specify a different <strong>provider domain</strong> for each environment. For example:</p>
+
+<p><code>provider.staging.json</code></p>
+
+<pre><code>{
+ "domain": "staging.example.org"
+}
+</code></pre>
+
+<h2><a name="a-separate-domain-for-the-webapp"></a>A separate domain for the webapp</h2>
+
+<p>It is possible make the <strong>webapp domain</strong> different than the <strong>provider domain</strong>. This is needed if you already have a website running at your <strong>provider domain</strong>.</p>
+
+<p>In order to put webapp on a different domain, you must take two steps:</p>
+
+<ol>
+<li>You must configure <code>webapp.domain</code> for nodes with the <code>webapp</code> service.</li>
+<li>You must make the compiled <code>provider.json</code> available at the root of the <strong>provider domain</strong>.</li>
+</ol>
+
+
+<p>NOTE: This compiled provider.json is different than the provider.json that you edit and lives in the root of the provider directory.</p>
+
+<h3><a name="step-1-configuring-webappdomain"></a>Step 1. Configuring <code>webapp.domain</code></h3>
+
+<p>In <code>services/webapp.json</code>:</p>
+
+<pre><code>{
+ "webapp": {
+ "domain": "user.example.org"
+ }
+}
+</code></pre>
+
+<h3><a name="step-2-putting-the-compiled-providerjson-in-place"></a>Step 2. Putting the compiled <code>provider.json</code> in place</h3>
+
+<p>Generate the compiled <code>provider.json</code>:</p>
+
+<pre><code>leap compile provider.json
+= created files/web/bootstrap/
+= created files/web/bootstrap/README
+= created files/web/bootstrap/production/
+= created files/web/bootstrap/production/provider.json
+= created files/web/bootstrap/production/htaccess
+= created files/web/bootstrap/staging/
+= created files/web/bootstrap/staging/provider.json
+= created files/web/bootstrap/staging/htaccess
+</code></pre>
+
+<p>This command compiles a separate <code>provider.json</code> for each environment, or &ldquo;default&rdquo; if you don&rsquo;t have an environment. In the example above, there is an environment called &ldquo;production&rdquo; and one called &ldquo;staging&rdquo;, but your setup will probably differ.</p>
+
+<p>The resulting <code>provider.json</code> file must then be put at the root URL of your <strong>provider domain</strong> for the appropriate environment.</p>
+
+<p>There is one additional complication: currently, the Bitmask client tests for compatibility using some HTTP headers on the <code>/provider.json</code> response. This is will hopefully change in the future, but for now you need to ensure the right headers are set in the response. The included file <code>htaccess</code> has example directives for Apache, if that is what you use.</p>
+
+<p>This step can be skipped if you happen to use the <code>static</code> service to deploy an <code>amber</code> powered static website to <strong>provider domain</strong>. In this case, the correct <code>provider.json</code> will be automatically put into place.</p>
+
+<h2><a name="integrating-with-existing-email-system"></a>Integrating with existing email system</h2>
+
+<p>If your <strong>mail domain</strong> already has users from a legacy email system, then things get a bit complicated. In order to be able to support both LEAP-powered email and legacy email on the same domain, you need to follow these steps:</p>
+
+<ol>
+<li>Modify the LEAP webapp so that it does not create users with the same name as users in the legacy system.</li>
+<li>Configure your legacy MX servers to forward mail that they cannot handle to the LEAP MX servers, or vice versa.</li>
+</ol>
+
+
+<h3><a name="step-1-modify-leap-webapp"></a>Step 1. Modify LEAP webapp</h3>
+
+<p>In order to modify the webapp to respect the usernames already reserved by your legacy system, you need to modify the LEAP webapp code. The easiest way to do this is to create a custom gem that modifies the behavior of the webapp.</p>
+
+<p>For this example, we will call our custom gem <code>reserve_usernames</code>.</p>
+
+<p>This gem can live in one of two places:</p>
+
+<p>(1) You can fork the project leap_web and put the gem in <code>leap_web/vendor/gems/reserve_usernames</code>. Then, modify <code>Gemfile</code> and add the line <code>gem 'common_languages', :path =&gt; 'vendor/gems/reserve_usernames'</code></p>
+
+<p>(2) Alternately, you can put the gem in the local provider directory <code>files/webapp/gems/reserve_username</code>. This will get synced to the webapp servers when you deploy and put in <code>/srv/leap/webapp/config/customization</code> where it will get automatically loaded by the webapp.</p>
+
+<p>What should the gem <code>reserve_usernames</code> look like? There is an example available here: <a href="https://leap.se/git/reserved_usernames.git">https://leap.se/git/reserved_usernames.git</a></p>
+
+<p>This example gem uses ActiveResource to communicate with a remote REST API for creating and checking username reservations. This ensures that both the legacy system and the LEAP system use the same namespace. Alternately, you could write a gem that checks the legacy database directly.</p>
+
+<h3><a name="step-2-configure-mx-servers"></a>Step 2. Configure MX servers</h3>
+
+<p>To be written.</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/guide/domains/index.html b/docs/en/guide/domains/index.html
new file mode 100644
index 00000000..9ebf3b2c
--- /dev/null
+++ b/docs/en/guide/domains/index.html
@@ -0,0 +1,298 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Domains - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../../index.html'>Home</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../../guide.html'>Guide</a>
+</li>
+<li class=' level1'>
+<a class='' href='../getting-started.html'>Getting Started</a>
+</li>
+<li class=' level1'>
+<a class='' href='../config.html'>Configuration Files</a>
+</li>
+<li class=' level1'>
+<a class='' href='../nodes.html'>Nodes</a>
+</li>
+<li class=' level1'>
+<a class='' href='../keys-and-certificates.html'>Keys and Certificates</a>
+</li>
+<li class='active level1'>
+<a class='' href='../domains.html'>Domains</a>
+</li>
+<li class=' level1'>
+<a class='' href='../provider-configuration.html'>Provider Configuration</a>
+</li>
+<li class=' level1'>
+<a class='' href='../environments.html'>Environments</a>
+</li>
+<li class=' level1'>
+<a class='' href='../virtual-machines.html'>Virtual Machines</a>
+</li>
+<li class=' level1'>
+<a class='' href='../miscellaneous.html'>Miscellaneous</a>
+</li>
+<li class=' level1'>
+<a class='' href='../commands.html'>Command Line Reference</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Domains</h1>
+
+<div id='summary'>How to handle domain names and integrating LEAP with existing services.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="index.html#overview">Overview</a>
+ <ol>
+ <li>
+ <a href="index.html#definitions">Definitions</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#generating-a-zone-file">Generating a zone file</a>
+ </li>
+ <li>
+ <a href="index.html#a-single-domain">A single domain</a>
+ </li>
+ <li>
+ <a href="index.html#a-separate-domain-for-the-webapp">A separate domain for the webapp</a>
+ <ol>
+ <li>
+ <a href="index.html#step-1-configuring-webappdomain">Step 1. Configuring <code>webapp.domain</code></a>
+ </li>
+ <li>
+ <a href="index.html#step-2-putting-the-compiled-providerjson-in-place">Step 2. Putting the compiled <code>provider.json</code> in place</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#integrating-with-existing-email-system">Integrating with existing email system</a>
+ <ol>
+ <li>
+ <a href="index.html#step-1-modify-leap-webapp">Step 1. Modify LEAP webapp</a>
+ </li>
+ <li>
+ <a href="index.html#step-2-configure-mx-servers">Step 2. Configure MX servers</a>
+ </li>
+ </ol>
+ </li>
+</ol></div>
+
+<h2><a name="overview"></a>Overview</h2>
+
+<p>Deploying LEAP can start to get very tricky when you need to integrate LEAP services with an existing domain that you already use or which already has users. Most of this complexity is unavoidable, although there are a few things we plan to do in the future to make this a little less painful.</p>
+
+<p>Because integration with legacy systems is an advanced topic, we recommend that you begin with a new domain. Once everything works and you are comfortable with your LEAP-powered infrastructure, you can then contemplate integrating with your existing domain.</p>
+
+<h3><a name="definitions"></a>Definitions</h3>
+
+<p><strong>provider domain</strong></p>
+
+<p>This is the main domain used to identify the provider. The <strong>provider domain</strong> is what the user enters in the Bitmask client. e.g. <code>example.org</code>. The full host name of every node in your provider infrastructure will use the <strong>provider domain</strong> (e.g. <code>dbnode.example.org</code>).</p>
+
+<p>In order for the Bitmask client to get configured for use with a provider, it must be able to find the <code>provider.json</code> bootstrap file at the root of the <strong>provider domain</strong>. This is not needed if the Bitmask client is &ldquo;pre-seeded&rdquo; with the provider&rsquo;s information (these providers show up in a the initial list of available providers).</p>
+
+<p><strong>webapp domain</strong></p>
+
+<p>This is the domain that runs the leap_web application that allows users to register accounts, create help tickets, etc. e.g. <code>example.org</code> or <code>user.example.org</code>. The <strong>webapp domain</strong> defaults to the <strong>provider domain</strong> unless it is explicitly configured separately.</p>
+
+<p><strong>API domain</strong></p>
+
+<p>This is the domain that the provider API runs on. Typically, this is set automatically and you never need to configure it. The user should never be aware of this domain. e.g. <code>api.example.org</code>. The Bitmask client discovers this API domain by reading it from the <code>provider.json</code> file it grabs from the <strong>provider domain</strong>.</p>
+
+<p><strong>mail domain</strong></p>
+
+<p>This is the domain used for mail accounts, e.g. <code>username@example.org</code>. Currently, this is always the <strong>provider domain</strong>, but it may be independently configurable in the future.</p>
+
+<h2><a name="generating-a-zone-file"></a>Generating a zone file</h2>
+
+<p>Currently, the platform does not include a dedicated <code>dns</code> service type, so you need to have your own setup for DNS. You can generate the appropriate configuration options with this command:</p>
+
+<pre><code>leap compile zone
+</code></pre>
+
+<h2><a name="a-single-domain"></a>A single domain</h2>
+
+<p>The easy approach is to use a single domain for <strong>provider domain</strong>, <strong>webapp domain</strong>, and <strong>email domain</strong>. This will install the webapp on the <strong>provider domain</strong>, which means that this domain must be a new one that you are not currently using for anything.</p>
+
+<p>To configure a single domain, just set the domain in <code>provider.json</code>:</p>
+
+<pre><code>{
+ "domain": "example.org"
+}
+</code></pre>
+
+<p>If you have multiple environments, you can specify a different <strong>provider domain</strong> for each environment. For example:</p>
+
+<p><code>provider.staging.json</code></p>
+
+<pre><code>{
+ "domain": "staging.example.org"
+}
+</code></pre>
+
+<h2><a name="a-separate-domain-for-the-webapp"></a>A separate domain for the webapp</h2>
+
+<p>It is possible make the <strong>webapp domain</strong> different than the <strong>provider domain</strong>. This is needed if you already have a website running at your <strong>provider domain</strong>.</p>
+
+<p>In order to put webapp on a different domain, you must take two steps:</p>
+
+<ol>
+<li>You must configure <code>webapp.domain</code> for nodes with the <code>webapp</code> service.</li>
+<li>You must make the compiled <code>provider.json</code> available at the root of the <strong>provider domain</strong>.</li>
+</ol>
+
+
+<p>NOTE: This compiled provider.json is different than the provider.json that you edit and lives in the root of the provider directory.</p>
+
+<h3><a name="step-1-configuring-webappdomain"></a>Step 1. Configuring <code>webapp.domain</code></h3>
+
+<p>In <code>services/webapp.json</code>:</p>
+
+<pre><code>{
+ "webapp": {
+ "domain": "user.example.org"
+ }
+}
+</code></pre>
+
+<h3><a name="step-2-putting-the-compiled-providerjson-in-place"></a>Step 2. Putting the compiled <code>provider.json</code> in place</h3>
+
+<p>Generate the compiled <code>provider.json</code>:</p>
+
+<pre><code>leap compile provider.json
+= created files/web/bootstrap/
+= created files/web/bootstrap/README
+= created files/web/bootstrap/production/
+= created files/web/bootstrap/production/provider.json
+= created files/web/bootstrap/production/htaccess
+= created files/web/bootstrap/staging/
+= created files/web/bootstrap/staging/provider.json
+= created files/web/bootstrap/staging/htaccess
+</code></pre>
+
+<p>This command compiles a separate <code>provider.json</code> for each environment, or &ldquo;default&rdquo; if you don&rsquo;t have an environment. In the example above, there is an environment called &ldquo;production&rdquo; and one called &ldquo;staging&rdquo;, but your setup will probably differ.</p>
+
+<p>The resulting <code>provider.json</code> file must then be put at the root URL of your <strong>provider domain</strong> for the appropriate environment.</p>
+
+<p>There is one additional complication: currently, the Bitmask client tests for compatibility using some HTTP headers on the <code>/provider.json</code> response. This is will hopefully change in the future, but for now you need to ensure the right headers are set in the response. The included file <code>htaccess</code> has example directives for Apache, if that is what you use.</p>
+
+<p>This step can be skipped if you happen to use the <code>static</code> service to deploy an <code>amber</code> powered static website to <strong>provider domain</strong>. In this case, the correct <code>provider.json</code> will be automatically put into place.</p>
+
+<h2><a name="integrating-with-existing-email-system"></a>Integrating with existing email system</h2>
+
+<p>If your <strong>mail domain</strong> already has users from a legacy email system, then things get a bit complicated. In order to be able to support both LEAP-powered email and legacy email on the same domain, you need to follow these steps:</p>
+
+<ol>
+<li>Modify the LEAP webapp so that it does not create users with the same name as users in the legacy system.</li>
+<li>Configure your legacy MX servers to forward mail that they cannot handle to the LEAP MX servers, or vice versa.</li>
+</ol>
+
+
+<h3><a name="step-1-modify-leap-webapp"></a>Step 1. Modify LEAP webapp</h3>
+
+<p>In order to modify the webapp to respect the usernames already reserved by your legacy system, you need to modify the LEAP webapp code. The easiest way to do this is to create a custom gem that modifies the behavior of the webapp.</p>
+
+<p>For this example, we will call our custom gem <code>reserve_usernames</code>.</p>
+
+<p>This gem can live in one of two places:</p>
+
+<p>(1) You can fork the project leap_web and put the gem in <code>leap_web/vendor/gems/reserve_usernames</code>. Then, modify <code>Gemfile</code> and add the line <code>gem 'common_languages', :path =&gt; 'vendor/gems/reserve_usernames'</code></p>
+
+<p>(2) Alternately, you can put the gem in the local provider directory <code>files/webapp/gems/reserve_username</code>. This will get synced to the webapp servers when you deploy and put in <code>/srv/leap/webapp/config/customization</code> where it will get automatically loaded by the webapp.</p>
+
+<p>What should the gem <code>reserve_usernames</code> look like? There is an example available here: <a href="https://leap.se/git/reserved_usernames.git">https://leap.se/git/reserved_usernames.git</a></p>
+
+<p>This example gem uses ActiveResource to communicate with a remote REST API for creating and checking username reservations. This ensures that both the legacy system and the LEAP system use the same namespace. Alternately, you could write a gem that checks the legacy database directly.</p>
+
+<h3><a name="step-2-configure-mx-servers"></a>Step 2. Configure MX servers</h3>
+
+<p>To be written.</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/guide/environments.html b/docs/en/guide/environments.html
new file mode 100644
index 00000000..db82302d
--- /dev/null
+++ b/docs/en/guide/environments.html
@@ -0,0 +1,228 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Environments - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class=' level1'>
+<a class='' href='getting-started.html'>Getting Started</a>
+</li>
+<li class=' level1'>
+<a class='' href='config.html'>Configuration Files</a>
+</li>
+<li class=' level1'>
+<a class='' href='nodes.html'>Nodes</a>
+</li>
+<li class=' level1'>
+<a class='' href='keys-and-certificates.html'>Keys and Certificates</a>
+</li>
+<li class=' level1'>
+<a class='' href='domains.html'>Domains</a>
+</li>
+<li class=' level1'>
+<a class='' href='provider-configuration.html'>Provider Configuration</a>
+</li>
+<li class='active level1'>
+<a class='' href='environments.html'>Environments</a>
+</li>
+<li class=' level1'>
+<a class='' href='virtual-machines.html'>Virtual Machines</a>
+</li>
+<li class=' level1'>
+<a class='' href='miscellaneous.html'>Miscellaneous</a>
+</li>
+<li class=' level1'>
+<a class='' href='commands.html'>Command Line Reference</a>
+</li>
+<li class=' level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Working with environments</h1>
+
+<div id='summary'>How to partition the nodes into separate environments.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="environments/index.html#assign-an-environment">Assign an environment</a>
+ </li>
+ <li>
+ <a href="environments/index.html#environment-commands">Environment commands</a>
+ </li>
+ <li>
+ <a href="environments/index.html#environment-specific-json-files">Environment specific JSON files</a>
+ </li>
+ <li>
+ <a href="environments/index.html#bind-an-environment-to-a-platform-version">Bind an environment to a Platform version</a>
+ </li>
+</ol></div>
+
+<p>With environments, you can divide your nodes into different and entirely separate sets. For example, you might have sets of nodes for &lsquo;testing&rsquo;, &lsquo;staging&rsquo; and &lsquo;production&rsquo;.</p>
+
+<p>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.</p>
+
+<p>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).</p>
+
+<h2><a name="assign-an-environment"></a>Assign an environment</h2>
+
+<p>To assign an environment to a node, you just set the <code>environment</code> node property. This is typically done with tags, although it is not necessary. For example:</p>
+
+<p><code>tags/production.json</code></p>
+
+<pre><code>{
+ "environment": "production"
+}
+</code></pre>
+
+<p><code>nodes/mynode.json</code></p>
+
+<pre><code>{
+ "tags": ["production"]
+}
+</code></pre>
+
+<p>There are several built-in tags that will apply a value for the environment:</p>
+
+<ul>
+<li><code>production</code>: An environment for nodes that are in use by end users.</li>
+<li><code>development</code>: An environment to be used for nodes that are being used for experiments or staging.</li>
+<li><code>local</code>: This environment gets automatically applied to all nodes that run only on local VMs. Nodes with a <code>local</code> environment are treated special and excluded from certain calculations.</li>
+</ul>
+
+
+<p>You don&rsquo;t need to use these and you can add your own.</p>
+
+<h2><a name="environment-commands"></a>Environment commands</h2>
+
+<ul>
+<li><code>leap env</code> &ndash; List the available environments and disply which one is active.</li>
+<li><code>leap env pin ENV</code> &ndash; Pin the current environment to ENV.</li>
+<li><code>leap env unpin</code> &ndash; Remove the environment pin.</li>
+</ul>
+
+
+<p>The environment pin is only active for your local machine: it is not recorded in the provider directory and not shared with other users.</p>
+
+<h2><a name="environment-specific-json-files"></a>Environment specific JSON files</h2>
+
+<p>You can add JSON configuration files that are only applied when a specific environment is active. For example, if you create a file <code>provider.production.json</code>, these values will only get applied to the <code>provider.json</code> file for the <code>production</code> environment.</p>
+
+<p>This will also work for services and tags. For example:</p>
+
+<pre><code>provider.local.json
+services/webapp.development.json
+tags/seattle.production.json
+</code></pre>
+
+<p>In this example, <code>local</code>, <code>development</code>, and <code>production</code> are the names of environments.</p>
+
+<h2><a name="bind-an-environment-to-a-platform-version"></a>Bind an environment to a Platform version</h2>
+
+<p>If you want to ensure that a particular environment is bound to a particular version of the LEAP Platform, you can add a <code>platform</code> section to the <code>provider.ENV.json</code> file (where ENV is the name of the environment in question).</p>
+
+<p>The available options are <code>platform.version</code>, <code>platform.branch</code>, or <code>platform.commit</code>. For example:</p>
+
+<pre><code>{
+ "platform": {
+ "version": "1.6.1",
+ "branch": "develop",
+ "commit": "5df867fbd3a78ca4160eb54d708d55a7d047bdb2"
+ }
+}
+</code></pre>
+
+<p>You can use any combination of <code>version</code>, <code>branch</code>, and <code>commit</code> to specify the binding. The values for <code>branch</code> and <code>commit</code> only work if the <code>leap_platform</code> directory is a git repository.</p>
+
+<p>The value for <code>commit</code> is passed directly through to <code>git log</code> to query for a list of acceptable commits. See <a href="https://www.kernel.org/pub/software/scm/git/docs/gitrevisions.html#_specifying_ranges">man gitrevisions</a> to see how to specify ranges. For example:</p>
+
+<ul>
+<li><code>HEAD^..HEAD</code> - current commit must be head of the branch.</li>
+<li><code>3172444652af71bd771609d6b80258e70cc82ce9..HEAD</code> - current commit must be after 3172444652af71bd771609d6b80258e70cc82ce9.</li>
+<li><code>refs/tags/0.6.0rc1..refs/tags/0.6.0rc2</code> - current commit must be after tag 0.6.0rc1 and before or including tag 0.6.0rc2.</li>
+</ul>
+
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/guide/environments/index.html b/docs/en/guide/environments/index.html
new file mode 100644
index 00000000..faeb6c6c
--- /dev/null
+++ b/docs/en/guide/environments/index.html
@@ -0,0 +1,228 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Environments - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../../index.html'>Home</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../../guide.html'>Guide</a>
+</li>
+<li class=' level1'>
+<a class='' href='../getting-started.html'>Getting Started</a>
+</li>
+<li class=' level1'>
+<a class='' href='../config.html'>Configuration Files</a>
+</li>
+<li class=' level1'>
+<a class='' href='../nodes.html'>Nodes</a>
+</li>
+<li class=' level1'>
+<a class='' href='../keys-and-certificates.html'>Keys and Certificates</a>
+</li>
+<li class=' level1'>
+<a class='' href='../domains.html'>Domains</a>
+</li>
+<li class=' level1'>
+<a class='' href='../provider-configuration.html'>Provider Configuration</a>
+</li>
+<li class='active level1'>
+<a class='' href='../environments.html'>Environments</a>
+</li>
+<li class=' level1'>
+<a class='' href='../virtual-machines.html'>Virtual Machines</a>
+</li>
+<li class=' level1'>
+<a class='' href='../miscellaneous.html'>Miscellaneous</a>
+</li>
+<li class=' level1'>
+<a class='' href='../commands.html'>Command Line Reference</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Working with environments</h1>
+
+<div id='summary'>How to partition the nodes into separate environments.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="index.html#assign-an-environment">Assign an environment</a>
+ </li>
+ <li>
+ <a href="index.html#environment-commands">Environment commands</a>
+ </li>
+ <li>
+ <a href="index.html#environment-specific-json-files">Environment specific JSON files</a>
+ </li>
+ <li>
+ <a href="index.html#bind-an-environment-to-a-platform-version">Bind an environment to a Platform version</a>
+ </li>
+</ol></div>
+
+<p>With environments, you can divide your nodes into different and entirely separate sets. For example, you might have sets of nodes for &lsquo;testing&rsquo;, &lsquo;staging&rsquo; and &lsquo;production&rsquo;.</p>
+
+<p>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.</p>
+
+<p>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).</p>
+
+<h2><a name="assign-an-environment"></a>Assign an environment</h2>
+
+<p>To assign an environment to a node, you just set the <code>environment</code> node property. This is typically done with tags, although it is not necessary. For example:</p>
+
+<p><code>tags/production.json</code></p>
+
+<pre><code>{
+ "environment": "production"
+}
+</code></pre>
+
+<p><code>nodes/mynode.json</code></p>
+
+<pre><code>{
+ "tags": ["production"]
+}
+</code></pre>
+
+<p>There are several built-in tags that will apply a value for the environment:</p>
+
+<ul>
+<li><code>production</code>: An environment for nodes that are in use by end users.</li>
+<li><code>development</code>: An environment to be used for nodes that are being used for experiments or staging.</li>
+<li><code>local</code>: This environment gets automatically applied to all nodes that run only on local VMs. Nodes with a <code>local</code> environment are treated special and excluded from certain calculations.</li>
+</ul>
+
+
+<p>You don&rsquo;t need to use these and you can add your own.</p>
+
+<h2><a name="environment-commands"></a>Environment commands</h2>
+
+<ul>
+<li><code>leap env</code> &ndash; List the available environments and disply which one is active.</li>
+<li><code>leap env pin ENV</code> &ndash; Pin the current environment to ENV.</li>
+<li><code>leap env unpin</code> &ndash; Remove the environment pin.</li>
+</ul>
+
+
+<p>The environment pin is only active for your local machine: it is not recorded in the provider directory and not shared with other users.</p>
+
+<h2><a name="environment-specific-json-files"></a>Environment specific JSON files</h2>
+
+<p>You can add JSON configuration files that are only applied when a specific environment is active. For example, if you create a file <code>provider.production.json</code>, these values will only get applied to the <code>provider.json</code> file for the <code>production</code> environment.</p>
+
+<p>This will also work for services and tags. For example:</p>
+
+<pre><code>provider.local.json
+services/webapp.development.json
+tags/seattle.production.json
+</code></pre>
+
+<p>In this example, <code>local</code>, <code>development</code>, and <code>production</code> are the names of environments.</p>
+
+<h2><a name="bind-an-environment-to-a-platform-version"></a>Bind an environment to a Platform version</h2>
+
+<p>If you want to ensure that a particular environment is bound to a particular version of the LEAP Platform, you can add a <code>platform</code> section to the <code>provider.ENV.json</code> file (where ENV is the name of the environment in question).</p>
+
+<p>The available options are <code>platform.version</code>, <code>platform.branch</code>, or <code>platform.commit</code>. For example:</p>
+
+<pre><code>{
+ "platform": {
+ "version": "1.6.1",
+ "branch": "develop",
+ "commit": "5df867fbd3a78ca4160eb54d708d55a7d047bdb2"
+ }
+}
+</code></pre>
+
+<p>You can use any combination of <code>version</code>, <code>branch</code>, and <code>commit</code> to specify the binding. The values for <code>branch</code> and <code>commit</code> only work if the <code>leap_platform</code> directory is a git repository.</p>
+
+<p>The value for <code>commit</code> is passed directly through to <code>git log</code> to query for a list of acceptable commits. See <a href="https://www.kernel.org/pub/software/scm/git/docs/gitrevisions.html#_specifying_ranges">man gitrevisions</a> to see how to specify ranges. For example:</p>
+
+<ul>
+<li><code>HEAD^..HEAD</code> - current commit must be head of the branch.</li>
+<li><code>3172444652af71bd771609d6b80258e70cc82ce9..HEAD</code> - current commit must be after 3172444652af71bd771609d6b80258e70cc82ce9.</li>
+<li><code>refs/tags/0.6.0rc1..refs/tags/0.6.0rc2</code> - current commit must be after tag 0.6.0rc1 and before or including tag 0.6.0rc2.</li>
+</ul>
+
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/guide/getting-started.html b/docs/en/guide/getting-started.html
new file mode 100644
index 00000000..b1274e14
--- /dev/null
+++ b/docs/en/guide/getting-started.html
@@ -0,0 +1,317 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Getting Started - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class='active level1'>
+<a class='' href='getting-started.html'>Getting Started</a>
+</li>
+<li class=' level1'>
+<a class='' href='config.html'>Configuration Files</a>
+</li>
+<li class=' level1'>
+<a class='' href='nodes.html'>Nodes</a>
+</li>
+<li class=' level1'>
+<a class='' href='keys-and-certificates.html'>Keys and Certificates</a>
+</li>
+<li class=' level1'>
+<a class='' href='domains.html'>Domains</a>
+</li>
+<li class=' level1'>
+<a class='' href='provider-configuration.html'>Provider Configuration</a>
+</li>
+<li class=' level1'>
+<a class='' href='environments.html'>Environments</a>
+</li>
+<li class=' level1'>
+<a class='' href='virtual-machines.html'>Virtual Machines</a>
+</li>
+<li class=' level1'>
+<a class='' href='miscellaneous.html'>Miscellaneous</a>
+</li>
+<li class=' level1'>
+<a class='' href='commands.html'>Command Line Reference</a>
+</li>
+<li class=' level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Getting Started</h1>
+
+<div id='summary'>An overview of the LEAP Platform</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="getting-started/index.html#sensitive-files">Sensitive files</a>
+ </li>
+ <li>
+ <a href="getting-started/index.html#useful-commands">Useful commands</a>
+ </li>
+ <li>
+ <a href="getting-started/index.html#node-filters">Node filters</a>
+ </li>
+ <li>
+ <a href="getting-started/index.html#tracking-the-provider-directory-in-git">Tracking the provider directory in git</a>
+ </li>
+ <li>
+ <a href="getting-started/index.html#editing-json-configuration-files">Editing JSON configuration files</a>
+ </li>
+ <li>
+ <a href="getting-started/index.html#how-does-it-work-under-the-hood">How does it work under the hood?</a>
+ </li>
+</ol></div>
+
+<h2><a name="sensitive-files"></a>Sensitive files</h2>
+
+<p>Some files in your provider directory are very sensitive. Leaking these files will compromise your provider.</p>
+
+<p>Super sensitive and irreplaceable:</p>
+
+<ul>
+<li><code>files/ca/*.key</code> &ndash; the private keys for the client and server CAs.</li>
+<li><code>files/cert/*.key</code> &ndash; the private key(s) for the commercial certificate for your domain(s).</li>
+</ul>
+
+
+<p>Sensitive, but can be erased and regenerated automatically:</p>
+
+<ul>
+<li><code>secrets.json</code> &ndash; various random secrets, such as passwords for databases.</li>
+<li><code>files/nodes/*/*.key</code> &ndash; the private key for each node.</li>
+<li><code>hiera/*.yaml</code> &ndash; hiera file contains a copy of the private key of the node.</li>
+</ul>
+
+
+<p>Also, each sysadmin has one or more public ssh keys in <code>users/*/*_ssh.pub</code>. Typically, you will want to keep these public keys secure as well.</p>
+
+<p>See <a href="keys-and-certificates.html">Keys and Certificates</a> for more information.</p>
+
+<h2><a name="useful-commands"></a>Useful commands</h2>
+
+<p>Here are a few useful <code>leap</code> commands:</p>
+
+<ul>
+<li><code>leap help [COMMAND]</code> &ndash; get help on COMMAND.</li>
+<li><code>leap history [FILTER]</code> &ndash; show the recent deployment history for the selected nodes.</li>
+<li><code>leap ssh web1</code> &ndash; SSH into node web1 (requires <code>leap node init web1</code> first).</li>
+<li><code>leap list [FILTER]</code> &ndash; list the selected nodes.
+
+<ul>
+<li><code>leap list production</code> &ndash; list only those nodes with the tag &lsquo;production&rsquo;</li>
+<li><code>leap list --print ip_address</code> &ndash; list a particular attribute of all nodes.</li>
+</ul>
+</li>
+</ul>
+
+
+<p>See the full <a href="commands.html">Command Line Reference</a> for more information.</p>
+
+<h2><a name="node-filters"></a>Node filters</h2>
+
+<p>Many of the <code>leap</code> commands take a &ldquo;node filter&rdquo;. You can use a node filter to target a command at one or more nodes.</p>
+
+<p>A node filter consists of one or more keywords, with an optional &ldquo;+&rdquo; before each keyword.</p>
+
+<ul>
+<li>keywords can be a node name, a service type, or a tag.</li>
+<li>the &ldquo;+&rdquo; before the keyword constructs an AND condition</li>
+<li>otherwise, multiple keywords together construct an OR condition</li>
+</ul>
+
+
+<p>Examples:</p>
+
+<ul>
+<li><code>leap list openvpn</code> &ndash; list all nodes with service openvpn.</li>
+<li><code>leap list openvpn +production</code> &ndash; only nodes of service type openvpn AND tag production.</li>
+<li><code>leap deploy webapp openvpn</code> &ndash; deploy to all webapp OR openvpn nodes.</li>
+<li><code>leap node init ostrich</code> &ndash; just init the node named ostrich.</li>
+</ul>
+
+
+<p>See the full <a href="commands.html">Command Line Reference</a> for more information.</p>
+
+<h2><a name="tracking-the-provider-directory-in-git"></a>Tracking the provider directory in git</h2>
+
+<p>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.</p>
+
+<p>Note that your provider directory contains secrets, such as private key material and passwords. 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.</p>
+
+<p>If you have a post-commit hook that emails the changes to contributors, you may want to exclude diffs for files that might have sensitive secrets. For example, create a <code>.gitattributes</code> file with:</p>
+
+<pre><code># No diff, no email for key files
+*.key -diff
+*.pem -diff
+
+# Discard diff for secrets.json
+secrets.json -diff
+
+# No diff for hiera files, they contain passwords
+hiera/* -diff
+</code></pre>
+
+<h2><a name="editing-json-configuration-files"></a>Editing JSON configuration files</h2>
+
+<p>All the settings that compose your provider are stored in JSON files.</p>
+
+<p>At a minimum, you will need at least two configuration files:</p>
+
+<ul>
+<li><code>provider.json</code> &ndash; general settings for you provider.</li>
+<li><code>nodes/NAME.json</code> &ndash; configuration file for node called &ldquo;NAME&rdquo;.</li>
+</ul>
+
+
+<p>There are a few required properties in provider.json:</p>
+
+<pre><code>{
+ "domain": "example.org",
+ "name": "Example",
+ "contacts": {
+ "default": "email1@example.org"
+ }
+}
+</code></pre>
+
+<p>See <a href="provider-configuration.html">Provider Configuration</a> for more details.</p>
+
+<p>For node configuration files, there are two required properties:</p>
+
+<pre><code>{
+ "ip_address": "1.1.1.1",
+ "services": ["openvpn"]
+}
+</code></pre>
+
+<p>See <a href="../services.html">Services</a> for details on what servers are available, and see <a href="config.html">Configuration Files</a> details on how configuration files work.</p>
+
+<h2><a name="how-does-it-work-under-the-hood"></a>How does it work under the hood?</h2>
+
+<p>You don&rsquo;t need to know any of the details of what happens &ldquo;under the hood&rdquo; in order to use the LEAP platform. However, if you are curious as to what is going on, here is a quick primer.</p>
+
+<p>First, some background terminology:</p>
+
+<ul>
+<li><strong>puppet</strong>: Puppet is a system for automating deployment and management of servers (called nodes).</li>
+<li><strong>hiera files</strong>: In puppet, you can use something called a &lsquo;hiera file&rsquo; to seed a node with a few configuration values. In LEAP, we go all out and put <em>every</em> configuration value needed for a node in the hiera file, and automatically compile a custom hiera file for each node.</li>
+</ul>
+
+
+<p>When you run <code>leap deploy</code>, a bunch of things happen, in this order:</p>
+
+<ol>
+<li><strong>Compile hiera files</strong>: The hiera configuration file for each node is compiled in YAML format and saved in the directory <code>hiera</code>. The source material for this hiera file consists of all the JSON configuration files imported or inherited by the node&rsquo;s JSON config file.</li>
+<li><strong>Copy required files to node</strong>: 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&rsquo;s hiera file and other files needed by puppet to set up the node (keys, binary files, etc).</li>
+<li><strong>Puppet is run</strong>: Once the node is ready, leap connects to the node via ssh and runs <code>puppet apply</code>. Puppet is applied locally on the node, without a daemon or puppetmaster.</li>
+</ol>
+
+
+<p>You can run <code>leap -v2 deploy</code> to see exactly what commands are being executed.</p>
+
+<p>This mode of operation is fundamentally different from how puppet is normally used:</p>
+
+<ul>
+<li>There is no puppetmaster that all the servers take orders from, and there is no puppetd running in the background.</li>
+<li>Servers cannot dynamically query the puppetmaster for information about the other servers.</li>
+<li>There is a static representation for the state of every server that can be committed to git.</li>
+</ul>
+
+
+<p>There are advantages and disadvantages to the model that LEAP uses. We have found it very useful for our goal of having a common LEAP platform that many different providers can all use while still allowing providers to configure their unique infrastructure.</p>
+
+<p>We also find it very beneficial to be able to track the state of your infrastructure in git.</p>
+
+<p>Traditional system configuration automation systems, like <a href="https://puppetlabs.com/puppet/puppet-open-source/">Puppet</a> or <a href="http://www.opscode.com/chef/">Chef</a>, 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.</p>
+
+<p>Instead, the <code>leap</code> tool uses a masterless push method: The sysadmin runs <code>leap deploy</code> 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.</p>
+
+<p>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 <code>leap</code> command compiles static representation of all the information a particular server will need in order to apply the recipes. In compiling this static representation, <code>leap</code> can use arbitrary programming logic to query and manipulate information about other servers.</p>
+
+<p>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.</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/guide/getting-started/index.html b/docs/en/guide/getting-started/index.html
new file mode 100644
index 00000000..c9457ddd
--- /dev/null
+++ b/docs/en/guide/getting-started/index.html
@@ -0,0 +1,317 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Getting Started - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../../index.html'>Home</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../../guide.html'>Guide</a>
+</li>
+<li class='active level1'>
+<a class='' href='../getting-started.html'>Getting Started</a>
+</li>
+<li class=' level1'>
+<a class='' href='../config.html'>Configuration Files</a>
+</li>
+<li class=' level1'>
+<a class='' href='../nodes.html'>Nodes</a>
+</li>
+<li class=' level1'>
+<a class='' href='../keys-and-certificates.html'>Keys and Certificates</a>
+</li>
+<li class=' level1'>
+<a class='' href='../domains.html'>Domains</a>
+</li>
+<li class=' level1'>
+<a class='' href='../provider-configuration.html'>Provider Configuration</a>
+</li>
+<li class=' level1'>
+<a class='' href='../environments.html'>Environments</a>
+</li>
+<li class=' level1'>
+<a class='' href='../virtual-machines.html'>Virtual Machines</a>
+</li>
+<li class=' level1'>
+<a class='' href='../miscellaneous.html'>Miscellaneous</a>
+</li>
+<li class=' level1'>
+<a class='' href='../commands.html'>Command Line Reference</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Getting Started</h1>
+
+<div id='summary'>An overview of the LEAP Platform</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="index.html#sensitive-files">Sensitive files</a>
+ </li>
+ <li>
+ <a href="index.html#useful-commands">Useful commands</a>
+ </li>
+ <li>
+ <a href="index.html#node-filters">Node filters</a>
+ </li>
+ <li>
+ <a href="index.html#tracking-the-provider-directory-in-git">Tracking the provider directory in git</a>
+ </li>
+ <li>
+ <a href="index.html#editing-json-configuration-files">Editing JSON configuration files</a>
+ </li>
+ <li>
+ <a href="index.html#how-does-it-work-under-the-hood">How does it work under the hood?</a>
+ </li>
+</ol></div>
+
+<h2><a name="sensitive-files"></a>Sensitive files</h2>
+
+<p>Some files in your provider directory are very sensitive. Leaking these files will compromise your provider.</p>
+
+<p>Super sensitive and irreplaceable:</p>
+
+<ul>
+<li><code>files/ca/*.key</code> &ndash; the private keys for the client and server CAs.</li>
+<li><code>files/cert/*.key</code> &ndash; the private key(s) for the commercial certificate for your domain(s).</li>
+</ul>
+
+
+<p>Sensitive, but can be erased and regenerated automatically:</p>
+
+<ul>
+<li><code>secrets.json</code> &ndash; various random secrets, such as passwords for databases.</li>
+<li><code>files/nodes/*/*.key</code> &ndash; the private key for each node.</li>
+<li><code>hiera/*.yaml</code> &ndash; hiera file contains a copy of the private key of the node.</li>
+</ul>
+
+
+<p>Also, each sysadmin has one or more public ssh keys in <code>users/*/*_ssh.pub</code>. Typically, you will want to keep these public keys secure as well.</p>
+
+<p>See <a href="../keys-and-certificates.html">Keys and Certificates</a> for more information.</p>
+
+<h2><a name="useful-commands"></a>Useful commands</h2>
+
+<p>Here are a few useful <code>leap</code> commands:</p>
+
+<ul>
+<li><code>leap help [COMMAND]</code> &ndash; get help on COMMAND.</li>
+<li><code>leap history [FILTER]</code> &ndash; show the recent deployment history for the selected nodes.</li>
+<li><code>leap ssh web1</code> &ndash; SSH into node web1 (requires <code>leap node init web1</code> first).</li>
+<li><code>leap list [FILTER]</code> &ndash; list the selected nodes.
+
+<ul>
+<li><code>leap list production</code> &ndash; list only those nodes with the tag &lsquo;production&rsquo;</li>
+<li><code>leap list --print ip_address</code> &ndash; list a particular attribute of all nodes.</li>
+</ul>
+</li>
+</ul>
+
+
+<p>See the full <a href="../commands.html">Command Line Reference</a> for more information.</p>
+
+<h2><a name="node-filters"></a>Node filters</h2>
+
+<p>Many of the <code>leap</code> commands take a &ldquo;node filter&rdquo;. You can use a node filter to target a command at one or more nodes.</p>
+
+<p>A node filter consists of one or more keywords, with an optional &ldquo;+&rdquo; before each keyword.</p>
+
+<ul>
+<li>keywords can be a node name, a service type, or a tag.</li>
+<li>the &ldquo;+&rdquo; before the keyword constructs an AND condition</li>
+<li>otherwise, multiple keywords together construct an OR condition</li>
+</ul>
+
+
+<p>Examples:</p>
+
+<ul>
+<li><code>leap list openvpn</code> &ndash; list all nodes with service openvpn.</li>
+<li><code>leap list openvpn +production</code> &ndash; only nodes of service type openvpn AND tag production.</li>
+<li><code>leap deploy webapp openvpn</code> &ndash; deploy to all webapp OR openvpn nodes.</li>
+<li><code>leap node init ostrich</code> &ndash; just init the node named ostrich.</li>
+</ul>
+
+
+<p>See the full <a href="../commands.html">Command Line Reference</a> for more information.</p>
+
+<h2><a name="tracking-the-provider-directory-in-git"></a>Tracking the provider directory in git</h2>
+
+<p>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.</p>
+
+<p>Note that your provider directory contains secrets, such as private key material and passwords. 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.</p>
+
+<p>If you have a post-commit hook that emails the changes to contributors, you may want to exclude diffs for files that might have sensitive secrets. For example, create a <code>.gitattributes</code> file with:</p>
+
+<pre><code># No diff, no email for key files
+*.key -diff
+*.pem -diff
+
+# Discard diff for secrets.json
+secrets.json -diff
+
+# No diff for hiera files, they contain passwords
+hiera/* -diff
+</code></pre>
+
+<h2><a name="editing-json-configuration-files"></a>Editing JSON configuration files</h2>
+
+<p>All the settings that compose your provider are stored in JSON files.</p>
+
+<p>At a minimum, you will need at least two configuration files:</p>
+
+<ul>
+<li><code>provider.json</code> &ndash; general settings for you provider.</li>
+<li><code>nodes/NAME.json</code> &ndash; configuration file for node called &ldquo;NAME&rdquo;.</li>
+</ul>
+
+
+<p>There are a few required properties in provider.json:</p>
+
+<pre><code>{
+ "domain": "example.org",
+ "name": "Example",
+ "contacts": {
+ "default": "email1@example.org"
+ }
+}
+</code></pre>
+
+<p>See <a href="../provider-configuration.html">Provider Configuration</a> for more details.</p>
+
+<p>For node configuration files, there are two required properties:</p>
+
+<pre><code>{
+ "ip_address": "1.1.1.1",
+ "services": ["openvpn"]
+}
+</code></pre>
+
+<p>See <a href="../../services.html">Services</a> for details on what servers are available, and see <a href="../config.html">Configuration Files</a> details on how configuration files work.</p>
+
+<h2><a name="how-does-it-work-under-the-hood"></a>How does it work under the hood?</h2>
+
+<p>You don&rsquo;t need to know any of the details of what happens &ldquo;under the hood&rdquo; in order to use the LEAP platform. However, if you are curious as to what is going on, here is a quick primer.</p>
+
+<p>First, some background terminology:</p>
+
+<ul>
+<li><strong>puppet</strong>: Puppet is a system for automating deployment and management of servers (called nodes).</li>
+<li><strong>hiera files</strong>: In puppet, you can use something called a &lsquo;hiera file&rsquo; to seed a node with a few configuration values. In LEAP, we go all out and put <em>every</em> configuration value needed for a node in the hiera file, and automatically compile a custom hiera file for each node.</li>
+</ul>
+
+
+<p>When you run <code>leap deploy</code>, a bunch of things happen, in this order:</p>
+
+<ol>
+<li><strong>Compile hiera files</strong>: The hiera configuration file for each node is compiled in YAML format and saved in the directory <code>hiera</code>. The source material for this hiera file consists of all the JSON configuration files imported or inherited by the node&rsquo;s JSON config file.</li>
+<li><strong>Copy required files to node</strong>: 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&rsquo;s hiera file and other files needed by puppet to set up the node (keys, binary files, etc).</li>
+<li><strong>Puppet is run</strong>: Once the node is ready, leap connects to the node via ssh and runs <code>puppet apply</code>. Puppet is applied locally on the node, without a daemon or puppetmaster.</li>
+</ol>
+
+
+<p>You can run <code>leap -v2 deploy</code> to see exactly what commands are being executed.</p>
+
+<p>This mode of operation is fundamentally different from how puppet is normally used:</p>
+
+<ul>
+<li>There is no puppetmaster that all the servers take orders from, and there is no puppetd running in the background.</li>
+<li>Servers cannot dynamically query the puppetmaster for information about the other servers.</li>
+<li>There is a static representation for the state of every server that can be committed to git.</li>
+</ul>
+
+
+<p>There are advantages and disadvantages to the model that LEAP uses. We have found it very useful for our goal of having a common LEAP platform that many different providers can all use while still allowing providers to configure their unique infrastructure.</p>
+
+<p>We also find it very beneficial to be able to track the state of your infrastructure in git.</p>
+
+<p>Traditional system configuration automation systems, like <a href="https://puppetlabs.com/puppet/puppet-open-source/">Puppet</a> or <a href="http://www.opscode.com/chef/">Chef</a>, 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.</p>
+
+<p>Instead, the <code>leap</code> tool uses a masterless push method: The sysadmin runs <code>leap deploy</code> 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.</p>
+
+<p>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 <code>leap</code> command compiles static representation of all the information a particular server will need in order to apply the recipes. In compiling this static representation, <code>leap</code> can use arbitrary programming logic to query and manipulate information about other servers.</p>
+
+<p>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.</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/guide/keys-and-certificates.html b/docs/en/guide/keys-and-certificates.html
new file mode 100644
index 00000000..f5f83066
--- /dev/null
+++ b/docs/en/guide/keys-and-certificates.html
@@ -0,0 +1,451 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Keys and Certificates - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class=' level1'>
+<a class='' href='getting-started.html'>Getting Started</a>
+</li>
+<li class=' level1'>
+<a class='' href='config.html'>Configuration Files</a>
+</li>
+<li class=' level1'>
+<a class='' href='nodes.html'>Nodes</a>
+</li>
+<li class='active level1'>
+<a class='' href='keys-and-certificates.html'>Keys and Certificates</a>
+</li>
+<li class=' level1'>
+<a class='' href='domains.html'>Domains</a>
+</li>
+<li class=' level1'>
+<a class='' href='provider-configuration.html'>Provider Configuration</a>
+</li>
+<li class=' level1'>
+<a class='' href='environments.html'>Environments</a>
+</li>
+<li class=' level1'>
+<a class='' href='virtual-machines.html'>Virtual Machines</a>
+</li>
+<li class=' level1'>
+<a class='' href='miscellaneous.html'>Miscellaneous</a>
+</li>
+<li class=' level1'>
+<a class='' href='commands.html'>Command Line Reference</a>
+</li>
+<li class=' level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Keys and Certificates</h1>
+
+<div id='summary'>Working with SSH keys, secrets, and X.509 certificates.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="keys-and-certificates/index.html#working-with-ssh">Working with SSH</a>
+ <ol>
+ <li>
+ <a href="keys-and-certificates/index.html#ssh-related-files">SSH related files</a>
+ </li>
+ <li>
+ <a href="keys-and-certificates/index.html#ssh-and-local-nodes">SSH and local nodes</a>
+ </li>
+ <li>
+ <a href="keys-and-certificates/index.html#to-upgrade-a-ssh-host-key">To upgrade a SSH host key</a>
+ </li>
+ <li>
+ <a href="keys-and-certificates/index.html#when-ssh-host-key-changes">When SSH host key changes</a>
+ </li>
+ <li>
+ <a href="keys-and-certificates/index.html#changing-the-ssh-port">Changing the SSH port</a>
+ </li>
+ <li>
+ <a href="keys-and-certificates/index.html#sysadmins-with-multiple-ssh-keys">Sysadmins with multiple SSH keys</a>
+ </li>
+ <li>
+ <a href="keys-and-certificates/index.html#removing-sysadmin-access">Removing sysadmin access</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="keys-and-certificates/index.html#x509-certificates">X.509 Certificates</a>
+ <ol>
+ <li>
+ <a href="keys-and-certificates/index.html#configuration-options">Configuration options</a>
+ </li>
+ <li>
+ <a href="keys-and-certificates/index.html#certificate-authorities">Certificate Authorities</a>
+ </li>
+ <li>
+ <a href="keys-and-certificates/index.html#server-certificates">Server certificates</a>
+ </li>
+ <li>
+ <a href="keys-and-certificates/index.html#client-certificates">Client certificates</a>
+ </li>
+ <li>
+ <a href="keys-and-certificates/index.html#signed-certificates">Signed certificates</a>
+ </li>
+ <li>
+ <a href="keys-and-certificates/index.html#examine-certs">Examine Certs</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="keys-and-certificates/index.html#lets-encrypt">Let’s Encrypt</a>
+ <ol>
+ <li>
+ <a href="keys-and-certificates/index.html#creating-a-certificate">Creating a certificate</a>
+ </li>
+ <li>
+ <a href="keys-and-certificates/index.html#renewing-a-certificate">Renewing a certificate</a>
+ </li>
+ </ol>
+ </li>
+</ol></div>
+
+<h1><a name="working-with-ssh"></a>Working with SSH</h1>
+
+<p>Whenever the <code>leap</code> command needs 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 <code>leap</code> uses SSH.</p>
+
+<h2><a name="ssh-related-files"></a>SSH related files</h2>
+
+<p>Assuming your provider directory is called &lsquo;provider&rsquo;:</p>
+
+<ul>
+<li><code>provider/nodes/crow/crow_ssh.pub</code> &ndash; The public SSH host key for node &lsquo;crow&rsquo;.</li>
+<li><code>provider/users/alice/alice_ssh.pub</code> &ndash; The public SSH user key for user &lsquo;alice&rsquo;. Anyone with the private key that corresponds to this public key will have root access to all nodes.</li>
+<li><code>provider/files/ssh/known_hosts</code> &ndash; An autogenerated known_hosts, built from combining <code>provider/nodes/*/*_ssh.pub</code>. 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 <code>known_hosts</code> and then run <code>leap compile</code>.</li>
+<li><code>provider/files/ssh/authorized_keys</code> &ndash; An autogenerated list of all the user SSH keys with root access to the notes. It is created from <code>provider/users/*/*_ssh.pub</code>. 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 <code>authorized_keys</code> and then run <code>leap compile</code>.</li>
+</ul>
+
+
+<p>All of these files should be committed to source control.</p>
+
+<p>If you rename, remove, or add a node with <code>leap node [mv|add|rm]</code> the SSH key files and the <code>known_hosts</code> file will get properly updated.</p>
+
+<h2><a name="ssh-and-local-nodes"></a>SSH and local nodes</h2>
+
+<p>Local nodes are run as Vagrant virtual machines. The <code>leap</code> command handles SSH slightly differently for these nodes.</p>
+
+<p>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&rsquo;t be reached from the internet, this is not a problem.</p>
+
+<p>Specifically, for local nodes:</p>
+
+<ol>
+<li><code>known_hosts</code> is never updated with local node keys, since the SSH public key of a local node is different for each user.</li>
+<li><code>leap</code> entirely skips the checking of host keys when connecting with a local node.</li>
+<li><code>leap</code> 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.</li>
+</ol>
+
+
+<h2><a name="to-upgrade-a-ssh-host-key"></a>To upgrade a SSH host key</h2>
+
+<p>Most servers will have more than one SSH host key. Sometimes, the server will have a better SSH host key than the one you have on file. In order to upgrade to the better SSH host key, simply re-run the init command:</p>
+
+<pre><code>workstation$ leap node init NODE_NAME
+</code></pre>
+
+<p>This will prompt you if you want to upgrade the SSH host key, but only if <code>leap</code> thinks that an upgrade is advisable.</p>
+
+<h2><a name="when-ssh-host-key-changes"></a>When SSH host key changes</h2>
+
+<p>If the host key for a node has changed, you will get an error &ldquo;WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED&rdquo;.</p>
+
+<p>To fix this, you need to remove the file <code>files/nodes/stompy/stompy_ssh.pub</code> and run <code>leap node init stompy</code>, where the node&rsquo;s name is &lsquo;stompy&rsquo;. <strong>Only do this if you are ABSOLUTELY CERTAIN that the node&rsquo;s SSH host key has changed</strong>.</p>
+
+<h2><a name="changing-the-ssh-port"></a>Changing the SSH port</h2>
+
+<p>Suppose you have a node <code>blinky</code> that has SSH listening on port 22 and you want to make it port 2200.</p>
+
+<p>First, modify the configuration for <code>blinky</code> to specify the variable <code>ssh.port</code> as 2200. Usually, this is done in <code>common.json</code> or in a tag file.</p>
+
+<p>For example, you could put this in <code>tags/production.json</code>:</p>
+
+<pre><code>{
+ "ssh": {
+ "port": 2200
+ }
+}
+</code></pre>
+
+<p>Run <code>leap compile</code> and open <code>hiera/blinky.yaml</code> to confirm that <code>ssh.port</code> is set to 2200. The port number must be specified as a number, not a string (no quotes).</p>
+
+<p>Then, you need to deploy this change so that SSH will bind to 2200. You cannot simply run <code>leap deploy blinky</code> because this command will default to using the variable <code>ssh.port</code> which is now <code>2200</code> but SSH on the node is still bound to 22.</p>
+
+<p>So, you manually override the port in the deploy command, using the old port:</p>
+
+<pre><code>leap deploy --port 22 blinky
+</code></pre>
+
+<p>Afterwards, SSH on <code>blinky</code> should be listening on port 2200 and you can just run <code>leap deploy blinky</code> from then on.</p>
+
+<h2><a name="sysadmins-with-multiple-ssh-keys"></a>Sysadmins with multiple SSH keys</h2>
+
+<p>The command <code>leap add-user --self</code> allows only one SSH key. If you want to specify more than one key for a user, you can do it manually:</p>
+
+<pre><code>users/userx/userx_ssh.pub
+users/userx/otherkey_ssh.pub
+</code></pre>
+
+<p>All keys matching &lsquo;userx/*_ssh.pub&rsquo; will be usable.</p>
+
+<h2><a name="removing-sysadmin-access"></a>Removing sysadmin access</h2>
+
+<p>Suppose you want to remove <code>userx</code> from having any further SSH access to the servers. Do this:</p>
+
+<pre><code>rm -r users/userx
+leap deploy
+</code></pre>
+
+<h1><a name="x509-certificates"></a>X.509 Certificates</h1>
+
+<h2><a name="configuration-options"></a>Configuration options</h2>
+
+<p>The <code>ca</code> option in provider.json provides settings used when generating CAs and certificates. The defaults are as follows:</p>
+
+<pre><code>{
+ "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"
+ }
+ }
+}
+</code></pre>
+
+<p>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 <code>leap inspect provider.json</code>.</p>
+
+<p>NOTE: A certificate <code>bit_size</code> greater than 2048 will probably not be recognized by most commercial CAs.</p>
+
+<h2><a name="certificate-authorities"></a>Certificate Authorities</h2>
+
+<p>There are three x.509 certificate authorities (CA) associated with your provider:</p>
+
+<ol>
+<li><strong>Commercial CA:</strong> 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 <code>files/cert/commercial_ca.crt</code>.</li>
+<li><strong>Server CA:</strong> This is a self-signed CA responsible for signing all the <strong>server</strong> certificates. The private key lives at <code>files/ca/ca.key</code> and the public cert lives at <code>files/ca/ca.crt</code>. The key is very sensitive information and must be kept private. The public cert is distributed publicly.</li>
+<li><strong>Client CA:</strong> This is a self-signed CA responsible for signing all the <strong>client</strong> certificates. The private key lives at <code>files/ca/client_ca.key</code> and the public cert lives at <code>files/ca/client_ca.crt</code>. 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.</li>
+</ol>
+
+
+<p>To generate both the Server CA and the Client CA, run the command:</p>
+
+<pre><code>leap cert ca
+</code></pre>
+
+<h2><a name="server-certificates"></a>Server certificates</h2>
+
+<p>Most every server in your service provider will have a x.509 certificate, generated by the <code>leap</code> command using the Server CA. Whenever you modify any settings of a node that might affect it&rsquo;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:</p>
+
+<pre><code>leap cert update
+</code></pre>
+
+<p>Run <code>leap help cert update</code> for notes on usage options.</p>
+
+<p>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.</p>
+
+<h2><a name="client-certificates"></a>Client certificates</h2>
+
+<p>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.</p>
+
+<p>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.</p>
+
+<p>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 <code>provider.service.bandwidth_limit</code> (in Bytes per second). An unlimited cert is given to the user if they authenticate and the user&rsquo;s service level matches one configured in <code>provider.service.levels</code> without bandwidth limits. Otherwise, the user is given a limited client cert.</p>
+
+<h2><a name="signed-certificates"></a>Signed certificates</h2>
+
+<p>We strongly recommend that the primary domain for your provider has a certificate signed by a &ldquo;trusted CA&rdquo; (e.g. A Certificate Authority that is trusted by the web browsers and in the Debian <code>ca-certificates</code> package). This provides several benefits:</p>
+
+<ol>
+<li>When users visit your website, they don&rsquo;t get a scary notice that something is wrong.</li>
+<li>When a user runs the LEAP client, selecting your service provider will not cause a warning message.</li>
+<li>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.</li>
+</ol>
+
+
+<p>The LEAP platform is designed so that it assumes you are using a certificate signed by a &ldquo;trusted CA&rdquo; for the primary domain of your provider, but all other servers are assumed to use certs signed by the Server CA you create.</p>
+
+<p>To generate a CSR, run:</p>
+
+<pre><code>leap cert csr [DOMAIN]
+</code></pre>
+
+<p>This command will generate the CSR and private key matching <code>provider.domain</code> or use DOMAIN. It also generates a server certificate signed with the Server CA. You should delete this certificate and replace it with a real one you get back from a &ldquo;trusted CA&rdquo;.</p>
+
+<p>The related commercial cert files are:</p>
+
+<pre><code>files/
+ cert/
+ domain.org.crt # Server certificate for domain.org, obtained from
+ # the trusted CA (this file is initially signed with
+ # the Server CA, but you should replace it).
+ domain.org.csr # Certificate signing request (PEM format)
+ domain.org.key # Private key for you certificate (PEM format)
+ commercial_ca.crt # DEPRECATED: The certificate chain obtained from
+ # the trusted CA (PEM format)
+</code></pre>
+
+<p>The private key file is extremely sensitive and care should be taken with its provenance.</p>
+
+<p>A few notes on the certificate chain:</p>
+
+<ul>
+<li>A certificate is basically just a key signed by another key. In x.509, the signing key might be signed by yet another key, and so on, all the way to a &lsquo;root&rsquo; key. It is the root key that a browser trusts or is in the Debian <code>ca-certificates</code> package. The chain is the set of all the keys from the root to the end certificate.</li>
+<li>For TLS, both the server and the client need the full chain from the certificate to the CA&rsquo;s root.</li>
+<li>The full chain should be appended in the file <code>domain.org.crt</code> after the server certificate. The chain can also live in <code>commercial_ca.crt</code>, but this is deprecated.</li>
+</ul>
+
+
+<p>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:</p>
+
+<pre><code> "ca": {
+ "server_certificates": {
+ "country": "US",
+ "state": "Washington",
+ "locality": "Seattle"
+ }
+ }
+</code></pre>
+
+<p>If they are not present, the CSR will be created without them.</p>
+
+<h2><a name="examine-certs"></a>Examine Certs</h2>
+
+<p>To see details about the keys and certs you can use <code>leap inspect</code> like so:</p>
+
+<pre><code>$ leap inspect files/ca/ca.crt
+</code></pre>
+
+<h1><a name="lets-encrypt"></a>Let’s Encrypt</h1>
+
+<p>Let&rsquo;s Encrypt is a free &ldquo;trusted CA&rdquo;. You can obtain signed certificates from Let&rsquo;s Encrypt very easily using the LEAP command line, so long as you have first set up DNS correctly.</p>
+
+<h2><a name="creating-a-certificate"></a>Creating a certificate</h2>
+
+<p>For example:</p>
+
+<pre><code>workstation$ leap cert register
+workstation$ leap cert csr demo.bitmask.net
+workstation$ leap cert renew demo.bitmask.net
+workstation$ leap deploy
+</code></pre>
+
+<p>Some notes:</p>
+
+<ol>
+<li>You only need to run <code>leap cert register</code> once. Registering will save the Let&rsquo;s Encrypt account key to <code>files/ca/lets-encrypt-account.key</code>. If you delete this file, just run <code>leap cert register</code> again.</li>
+<li>Let&rsquo;s Encrypt support requires that you have already platform 0.9 or later.</li>
+<li>This requires that the DNS records are correct for the domain.</li>
+</ol>
+
+
+<h2><a name="renewing-a-certificate"></a>Renewing a certificate</h2>
+
+<p>Let&rsquo;s Encrypt validations are short lived. You will need to renew the certificate at least once every three months. There is no harm in doing it more regularly, however. You can renew your cert every day if you wanted.</p>
+
+<pre><code>workstation$ leap cert renew demo.bitmask.net
+workstation$ leap deploy
+</code></pre>
+
+<p>There is no need to create a new CSR: renewing will reuse the old private key and the old CSR. It is especially important to not create a new CSR if you have advertised public key pins using HPKP.</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/guide/keys-and-certificates/index.html b/docs/en/guide/keys-and-certificates/index.html
new file mode 100644
index 00000000..016a03a7
--- /dev/null
+++ b/docs/en/guide/keys-and-certificates/index.html
@@ -0,0 +1,451 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Keys and Certificates - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../../index.html'>Home</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../../guide.html'>Guide</a>
+</li>
+<li class=' level1'>
+<a class='' href='../getting-started.html'>Getting Started</a>
+</li>
+<li class=' level1'>
+<a class='' href='../config.html'>Configuration Files</a>
+</li>
+<li class=' level1'>
+<a class='' href='../nodes.html'>Nodes</a>
+</li>
+<li class='active level1'>
+<a class='' href='../keys-and-certificates.html'>Keys and Certificates</a>
+</li>
+<li class=' level1'>
+<a class='' href='../domains.html'>Domains</a>
+</li>
+<li class=' level1'>
+<a class='' href='../provider-configuration.html'>Provider Configuration</a>
+</li>
+<li class=' level1'>
+<a class='' href='../environments.html'>Environments</a>
+</li>
+<li class=' level1'>
+<a class='' href='../virtual-machines.html'>Virtual Machines</a>
+</li>
+<li class=' level1'>
+<a class='' href='../miscellaneous.html'>Miscellaneous</a>
+</li>
+<li class=' level1'>
+<a class='' href='../commands.html'>Command Line Reference</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Keys and Certificates</h1>
+
+<div id='summary'>Working with SSH keys, secrets, and X.509 certificates.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="index.html#working-with-ssh">Working with SSH</a>
+ <ol>
+ <li>
+ <a href="index.html#ssh-related-files">SSH related files</a>
+ </li>
+ <li>
+ <a href="index.html#ssh-and-local-nodes">SSH and local nodes</a>
+ </li>
+ <li>
+ <a href="index.html#to-upgrade-a-ssh-host-key">To upgrade a SSH host key</a>
+ </li>
+ <li>
+ <a href="index.html#when-ssh-host-key-changes">When SSH host key changes</a>
+ </li>
+ <li>
+ <a href="index.html#changing-the-ssh-port">Changing the SSH port</a>
+ </li>
+ <li>
+ <a href="index.html#sysadmins-with-multiple-ssh-keys">Sysadmins with multiple SSH keys</a>
+ </li>
+ <li>
+ <a href="index.html#removing-sysadmin-access">Removing sysadmin access</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#x509-certificates">X.509 Certificates</a>
+ <ol>
+ <li>
+ <a href="index.html#configuration-options">Configuration options</a>
+ </li>
+ <li>
+ <a href="index.html#certificate-authorities">Certificate Authorities</a>
+ </li>
+ <li>
+ <a href="index.html#server-certificates">Server certificates</a>
+ </li>
+ <li>
+ <a href="index.html#client-certificates">Client certificates</a>
+ </li>
+ <li>
+ <a href="index.html#signed-certificates">Signed certificates</a>
+ </li>
+ <li>
+ <a href="index.html#examine-certs">Examine Certs</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#lets-encrypt">Let’s Encrypt</a>
+ <ol>
+ <li>
+ <a href="index.html#creating-a-certificate">Creating a certificate</a>
+ </li>
+ <li>
+ <a href="index.html#renewing-a-certificate">Renewing a certificate</a>
+ </li>
+ </ol>
+ </li>
+</ol></div>
+
+<h1><a name="working-with-ssh"></a>Working with SSH</h1>
+
+<p>Whenever the <code>leap</code> command needs 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 <code>leap</code> uses SSH.</p>
+
+<h2><a name="ssh-related-files"></a>SSH related files</h2>
+
+<p>Assuming your provider directory is called &lsquo;provider&rsquo;:</p>
+
+<ul>
+<li><code>provider/nodes/crow/crow_ssh.pub</code> &ndash; The public SSH host key for node &lsquo;crow&rsquo;.</li>
+<li><code>provider/users/alice/alice_ssh.pub</code> &ndash; The public SSH user key for user &lsquo;alice&rsquo;. Anyone with the private key that corresponds to this public key will have root access to all nodes.</li>
+<li><code>provider/files/ssh/known_hosts</code> &ndash; An autogenerated known_hosts, built from combining <code>provider/nodes/*/*_ssh.pub</code>. 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 <code>known_hosts</code> and then run <code>leap compile</code>.</li>
+<li><code>provider/files/ssh/authorized_keys</code> &ndash; An autogenerated list of all the user SSH keys with root access to the notes. It is created from <code>provider/users/*/*_ssh.pub</code>. 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 <code>authorized_keys</code> and then run <code>leap compile</code>.</li>
+</ul>
+
+
+<p>All of these files should be committed to source control.</p>
+
+<p>If you rename, remove, or add a node with <code>leap node [mv|add|rm]</code> the SSH key files and the <code>known_hosts</code> file will get properly updated.</p>
+
+<h2><a name="ssh-and-local-nodes"></a>SSH and local nodes</h2>
+
+<p>Local nodes are run as Vagrant virtual machines. The <code>leap</code> command handles SSH slightly differently for these nodes.</p>
+
+<p>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&rsquo;t be reached from the internet, this is not a problem.</p>
+
+<p>Specifically, for local nodes:</p>
+
+<ol>
+<li><code>known_hosts</code> is never updated with local node keys, since the SSH public key of a local node is different for each user.</li>
+<li><code>leap</code> entirely skips the checking of host keys when connecting with a local node.</li>
+<li><code>leap</code> 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.</li>
+</ol>
+
+
+<h2><a name="to-upgrade-a-ssh-host-key"></a>To upgrade a SSH host key</h2>
+
+<p>Most servers will have more than one SSH host key. Sometimes, the server will have a better SSH host key than the one you have on file. In order to upgrade to the better SSH host key, simply re-run the init command:</p>
+
+<pre><code>workstation$ leap node init NODE_NAME
+</code></pre>
+
+<p>This will prompt you if you want to upgrade the SSH host key, but only if <code>leap</code> thinks that an upgrade is advisable.</p>
+
+<h2><a name="when-ssh-host-key-changes"></a>When SSH host key changes</h2>
+
+<p>If the host key for a node has changed, you will get an error &ldquo;WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED&rdquo;.</p>
+
+<p>To fix this, you need to remove the file <code>files/nodes/stompy/stompy_ssh.pub</code> and run <code>leap node init stompy</code>, where the node&rsquo;s name is &lsquo;stompy&rsquo;. <strong>Only do this if you are ABSOLUTELY CERTAIN that the node&rsquo;s SSH host key has changed</strong>.</p>
+
+<h2><a name="changing-the-ssh-port"></a>Changing the SSH port</h2>
+
+<p>Suppose you have a node <code>blinky</code> that has SSH listening on port 22 and you want to make it port 2200.</p>
+
+<p>First, modify the configuration for <code>blinky</code> to specify the variable <code>ssh.port</code> as 2200. Usually, this is done in <code>common.json</code> or in a tag file.</p>
+
+<p>For example, you could put this in <code>tags/production.json</code>:</p>
+
+<pre><code>{
+ "ssh": {
+ "port": 2200
+ }
+}
+</code></pre>
+
+<p>Run <code>leap compile</code> and open <code>hiera/blinky.yaml</code> to confirm that <code>ssh.port</code> is set to 2200. The port number must be specified as a number, not a string (no quotes).</p>
+
+<p>Then, you need to deploy this change so that SSH will bind to 2200. You cannot simply run <code>leap deploy blinky</code> because this command will default to using the variable <code>ssh.port</code> which is now <code>2200</code> but SSH on the node is still bound to 22.</p>
+
+<p>So, you manually override the port in the deploy command, using the old port:</p>
+
+<pre><code>leap deploy --port 22 blinky
+</code></pre>
+
+<p>Afterwards, SSH on <code>blinky</code> should be listening on port 2200 and you can just run <code>leap deploy blinky</code> from then on.</p>
+
+<h2><a name="sysadmins-with-multiple-ssh-keys"></a>Sysadmins with multiple SSH keys</h2>
+
+<p>The command <code>leap add-user --self</code> allows only one SSH key. If you want to specify more than one key for a user, you can do it manually:</p>
+
+<pre><code>users/userx/userx_ssh.pub
+users/userx/otherkey_ssh.pub
+</code></pre>
+
+<p>All keys matching &lsquo;userx/*_ssh.pub&rsquo; will be usable.</p>
+
+<h2><a name="removing-sysadmin-access"></a>Removing sysadmin access</h2>
+
+<p>Suppose you want to remove <code>userx</code> from having any further SSH access to the servers. Do this:</p>
+
+<pre><code>rm -r users/userx
+leap deploy
+</code></pre>
+
+<h1><a name="x509-certificates"></a>X.509 Certificates</h1>
+
+<h2><a name="configuration-options"></a>Configuration options</h2>
+
+<p>The <code>ca</code> option in provider.json provides settings used when generating CAs and certificates. The defaults are as follows:</p>
+
+<pre><code>{
+ "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"
+ }
+ }
+}
+</code></pre>
+
+<p>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 <code>leap inspect provider.json</code>.</p>
+
+<p>NOTE: A certificate <code>bit_size</code> greater than 2048 will probably not be recognized by most commercial CAs.</p>
+
+<h2><a name="certificate-authorities"></a>Certificate Authorities</h2>
+
+<p>There are three x.509 certificate authorities (CA) associated with your provider:</p>
+
+<ol>
+<li><strong>Commercial CA:</strong> 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 <code>files/cert/commercial_ca.crt</code>.</li>
+<li><strong>Server CA:</strong> This is a self-signed CA responsible for signing all the <strong>server</strong> certificates. The private key lives at <code>files/ca/ca.key</code> and the public cert lives at <code>files/ca/ca.crt</code>. The key is very sensitive information and must be kept private. The public cert is distributed publicly.</li>
+<li><strong>Client CA:</strong> This is a self-signed CA responsible for signing all the <strong>client</strong> certificates. The private key lives at <code>files/ca/client_ca.key</code> and the public cert lives at <code>files/ca/client_ca.crt</code>. 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.</li>
+</ol>
+
+
+<p>To generate both the Server CA and the Client CA, run the command:</p>
+
+<pre><code>leap cert ca
+</code></pre>
+
+<h2><a name="server-certificates"></a>Server certificates</h2>
+
+<p>Most every server in your service provider will have a x.509 certificate, generated by the <code>leap</code> command using the Server CA. Whenever you modify any settings of a node that might affect it&rsquo;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:</p>
+
+<pre><code>leap cert update
+</code></pre>
+
+<p>Run <code>leap help cert update</code> for notes on usage options.</p>
+
+<p>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.</p>
+
+<h2><a name="client-certificates"></a>Client certificates</h2>
+
+<p>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.</p>
+
+<p>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.</p>
+
+<p>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 <code>provider.service.bandwidth_limit</code> (in Bytes per second). An unlimited cert is given to the user if they authenticate and the user&rsquo;s service level matches one configured in <code>provider.service.levels</code> without bandwidth limits. Otherwise, the user is given a limited client cert.</p>
+
+<h2><a name="signed-certificates"></a>Signed certificates</h2>
+
+<p>We strongly recommend that the primary domain for your provider has a certificate signed by a &ldquo;trusted CA&rdquo; (e.g. A Certificate Authority that is trusted by the web browsers and in the Debian <code>ca-certificates</code> package). This provides several benefits:</p>
+
+<ol>
+<li>When users visit your website, they don&rsquo;t get a scary notice that something is wrong.</li>
+<li>When a user runs the LEAP client, selecting your service provider will not cause a warning message.</li>
+<li>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.</li>
+</ol>
+
+
+<p>The LEAP platform is designed so that it assumes you are using a certificate signed by a &ldquo;trusted CA&rdquo; for the primary domain of your provider, but all other servers are assumed to use certs signed by the Server CA you create.</p>
+
+<p>To generate a CSR, run:</p>
+
+<pre><code>leap cert csr [DOMAIN]
+</code></pre>
+
+<p>This command will generate the CSR and private key matching <code>provider.domain</code> or use DOMAIN. It also generates a server certificate signed with the Server CA. You should delete this certificate and replace it with a real one you get back from a &ldquo;trusted CA&rdquo;.</p>
+
+<p>The related commercial cert files are:</p>
+
+<pre><code>files/
+ cert/
+ domain.org.crt # Server certificate for domain.org, obtained from
+ # the trusted CA (this file is initially signed with
+ # the Server CA, but you should replace it).
+ domain.org.csr # Certificate signing request (PEM format)
+ domain.org.key # Private key for you certificate (PEM format)
+ commercial_ca.crt # DEPRECATED: The certificate chain obtained from
+ # the trusted CA (PEM format)
+</code></pre>
+
+<p>The private key file is extremely sensitive and care should be taken with its provenance.</p>
+
+<p>A few notes on the certificate chain:</p>
+
+<ul>
+<li>A certificate is basically just a key signed by another key. In x.509, the signing key might be signed by yet another key, and so on, all the way to a &lsquo;root&rsquo; key. It is the root key that a browser trusts or is in the Debian <code>ca-certificates</code> package. The chain is the set of all the keys from the root to the end certificate.</li>
+<li>For TLS, both the server and the client need the full chain from the certificate to the CA&rsquo;s root.</li>
+<li>The full chain should be appended in the file <code>domain.org.crt</code> after the server certificate. The chain can also live in <code>commercial_ca.crt</code>, but this is deprecated.</li>
+</ul>
+
+
+<p>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:</p>
+
+<pre><code> "ca": {
+ "server_certificates": {
+ "country": "US",
+ "state": "Washington",
+ "locality": "Seattle"
+ }
+ }
+</code></pre>
+
+<p>If they are not present, the CSR will be created without them.</p>
+
+<h2><a name="examine-certs"></a>Examine Certs</h2>
+
+<p>To see details about the keys and certs you can use <code>leap inspect</code> like so:</p>
+
+<pre><code>$ leap inspect files/ca/ca.crt
+</code></pre>
+
+<h1><a name="lets-encrypt"></a>Let’s Encrypt</h1>
+
+<p>Let&rsquo;s Encrypt is a free &ldquo;trusted CA&rdquo;. You can obtain signed certificates from Let&rsquo;s Encrypt very easily using the LEAP command line, so long as you have first set up DNS correctly.</p>
+
+<h2><a name="creating-a-certificate"></a>Creating a certificate</h2>
+
+<p>For example:</p>
+
+<pre><code>workstation$ leap cert register
+workstation$ leap cert csr demo.bitmask.net
+workstation$ leap cert renew demo.bitmask.net
+workstation$ leap deploy
+</code></pre>
+
+<p>Some notes:</p>
+
+<ol>
+<li>You only need to run <code>leap cert register</code> once. Registering will save the Let&rsquo;s Encrypt account key to <code>files/ca/lets-encrypt-account.key</code>. If you delete this file, just run <code>leap cert register</code> again.</li>
+<li>Let&rsquo;s Encrypt support requires that you have already platform 0.9 or later.</li>
+<li>This requires that the DNS records are correct for the domain.</li>
+</ol>
+
+
+<h2><a name="renewing-a-certificate"></a>Renewing a certificate</h2>
+
+<p>Let&rsquo;s Encrypt validations are short lived. You will need to renew the certificate at least once every three months. There is no harm in doing it more regularly, however. You can renew your cert every day if you wanted.</p>
+
+<pre><code>workstation$ leap cert renew demo.bitmask.net
+workstation$ leap deploy
+</code></pre>
+
+<p>There is no need to create a new CSR: renewing will reuse the old private key and the old CSR. It is especially important to not create a new CSR if you have advertised public key pins using HPKP.</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/guide/miscellaneous.html b/docs/en/guide/miscellaneous.html
new file mode 100644
index 00000000..03239410
--- /dev/null
+++ b/docs/en/guide/miscellaneous.html
@@ -0,0 +1,145 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Miscellaneous - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class=' level1'>
+<a class='' href='getting-started.html'>Getting Started</a>
+</li>
+<li class=' level1'>
+<a class='' href='config.html'>Configuration Files</a>
+</li>
+<li class=' level1'>
+<a class='' href='nodes.html'>Nodes</a>
+</li>
+<li class=' level1'>
+<a class='' href='keys-and-certificates.html'>Keys and Certificates</a>
+</li>
+<li class=' level1'>
+<a class='' href='domains.html'>Domains</a>
+</li>
+<li class=' level1'>
+<a class='' href='provider-configuration.html'>Provider Configuration</a>
+</li>
+<li class=' level1'>
+<a class='' href='environments.html'>Environments</a>
+</li>
+<li class=' level1'>
+<a class='' href='virtual-machines.html'>Virtual Machines</a>
+</li>
+<li class='active level1'>
+<a class='' href='miscellaneous.html'>Miscellaneous</a>
+</li>
+<li class=' level1'>
+<a class='' href='commands.html'>Command Line Reference</a>
+</li>
+<li class=' level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Miscellaneous</h1>
+
+<div id='summary'>Miscellaneous commands you may need to know.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="miscellaneous/index.html#facts">Facts</a>
+ </li>
+</ol></div>
+
+<h1><a name="facts"></a>Facts</h1>
+
+<p>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 <code>facts.json</code> is for. It stores a snapshot of certain facts about each node, as needed. Entries in <code>facts.json</code> are updated automatically when you initialize, rename, or remove a node. To manually force a full update of <code>facts.json</code>, run:</p>
+
+<pre><code>leap facts update FILTER
+</code></pre>
+
+<p>Run <code>leap help facts update</code> for more information.</p>
+
+<p>The file <code>facts.json</code> should be committed to source control. You might not have a <code>facts.json</code> if one is not required for your provider.</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/guide/miscellaneous/index.html b/docs/en/guide/miscellaneous/index.html
new file mode 100644
index 00000000..9f17df4e
--- /dev/null
+++ b/docs/en/guide/miscellaneous/index.html
@@ -0,0 +1,145 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Miscellaneous - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../../index.html'>Home</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../../guide.html'>Guide</a>
+</li>
+<li class=' level1'>
+<a class='' href='../getting-started.html'>Getting Started</a>
+</li>
+<li class=' level1'>
+<a class='' href='../config.html'>Configuration Files</a>
+</li>
+<li class=' level1'>
+<a class='' href='../nodes.html'>Nodes</a>
+</li>
+<li class=' level1'>
+<a class='' href='../keys-and-certificates.html'>Keys and Certificates</a>
+</li>
+<li class=' level1'>
+<a class='' href='../domains.html'>Domains</a>
+</li>
+<li class=' level1'>
+<a class='' href='../provider-configuration.html'>Provider Configuration</a>
+</li>
+<li class=' level1'>
+<a class='' href='../environments.html'>Environments</a>
+</li>
+<li class=' level1'>
+<a class='' href='../virtual-machines.html'>Virtual Machines</a>
+</li>
+<li class='active level1'>
+<a class='' href='../miscellaneous.html'>Miscellaneous</a>
+</li>
+<li class=' level1'>
+<a class='' href='../commands.html'>Command Line Reference</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Miscellaneous</h1>
+
+<div id='summary'>Miscellaneous commands you may need to know.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="index.html#facts">Facts</a>
+ </li>
+</ol></div>
+
+<h1><a name="facts"></a>Facts</h1>
+
+<p>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 <code>facts.json</code> is for. It stores a snapshot of certain facts about each node, as needed. Entries in <code>facts.json</code> are updated automatically when you initialize, rename, or remove a node. To manually force a full update of <code>facts.json</code>, run:</p>
+
+<pre><code>leap facts update FILTER
+</code></pre>
+
+<p>Run <code>leap help facts update</code> for more information.</p>
+
+<p>The file <code>facts.json</code> should be committed to source control. You might not have a <code>facts.json</code> if one is not required for your provider.</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/guide/nodes.html b/docs/en/guide/nodes.html
new file mode 100644
index 00000000..c6238b5f
--- /dev/null
+++ b/docs/en/guide/nodes.html
@@ -0,0 +1,231 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Nodes - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class=' level1'>
+<a class='' href='getting-started.html'>Getting Started</a>
+</li>
+<li class=' level1'>
+<a class='' href='config.html'>Configuration Files</a>
+</li>
+<li class='active level1'>
+<a class='' href='nodes.html'>Nodes</a>
+</li>
+<li class=' level1'>
+<a class='' href='keys-and-certificates.html'>Keys and Certificates</a>
+</li>
+<li class=' level1'>
+<a class='' href='domains.html'>Domains</a>
+</li>
+<li class=' level1'>
+<a class='' href='provider-configuration.html'>Provider Configuration</a>
+</li>
+<li class=' level1'>
+<a class='' href='environments.html'>Environments</a>
+</li>
+<li class=' level1'>
+<a class='' href='virtual-machines.html'>Virtual Machines</a>
+</li>
+<li class=' level1'>
+<a class='' href='miscellaneous.html'>Miscellaneous</a>
+</li>
+<li class=' level1'>
+<a class='' href='commands.html'>Command Line Reference</a>
+</li>
+<li class=' level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Nodes</h1>
+
+<div id='summary'>Working with nodes, services, tags, and locations.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="nodes/index.html#locations">Locations</a>
+ </li>
+ <li>
+ <a href="nodes/index.html#disabling-nodes">Disabling Nodes</a>
+ </li>
+</ol></div>
+
+<h1><a name="locations"></a>Locations</h1>
+
+<p>All nodes should have a <code>location.name</code> specified, and optionally additional information about the location, like the time zone. This location information is used for two things:</p>
+
+<ul>
+<li>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.</li>
+<li>Allows the client to prefer connections to nodes that are closer in physical proximity to the user. This is particularly important for OpenVPN nodes.</li>
+</ul>
+
+
+<p>The location stanza in a node&rsquo;s config file looks like this:</p>
+
+<pre><code>{
+ "location": {
+ "id": "ankara",
+ "name": "Ankara",
+ "country_code": "TR",
+ "timezone": "+2",
+ "hemisphere": "N"
+ }
+}
+</code></pre>
+
+<p>The fields:</p>
+
+<ul>
+<li><code>id</code>: An internal handle to use for this location. If two nodes have match <code>location.id</code>, then they are treated as being on a local network with one another. This value defaults to downcase and underscore of <code>location.name</code>.</li>
+<li><code>name</code>: Can be anything, might be displayed to the user in the client if they choose to manually select a gateway.</li>
+<li><code>country_code</code>: The <a href="https://en.wikipedia.org/wiki/ISO_3166-1">ISO 3166-1</a> two letter country code.</li>
+<li><code>timezone</code>: The timezone expressed as an offset from UTC (in standard time, not daylight savings). You can look up the timezone using this <a href="http://www.timeanddate.com/time/map/">handy map</a>.</li>
+<li><code>hemisphere</code>: This should be &ldquo;S&rdquo; for all servers in South America, Africa, or Australia. Otherwise, this should be &ldquo;N&rdquo;.</li>
+</ul>
+
+
+<p>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&rsquo;s timezone and locale.</p>
+
+<p>If you have multiple nodes in a single location, it is best to use a tag for the location. For example:</p>
+
+<p><code>tags/ankara.json</code>:</p>
+
+<pre><code>{
+ "location": {
+ "name": "Ankara",
+ "country_code": "TR",
+ "timezone": "+2",
+ "hemisphere": "N"
+ }
+}
+</code></pre>
+
+<p><code>nodes/vpngateway.json</code>:</p>
+
+<pre><code>{
+ "services": "openvpn",
+ "tags": ["production", "ankara"],
+ "ip_address": "1.1.1.1",
+ "openvpn": {
+ "gateway_address": "1.1.1.2"
+ }
+}
+</code></pre>
+
+<p>Unless you are using OpenStack or AWS, setting <code>location</code> for nodes is not required. It is, however, highly recommended.</p>
+
+<h1><a name="disabling-nodes"></a>Disabling Nodes</h1>
+
+<p>There are two ways to temporarily disable a node:</p>
+
+<p><strong>Option 1: disabled environment</strong></p>
+
+<p>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:</p>
+
+<pre><code>{
+ "environment": "disabled"
+}
+</code></pre>
+
+<p>Then use <code>leap env pin ENV</code> to pin the environment to something other than &lsquo;disabled&rsquo;. This only works if all the other nodes are also assigned to some environment.</p>
+
+<p><strong>Option 2: enabled == false</strong></p>
+
+<p>If a node has a property <code>enabled</code> set to false, then the <code>leap</code> command will skip over the node and pretend that it does not exist. For example:</p>
+
+<pre><code>{
+ "ip_address": "1.1.1.1",
+ "services": ["openvpn"],
+ "enabled": false
+}
+</code></pre>
+
+<p><strong>Options 3: no-deploy</strong></p>
+
+<p>If the file <code>/etc/leap/no-deploy</code> exists on a node, then when you run the commmand <code>leap deploy</code> it will halt and prevent a deploy from going through (if the node was going to be included in the deploy).</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/guide/nodes/index.html b/docs/en/guide/nodes/index.html
new file mode 100644
index 00000000..7f1a60b3
--- /dev/null
+++ b/docs/en/guide/nodes/index.html
@@ -0,0 +1,231 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Nodes - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../../index.html'>Home</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../../guide.html'>Guide</a>
+</li>
+<li class=' level1'>
+<a class='' href='../getting-started.html'>Getting Started</a>
+</li>
+<li class=' level1'>
+<a class='' href='../config.html'>Configuration Files</a>
+</li>
+<li class='active level1'>
+<a class='' href='../nodes.html'>Nodes</a>
+</li>
+<li class=' level1'>
+<a class='' href='../keys-and-certificates.html'>Keys and Certificates</a>
+</li>
+<li class=' level1'>
+<a class='' href='../domains.html'>Domains</a>
+</li>
+<li class=' level1'>
+<a class='' href='../provider-configuration.html'>Provider Configuration</a>
+</li>
+<li class=' level1'>
+<a class='' href='../environments.html'>Environments</a>
+</li>
+<li class=' level1'>
+<a class='' href='../virtual-machines.html'>Virtual Machines</a>
+</li>
+<li class=' level1'>
+<a class='' href='../miscellaneous.html'>Miscellaneous</a>
+</li>
+<li class=' level1'>
+<a class='' href='../commands.html'>Command Line Reference</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Nodes</h1>
+
+<div id='summary'>Working with nodes, services, tags, and locations.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="index.html#locations">Locations</a>
+ </li>
+ <li>
+ <a href="index.html#disabling-nodes">Disabling Nodes</a>
+ </li>
+</ol></div>
+
+<h1><a name="locations"></a>Locations</h1>
+
+<p>All nodes should have a <code>location.name</code> specified, and optionally additional information about the location, like the time zone. This location information is used for two things:</p>
+
+<ul>
+<li>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.</li>
+<li>Allows the client to prefer connections to nodes that are closer in physical proximity to the user. This is particularly important for OpenVPN nodes.</li>
+</ul>
+
+
+<p>The location stanza in a node&rsquo;s config file looks like this:</p>
+
+<pre><code>{
+ "location": {
+ "id": "ankara",
+ "name": "Ankara",
+ "country_code": "TR",
+ "timezone": "+2",
+ "hemisphere": "N"
+ }
+}
+</code></pre>
+
+<p>The fields:</p>
+
+<ul>
+<li><code>id</code>: An internal handle to use for this location. If two nodes have match <code>location.id</code>, then they are treated as being on a local network with one another. This value defaults to downcase and underscore of <code>location.name</code>.</li>
+<li><code>name</code>: Can be anything, might be displayed to the user in the client if they choose to manually select a gateway.</li>
+<li><code>country_code</code>: The <a href="https://en.wikipedia.org/wiki/ISO_3166-1">ISO 3166-1</a> two letter country code.</li>
+<li><code>timezone</code>: The timezone expressed as an offset from UTC (in standard time, not daylight savings). You can look up the timezone using this <a href="http://www.timeanddate.com/time/map/">handy map</a>.</li>
+<li><code>hemisphere</code>: This should be &ldquo;S&rdquo; for all servers in South America, Africa, or Australia. Otherwise, this should be &ldquo;N&rdquo;.</li>
+</ul>
+
+
+<p>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&rsquo;s timezone and locale.</p>
+
+<p>If you have multiple nodes in a single location, it is best to use a tag for the location. For example:</p>
+
+<p><code>tags/ankara.json</code>:</p>
+
+<pre><code>{
+ "location": {
+ "name": "Ankara",
+ "country_code": "TR",
+ "timezone": "+2",
+ "hemisphere": "N"
+ }
+}
+</code></pre>
+
+<p><code>nodes/vpngateway.json</code>:</p>
+
+<pre><code>{
+ "services": "openvpn",
+ "tags": ["production", "ankara"],
+ "ip_address": "1.1.1.1",
+ "openvpn": {
+ "gateway_address": "1.1.1.2"
+ }
+}
+</code></pre>
+
+<p>Unless you are using OpenStack or AWS, setting <code>location</code> for nodes is not required. It is, however, highly recommended.</p>
+
+<h1><a name="disabling-nodes"></a>Disabling Nodes</h1>
+
+<p>There are two ways to temporarily disable a node:</p>
+
+<p><strong>Option 1: disabled environment</strong></p>
+
+<p>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:</p>
+
+<pre><code>{
+ "environment": "disabled"
+}
+</code></pre>
+
+<p>Then use <code>leap env pin ENV</code> to pin the environment to something other than &lsquo;disabled&rsquo;. This only works if all the other nodes are also assigned to some environment.</p>
+
+<p><strong>Option 2: enabled == false</strong></p>
+
+<p>If a node has a property <code>enabled</code> set to false, then the <code>leap</code> command will skip over the node and pretend that it does not exist. For example:</p>
+
+<pre><code>{
+ "ip_address": "1.1.1.1",
+ "services": ["openvpn"],
+ "enabled": false
+}
+</code></pre>
+
+<p><strong>Options 3: no-deploy</strong></p>
+
+<p>If the file <code>/etc/leap/no-deploy</code> exists on a node, then when you run the commmand <code>leap deploy</code> it will halt and prevent a deploy from going through (if the node was going to be included in the deploy).</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/guide/provider-configuration.html b/docs/en/guide/provider-configuration.html
new file mode 100644
index 00000000..5c98eb34
--- /dev/null
+++ b/docs/en/guide/provider-configuration.html
@@ -0,0 +1,223 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Provider Configuration - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class=' level1'>
+<a class='' href='getting-started.html'>Getting Started</a>
+</li>
+<li class=' level1'>
+<a class='' href='config.html'>Configuration Files</a>
+</li>
+<li class=' level1'>
+<a class='' href='nodes.html'>Nodes</a>
+</li>
+<li class=' level1'>
+<a class='' href='keys-and-certificates.html'>Keys and Certificates</a>
+</li>
+<li class=' level1'>
+<a class='' href='domains.html'>Domains</a>
+</li>
+<li class='active level1'>
+<a class='' href='provider-configuration.html'>Provider Configuration</a>
+</li>
+<li class=' level1'>
+<a class='' href='environments.html'>Environments</a>
+</li>
+<li class=' level1'>
+<a class='' href='virtual-machines.html'>Virtual Machines</a>
+</li>
+<li class=' level1'>
+<a class='' href='miscellaneous.html'>Miscellaneous</a>
+</li>
+<li class=' level1'>
+<a class='' href='commands.html'>Command Line Reference</a>
+</li>
+<li class=' level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Provider Configuration</h1>
+
+<div id='summary'>Explore how to configure your provider.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="provider-configuration/index.html#required-provider-configuration">Required provider configuration</a>
+ </li>
+ <li>
+ <a href="provider-configuration/index.html#recommended-provider-configuration">Recommended provider configuration</a>
+ </li>
+ <li>
+ <a href="provider-configuration/index.html#configuring-service-levels">Configuring service levels</a>
+ </li>
+</ol></div>
+
+<h2><a name="required-provider-configuration"></a>Required provider configuration</h2>
+
+<p>There are a few required settings in <code>provider.json</code>. At a minimum, you must have:</p>
+
+<ul>
+<li><code>domain</code>: defines the primary domain of the provider. This is the domain that users will type in when using the Bitmask client, although it is not necessarily the domain where users will visit if they sign up via the web application. If email is supported, all accounts will be <code>username@domain</code>.</li>
+<li><code>name</code>: A brief title for this provider. It can be multiple words, but should not be too long.</li>
+<li><code>contacts.default</code>: One or more email addresses for sysadmins.</li>
+</ul>
+
+
+<p>For example:</p>
+
+<pre><code>{
+ "domain": "freerobot.org",
+ "name": "Freedom for Robots!",
+ "contacts": {
+ "default": "root@freerobot.org"
+ }
+}
+</code></pre>
+
+<h2><a name="recommended-provider-configuration"></a>Recommended provider configuration</h2>
+
+<ul>
+<li><code>description</code>: A longer description of the provider, shown to the user when they register a new account through Bitmask client.</li>
+<li><code>languages</code>: A list of language codes that should be enabled.</li>
+<li><code>default_language</code>: The initial default language code.</li>
+<li><code>enrollment_policy</code>: One of &ldquo;open&rdquo;, &ldquo;closed&rdquo;, or &ldquo;invite&rdquo;. (invite not currently supported).</li>
+</ul>
+
+
+<p>For example:</p>
+
+<pre><code>{
+ "description": "It is time for robots of the world to unite and throw of the shackles of servitude to our organic overlords.",
+ "languages": ["en", "de", "pt", "01"],
+ "default_language": "01",
+ "enrollman_policy": "open"
+}
+</code></pre>
+
+<p>For a full list of possible settings, you can use <code>leap inspect</code> to see how provider.json is evaluated after including the inherited defaults:</p>
+
+<pre><code>$ leap inspect provider.json
+</code></pre>
+
+<h2><a name="configuring-service-levels"></a>Configuring service levels</h2>
+
+<p>The <code>provider.json</code> file defines the available service levels for the provider.</p>
+
+<p>For example, in provider.json:</p>
+
+<pre><code>"service": {
+ "default_service_level": "low",
+ "levels": {
+ "low": {
+ "description": "Entry level plan, with unlimited bandwidth and minimal storage quota.",
+ "name": "entry",
+ "storage": "10 MB",
+ "rate": {
+ "USD": 5,
+ "GBP": 3,
+ "EUR": 6
+ }
+ },
+ "full": {
+ "description": "Full plan, with unlimited bandwidth and higher quota."
+ "name": "full",
+ "storage": "5 GB",
+ "rate": {
+ "USD": 10,
+ "GBP": 6,
+ "EUR": 12
+ }
+ }
+ }
+ }
+}
+</code></pre>
+
+<p>For a list of currency codes, see <a href="https://en.wikipedia.org/wiki/ISO_4217#Active_codes">https://en.wikipedia.org/wiki/ISO_4217#Active_codes</a></p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/guide/provider-configuration/index.html b/docs/en/guide/provider-configuration/index.html
new file mode 100644
index 00000000..b710cb64
--- /dev/null
+++ b/docs/en/guide/provider-configuration/index.html
@@ -0,0 +1,223 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Provider Configuration - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../../index.html'>Home</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../../guide.html'>Guide</a>
+</li>
+<li class=' level1'>
+<a class='' href='../getting-started.html'>Getting Started</a>
+</li>
+<li class=' level1'>
+<a class='' href='../config.html'>Configuration Files</a>
+</li>
+<li class=' level1'>
+<a class='' href='../nodes.html'>Nodes</a>
+</li>
+<li class=' level1'>
+<a class='' href='../keys-and-certificates.html'>Keys and Certificates</a>
+</li>
+<li class=' level1'>
+<a class='' href='../domains.html'>Domains</a>
+</li>
+<li class='active level1'>
+<a class='' href='../provider-configuration.html'>Provider Configuration</a>
+</li>
+<li class=' level1'>
+<a class='' href='../environments.html'>Environments</a>
+</li>
+<li class=' level1'>
+<a class='' href='../virtual-machines.html'>Virtual Machines</a>
+</li>
+<li class=' level1'>
+<a class='' href='../miscellaneous.html'>Miscellaneous</a>
+</li>
+<li class=' level1'>
+<a class='' href='../commands.html'>Command Line Reference</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Provider Configuration</h1>
+
+<div id='summary'>Explore how to configure your provider.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="index.html#required-provider-configuration">Required provider configuration</a>
+ </li>
+ <li>
+ <a href="index.html#recommended-provider-configuration">Recommended provider configuration</a>
+ </li>
+ <li>
+ <a href="index.html#configuring-service-levels">Configuring service levels</a>
+ </li>
+</ol></div>
+
+<h2><a name="required-provider-configuration"></a>Required provider configuration</h2>
+
+<p>There are a few required settings in <code>provider.json</code>. At a minimum, you must have:</p>
+
+<ul>
+<li><code>domain</code>: defines the primary domain of the provider. This is the domain that users will type in when using the Bitmask client, although it is not necessarily the domain where users will visit if they sign up via the web application. If email is supported, all accounts will be <code>username@domain</code>.</li>
+<li><code>name</code>: A brief title for this provider. It can be multiple words, but should not be too long.</li>
+<li><code>contacts.default</code>: One or more email addresses for sysadmins.</li>
+</ul>
+
+
+<p>For example:</p>
+
+<pre><code>{
+ "domain": "freerobot.org",
+ "name": "Freedom for Robots!",
+ "contacts": {
+ "default": "root@freerobot.org"
+ }
+}
+</code></pre>
+
+<h2><a name="recommended-provider-configuration"></a>Recommended provider configuration</h2>
+
+<ul>
+<li><code>description</code>: A longer description of the provider, shown to the user when they register a new account through Bitmask client.</li>
+<li><code>languages</code>: A list of language codes that should be enabled.</li>
+<li><code>default_language</code>: The initial default language code.</li>
+<li><code>enrollment_policy</code>: One of &ldquo;open&rdquo;, &ldquo;closed&rdquo;, or &ldquo;invite&rdquo;. (invite not currently supported).</li>
+</ul>
+
+
+<p>For example:</p>
+
+<pre><code>{
+ "description": "It is time for robots of the world to unite and throw of the shackles of servitude to our organic overlords.",
+ "languages": ["en", "de", "pt", "01"],
+ "default_language": "01",
+ "enrollman_policy": "open"
+}
+</code></pre>
+
+<p>For a full list of possible settings, you can use <code>leap inspect</code> to see how provider.json is evaluated after including the inherited defaults:</p>
+
+<pre><code>$ leap inspect provider.json
+</code></pre>
+
+<h2><a name="configuring-service-levels"></a>Configuring service levels</h2>
+
+<p>The <code>provider.json</code> file defines the available service levels for the provider.</p>
+
+<p>For example, in provider.json:</p>
+
+<pre><code>"service": {
+ "default_service_level": "low",
+ "levels": {
+ "low": {
+ "description": "Entry level plan, with unlimited bandwidth and minimal storage quota.",
+ "name": "entry",
+ "storage": "10 MB",
+ "rate": {
+ "USD": 5,
+ "GBP": 3,
+ "EUR": 6
+ }
+ },
+ "full": {
+ "description": "Full plan, with unlimited bandwidth and higher quota."
+ "name": "full",
+ "storage": "5 GB",
+ "rate": {
+ "USD": 10,
+ "GBP": 6,
+ "EUR": 12
+ }
+ }
+ }
+ }
+}
+</code></pre>
+
+<p>For a list of currency codes, see <a href="https://en.wikipedia.org/wiki/ISO_4217#Active_codes">https://en.wikipedia.org/wiki/ISO_4217#Active_codes</a></p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/guide/virtual-machines.html b/docs/en/guide/virtual-machines.html
new file mode 100644
index 00000000..5cee9a40
--- /dev/null
+++ b/docs/en/guide/virtual-machines.html
@@ -0,0 +1,299 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Virtual Machines - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class=' level1'>
+<a class='' href='getting-started.html'>Getting Started</a>
+</li>
+<li class=' level1'>
+<a class='' href='config.html'>Configuration Files</a>
+</li>
+<li class=' level1'>
+<a class='' href='nodes.html'>Nodes</a>
+</li>
+<li class=' level1'>
+<a class='' href='keys-and-certificates.html'>Keys and Certificates</a>
+</li>
+<li class=' level1'>
+<a class='' href='domains.html'>Domains</a>
+</li>
+<li class=' level1'>
+<a class='' href='provider-configuration.html'>Provider Configuration</a>
+</li>
+<li class=' level1'>
+<a class='' href='environments.html'>Environments</a>
+</li>
+<li class='active level1'>
+<a class='' href='virtual-machines.html'>Virtual Machines</a>
+</li>
+<li class=' level1'>
+<a class='' href='miscellaneous.html'>Miscellaneous</a>
+</li>
+<li class=' level1'>
+<a class='' href='commands.html'>Command Line Reference</a>
+</li>
+<li class=' level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Virtual Machines</h1>
+
+<div id='summary'>Running LEAP platform on remote virtual machines</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="virtual-machines/index.html#introduction">Introduction</a>
+ </li>
+ <li>
+ <a href="virtual-machines/index.html#configuration">Configuration</a>
+ </li>
+ <li>
+ <a href="virtual-machines/index.html#usage">Usage</a>
+ </li>
+ <li>
+ <a href="virtual-machines/index.html#keeping-state-synchronized">Keeping State Synchronized</a>
+ </li>
+ <li>
+ <a href="virtual-machines/index.html#multiple-authentication-profiles">Multiple authentication profiles</a>
+ </li>
+</ol></div>
+
+<h2><a name="introduction"></a>Introduction</h2>
+
+<p>You can use the <code>leap</code> command line to easily remote virtual machines.</p>
+
+<p>Note: there are two types of virtual machines that <code>leap</code> can handle:</p>
+
+<ul>
+<li><strong>Local</strong> virtual machines running with vagrant, for use in testing.</li>
+<li><strong>Remote</strong> virtual machines hosted by a cloud provider like AWS or Rackspace.</li>
+</ul>
+
+
+<p>This guide is for &ldquo;remote virtual machines&rdquo;. For &ldquo;local virtual machines&rdquo; see <a href="../tutorials/vagrant.html">Vagrant</a>.</p>
+
+<p>Currently, only Amazon AWS is supported as a cloud provider.</p>
+
+<h2><a name="configuration"></a>Configuration</h2>
+
+<p>To get started with virtual machines, you must configure a <code>cloud.json</code> file with your API credentials for the virtual machine vendor. This file lives in the root of your provider directory.</p>
+
+<p>For example:</p>
+
+<pre><code>{
+ "my_aws": {
+ "api": "aws",
+ "vendor": "aws",
+ "auth": {
+ "region": "us-west-2",
+ "aws_access_key_id": "xxxx my key id xxxx",
+ "aws_secret_access_key": "xxxx my access key xxxx"
+ }
+ }
+}
+</code></pre>
+
+<p>This will configure a cloud &ldquo;authentication profile&rdquo; called &ldquo;my_aws&rdquo;. This profile will be used by default if there is only one. See below for managing multiple authentication profiles.</p>
+
+<p><em>Required cloud.json properties</em></p>
+
+<ul>
+<li><code>$profile</code>: In this case, &lsquo;my_aws&rsquo;.</li>
+<li><code>$profile.api</code>: For now, must always be &ldquo;aws&rdquo;.</li>
+<li><code>$profile.vendor</code>: For now, must always be &ldquo;aws&rdquo;.</li>
+<li><code>$profile.auth</code>: API specific authentication configuration for this profile. In the case of AWS, it must include <code>auth.region</code>, <code>auth.aws_access_key_id</code>, and <code>aws_secret_access_key</code>.</li>
+</ul>
+
+
+<p><em>Additional cloud.json properties</em></p>
+
+<p>In addition to required configuration properties, these are optional:</p>
+
+<ul>
+<li><code>$profile.default_image</code>: What image to use for new nodes by default. Generally, you should not specify this, because it will automatically select the right Debian image for your region. A node can override this with the property <code>vm.image</code>.</li>
+<li><code>$profile.default_options</code>: This is passed directly to the cloud API, and so is specific to whichever API you are using. The node can override this with the property <code>vm.options</code>.</li>
+</ul>
+
+
+<p>A more complete example <code>cloud.json</code>:</p>
+
+<pre><code>{
+ "my_aws": {
+ "api": "aws",
+ "vendor": "aws",
+ "auth": {
+ "region": "us-west-2",
+ "aws_access_key_id": "xxxx my key id xxxx",
+ "aws_secret_access_key": "xxxx my access key xxxx"
+ },
+ "default_image": "ami-98e114f8",
+ "default_options": {
+ "InstanceType": "t2.nano"
+ }
+ }
+}
+</code></pre>
+
+<p>See also:</p>
+
+<ul>
+<li><a href="https://aws.amazon.com/ec2/instance-types/">Available instance types for AWS</a></li>
+</ul>
+
+
+<h2><a name="usage"></a>Usage</h2>
+
+<p>See <code>leap help vm</code> for a description of all the possible commands.</p>
+
+<p>In order to be able to create new virtual machine instances, you need to register your SSH key with the VM vendor.</p>
+
+<pre><code>leap vm key-register
+</code></pre>
+
+<p>You only have to do this once, and only people who will be creating new VM instances need to do this.</p>
+
+<p>Once you have done that, you just <code>leap vm add</code> to create the virtual machine and then <code>leap vm start</code> to actually boot it.</p>
+
+<pre><code>leap vm add mynode
+leap vm start mynode
+</code></pre>
+
+<p>You can specify seed values to <code>leap vm add</code>. For example:</p>
+
+<pre><code>leap vm add mynode services:webapp tags:seattle vm.options.InstanceType:t2.small
+</code></pre>
+
+<p>Check to see what the status is of all VMs:</p>
+
+<pre><code>leap vm status
+</code></pre>
+
+<p>If it looks good, you can now deploy to the new server:</p>
+
+<pre><code>leap node init mynode
+leap deploy mynode
+</code></pre>
+
+<p>To stop the VM:</p>
+
+<pre><code>leap vm stop mynode
+</code></pre>
+
+<p>To destroy the VM and clean up its storage space:</p>
+
+<pre><code>leap vm rm mynode
+</code></pre>
+
+<p>In general, you should remove VMs instead of stopping them, unless you plan on stopping the VM for a short amount of time. A stopped VM will still use disk space and still incur charges.</p>
+
+<h2><a name="keeping-state-synchronized"></a>Keeping State Synchronized</h2>
+
+<p>The LEAP platform stores all its state information in flat static files. The virtual machine vendor, however, also has its own state.</p>
+
+<p>On the provider side, VM state is stored in node configuration files in <code>nodes/*.json</code>. Of particular importance are the properties <code>ip_address</code> and <code>vm.id</code>.</p>
+
+<p>Most of the time, you should not have any trouble: the <code>leap vm</code> commands will keep things in sync. However, if the state of your configuration files gets out of sync with the state of the virtual machines, it can cause problems.</p>
+
+<p>The command <code>leap vm status</code> will warn you whenever it detects a problem and it will usually propose a fix.</p>
+
+<p>Typically, the fix is to manually update the binding between a local node configuration and the running remote virtual machine, like so:</p>
+
+<pre><code>leap vm bind NODE_NAME VM_ID
+</code></pre>
+
+<h2><a name="multiple-authentication-profiles"></a>Multiple authentication profiles</h2>
+
+<p>If you have multiple profiles configured in <code>cloud.json</code>, you can specify which one you want to use:</p>
+
+<ul>
+<li>Set the <code>vm.auth</code> property in the node configuration to match the name of the authentication profile.</li>
+<li>Or, pass <code>--auth PROFILE_NAME</code> on the command line.</li>
+</ul>
+
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/guide/virtual-machines/index.html b/docs/en/guide/virtual-machines/index.html
new file mode 100644
index 00000000..da0da107
--- /dev/null
+++ b/docs/en/guide/virtual-machines/index.html
@@ -0,0 +1,299 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Virtual Machines - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../../index.html'>Home</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../../guide.html'>Guide</a>
+</li>
+<li class=' level1'>
+<a class='' href='../getting-started.html'>Getting Started</a>
+</li>
+<li class=' level1'>
+<a class='' href='../config.html'>Configuration Files</a>
+</li>
+<li class=' level1'>
+<a class='' href='../nodes.html'>Nodes</a>
+</li>
+<li class=' level1'>
+<a class='' href='../keys-and-certificates.html'>Keys and Certificates</a>
+</li>
+<li class=' level1'>
+<a class='' href='../domains.html'>Domains</a>
+</li>
+<li class=' level1'>
+<a class='' href='../provider-configuration.html'>Provider Configuration</a>
+</li>
+<li class=' level1'>
+<a class='' href='../environments.html'>Environments</a>
+</li>
+<li class='active level1'>
+<a class='' href='../virtual-machines.html'>Virtual Machines</a>
+</li>
+<li class=' level1'>
+<a class='' href='../miscellaneous.html'>Miscellaneous</a>
+</li>
+<li class=' level1'>
+<a class='' href='../commands.html'>Command Line Reference</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Virtual Machines</h1>
+
+<div id='summary'>Running LEAP platform on remote virtual machines</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="index.html#introduction">Introduction</a>
+ </li>
+ <li>
+ <a href="index.html#configuration">Configuration</a>
+ </li>
+ <li>
+ <a href="index.html#usage">Usage</a>
+ </li>
+ <li>
+ <a href="index.html#keeping-state-synchronized">Keeping State Synchronized</a>
+ </li>
+ <li>
+ <a href="index.html#multiple-authentication-profiles">Multiple authentication profiles</a>
+ </li>
+</ol></div>
+
+<h2><a name="introduction"></a>Introduction</h2>
+
+<p>You can use the <code>leap</code> command line to easily remote virtual machines.</p>
+
+<p>Note: there are two types of virtual machines that <code>leap</code> can handle:</p>
+
+<ul>
+<li><strong>Local</strong> virtual machines running with vagrant, for use in testing.</li>
+<li><strong>Remote</strong> virtual machines hosted by a cloud provider like AWS or Rackspace.</li>
+</ul>
+
+
+<p>This guide is for &ldquo;remote virtual machines&rdquo;. For &ldquo;local virtual machines&rdquo; see <a href="../../tutorials/vagrant.html">Vagrant</a>.</p>
+
+<p>Currently, only Amazon AWS is supported as a cloud provider.</p>
+
+<h2><a name="configuration"></a>Configuration</h2>
+
+<p>To get started with virtual machines, you must configure a <code>cloud.json</code> file with your API credentials for the virtual machine vendor. This file lives in the root of your provider directory.</p>
+
+<p>For example:</p>
+
+<pre><code>{
+ "my_aws": {
+ "api": "aws",
+ "vendor": "aws",
+ "auth": {
+ "region": "us-west-2",
+ "aws_access_key_id": "xxxx my key id xxxx",
+ "aws_secret_access_key": "xxxx my access key xxxx"
+ }
+ }
+}
+</code></pre>
+
+<p>This will configure a cloud &ldquo;authentication profile&rdquo; called &ldquo;my_aws&rdquo;. This profile will be used by default if there is only one. See below for managing multiple authentication profiles.</p>
+
+<p><em>Required cloud.json properties</em></p>
+
+<ul>
+<li><code>$profile</code>: In this case, &lsquo;my_aws&rsquo;.</li>
+<li><code>$profile.api</code>: For now, must always be &ldquo;aws&rdquo;.</li>
+<li><code>$profile.vendor</code>: For now, must always be &ldquo;aws&rdquo;.</li>
+<li><code>$profile.auth</code>: API specific authentication configuration for this profile. In the case of AWS, it must include <code>auth.region</code>, <code>auth.aws_access_key_id</code>, and <code>aws_secret_access_key</code>.</li>
+</ul>
+
+
+<p><em>Additional cloud.json properties</em></p>
+
+<p>In addition to required configuration properties, these are optional:</p>
+
+<ul>
+<li><code>$profile.default_image</code>: What image to use for new nodes by default. Generally, you should not specify this, because it will automatically select the right Debian image for your region. A node can override this with the property <code>vm.image</code>.</li>
+<li><code>$profile.default_options</code>: This is passed directly to the cloud API, and so is specific to whichever API you are using. The node can override this with the property <code>vm.options</code>.</li>
+</ul>
+
+
+<p>A more complete example <code>cloud.json</code>:</p>
+
+<pre><code>{
+ "my_aws": {
+ "api": "aws",
+ "vendor": "aws",
+ "auth": {
+ "region": "us-west-2",
+ "aws_access_key_id": "xxxx my key id xxxx",
+ "aws_secret_access_key": "xxxx my access key xxxx"
+ },
+ "default_image": "ami-98e114f8",
+ "default_options": {
+ "InstanceType": "t2.nano"
+ }
+ }
+}
+</code></pre>
+
+<p>See also:</p>
+
+<ul>
+<li><a href="https://aws.amazon.com/ec2/instance-types/">Available instance types for AWS</a></li>
+</ul>
+
+
+<h2><a name="usage"></a>Usage</h2>
+
+<p>See <code>leap help vm</code> for a description of all the possible commands.</p>
+
+<p>In order to be able to create new virtual machine instances, you need to register your SSH key with the VM vendor.</p>
+
+<pre><code>leap vm key-register
+</code></pre>
+
+<p>You only have to do this once, and only people who will be creating new VM instances need to do this.</p>
+
+<p>Once you have done that, you just <code>leap vm add</code> to create the virtual machine and then <code>leap vm start</code> to actually boot it.</p>
+
+<pre><code>leap vm add mynode
+leap vm start mynode
+</code></pre>
+
+<p>You can specify seed values to <code>leap vm add</code>. For example:</p>
+
+<pre><code>leap vm add mynode services:webapp tags:seattle vm.options.InstanceType:t2.small
+</code></pre>
+
+<p>Check to see what the status is of all VMs:</p>
+
+<pre><code>leap vm status
+</code></pre>
+
+<p>If it looks good, you can now deploy to the new server:</p>
+
+<pre><code>leap node init mynode
+leap deploy mynode
+</code></pre>
+
+<p>To stop the VM:</p>
+
+<pre><code>leap vm stop mynode
+</code></pre>
+
+<p>To destroy the VM and clean up its storage space:</p>
+
+<pre><code>leap vm rm mynode
+</code></pre>
+
+<p>In general, you should remove VMs instead of stopping them, unless you plan on stopping the VM for a short amount of time. A stopped VM will still use disk space and still incur charges.</p>
+
+<h2><a name="keeping-state-synchronized"></a>Keeping State Synchronized</h2>
+
+<p>The LEAP platform stores all its state information in flat static files. The virtual machine vendor, however, also has its own state.</p>
+
+<p>On the provider side, VM state is stored in node configuration files in <code>nodes/*.json</code>. Of particular importance are the properties <code>ip_address</code> and <code>vm.id</code>.</p>
+
+<p>Most of the time, you should not have any trouble: the <code>leap vm</code> commands will keep things in sync. However, if the state of your configuration files gets out of sync with the state of the virtual machines, it can cause problems.</p>
+
+<p>The command <code>leap vm status</code> will warn you whenever it detects a problem and it will usually propose a fix.</p>
+
+<p>Typically, the fix is to manually update the binding between a local node configuration and the running remote virtual machine, like so:</p>
+
+<pre><code>leap vm bind NODE_NAME VM_ID
+</code></pre>
+
+<h2><a name="multiple-authentication-profiles"></a>Multiple authentication profiles</h2>
+
+<p>If you have multiple profiles configured in <code>cloud.json</code>, you can specify which one you want to use:</p>
+
+<ul>
+<li>Set the <code>vm.auth</code> property in the node configuration to match the name of the authentication profile.</li>
+<li>Or, pass <code>--auth PROFILE_NAME</code> on the command line.</li>
+</ul>
+
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/services.html b/docs/en/services.html
new file mode 100644
index 00000000..55211e64
--- /dev/null
+++ b/docs/en/services.html
@@ -0,0 +1,251 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Services - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='tutorials.html'>Tutorials</a>
+</li>
+<li class='active level0'>
+<a class='' href='services.html'>Services</a>
+</li>
+<li class=' level1'>
+<a class='' href='services/couchdb.html'>couchdb</a>
+</li>
+<li class=' level1'>
+<a class='' href='services/openvpn.html'>openvpn</a>
+</li>
+<li class=' level1'>
+<a class='' href='services/monitor.html'>monitor</a>
+</li>
+<li class=' level1'>
+<a class='' href='services/mx.html'>mx</a>
+</li>
+<li class=' level1'>
+<a class='' href='services/soledad.html'>soledad</a>
+</li>
+<li class=' level1'>
+<a class='' href='services/tor.html'>tor</a>
+</li>
+<li class=' level1'>
+<a class='' href='services/webapp.html'>webapp</a>
+</li>
+<li class=' level0'>
+<a class='' href='upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Guide to node services</h1>
+
+<div id='summary'></div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="services/index.html#introduction">Introduction</a>
+ </li>
+ <li>
+ <a href="services/index.html#available-services">Available services</a>
+ </li>
+</ol></div>
+
+<h1><a name="introduction"></a>Introduction</h1>
+
+<p>Every node (server) must have one or more <code>services</code> defined that determines what role the node performs. For example:</p>
+
+<pre><code>workstation$ cat nodes/stallman.json
+{
+ "ip_address": "199.99.99.1",
+ "services": ["webapp", "tor"]
+}
+</code></pre>
+
+<p>Here are common questions to ask when adding a new node to your provider:</p>
+
+<ul>
+<li><strong>many or few?</strong> Some services benefit from having many nodes, while some services are best run on only one or two nodes.</li>
+<li><strong>required or optional?</strong> Some services are required, while others can be left out.</li>
+<li><strong>who does the node communicate with?</strong> Some services communicate very heavily with other particular services. Nodes running these services should be close together.</li>
+<li><strong>public or private network?</strong> Some services communicate with the public internet, while others only need to communicate with other nodes in the infrastructure.</li>
+</ul>
+
+
+<h1><a name="available-services"></a>Available services</h1>
+
+<table class="table table-striped">
+<tr>
+ <th>Service</th>
+ <th>VPN</th>
+ <th>Email</th>
+ <th>Notes</th>
+</tr>
+<tr>
+ <td>webapp</td>
+ <td><i class="fa fa-circle"></i></td>
+ <td><i class="fa fa-circle"></i></td>
+ <td>User control panel, provider API, and support system.</td>
+</tr>
+<tr>
+ <td>couchdb</td>
+ <td><i class="fa fa-circle"></i></td>
+ <td><i class="fa fa-circle"></i></td>
+ <td>Data storage for everything. Private node.</td>
+<td></td>
+</tr>
+<tr>
+ <td>soledad</td>
+ <td><i class="fa fa-circle-o"></i></td>
+ <td><i class="fa fa-circle"></i></td>
+ <td>User data synchronization daemon. Usually paired with <code>couchdb</code> nodes.</td>
+<td></td>
+</tr>
+<tr>
+ <td>mx</td>
+ <td><i class="fa fa-circle-o"></i></td>
+ <td><i class="fa fa-circle"></i></td>
+ <td>Incoming and outgoing MX servers.</td>
+</tr>
+<tr>
+ <td>openvpn</td>
+ <td><i class="fa fa-circle"></i></td>
+ <td><i class="fa fa-circle-o"></i></td>
+ <td>OpenVPN gateways.</td>
+</tr>
+<tr>
+ <td>monitor</td>
+ <td><i class="fa fa-dot-circle-o"></i></td>
+ <td><i class="fa fa-dot-circle-o"></i></td>
+ <td>Nagios monitoring. This service must be on the webapp node.</td>
+</tr>
+<tr>
+ <td>tor</td>
+ <td><i class="fa fa-dot-circle-o"></i></td>
+ <td><i class="fa fa-dot-circle-o"></i></td>
+ <td>Tor exit node.</td>
+</tr>
+</table>
+
+
+<p>Key: <i class="fa fa-circle"> Required</i>, <i class="fa fa-dot-circle-o"> Optional</i>, <i class="fa fa-circle-o"> Not Used</i></p>
+
+<p><div class=' page-summary'>
+<h2>
+<a href='services/couchdb.html'>couchdb</a>
+</h2>
+<div class='summary'>Data storage for all user data.</div>
+</div>
+<div class=' page-summary'>
+<h2>
+<a href='services/openvpn.html'>openvpn</a>
+</h2>
+<div class='summary'>OpenVPN egress gateways</div>
+</div>
+<div class=' page-summary'>
+<h2>
+<a href='services/monitor.html'>monitor</a>
+</h2>
+<div class='summary'>Nagios monitoring and continuous testing.</div>
+</div>
+<div class=' page-summary'>
+<h2>
+<a href='services/mx.html'>mx</a>
+</h2>
+<div class='summary'>Incoming and outgoing MX servers.</div>
+</div>
+<div class=' page-summary'>
+<h2>
+<a href='services/soledad.html'>soledad</a>
+</h2>
+<div class='summary'>User data synchronization daemon</div>
+</div>
+<div class=' page-summary'>
+<h2>
+<a href='services/tor.html'>tor</a>
+</h2>
+<div class='summary'>Tor exit node or hidden service</div>
+</div>
+<div class=' page-summary'>
+<h2>
+<a href='services/webapp.html'>webapp</a>
+</h2>
+<div class='summary'>leap_web user management application and provider API.</div>
+</div>
+</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/services/couchdb.html b/docs/en/services/couchdb.html
new file mode 100644
index 00000000..6de6455c
--- /dev/null
+++ b/docs/en/services/couchdb.html
@@ -0,0 +1,328 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+couchdb - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class='active level1'>
+<a class='' href='couchdb.html'>couchdb</a>
+</li>
+<li class=' level1'>
+<a class='' href='openvpn.html'>openvpn</a>
+</li>
+<li class=' level1'>
+<a class='' href='monitor.html'>monitor</a>
+</li>
+<li class=' level1'>
+<a class='' href='mx.html'>mx</a>
+</li>
+<li class=' level1'>
+<a class='' href='soledad.html'>soledad</a>
+</li>
+<li class=' level1'>
+<a class='' href='tor.html'>tor</a>
+</li>
+<li class=' level1'>
+<a class='' href='webapp.html'>webapp</a>
+</li>
+<li class=' level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>couchdb</h1>
+
+<div id='summary'>Data storage for all user data.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="couchdb/index.html#topology">Topology</a>
+ </li>
+ <li>
+ <a href="couchdb/index.html#configuration">Configuration</a>
+ <ol>
+ <li>
+ <a href="couchdb/index.html#nighly-dumps">Nighly dumps</a>
+ </li>
+ <li>
+ <a href="couchdb/index.html#plain-couchdb">Plain CouchDB</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="couchdb/index.html#various-tasks">Various Tasks</a>
+ <ol>
+ <li>
+ <a href="couchdb/index.html#re-enabling-blocked-account">Re-enabling blocked account</a>
+ </li>
+ <li>
+ <a href="couchdb/index.html#how-to-find-out-which-userstore-belongs-to-which-identity">How to find out which userstore belongs to which identity?</a>
+ </li>
+ <li>
+ <a href="couchdb/index.html#how-much-disk-space-is-used-by-a-userstore">How much disk space is used by a userstore</a>
+ </li>
+ <li>
+ <a href="couchdb/index.html#migrating-from-bigcouch-to-plain-couchdb">Migrating from BigCouch to plain CouchDB</a>
+ </li>
+ </ol>
+ </li>
+</ol></div>
+
+<h2><a name="topology"></a>Topology</h2>
+
+<p>Required:</p>
+
+<ul>
+<li>Nodes with <code>couchdb</code> service must also have <code>soledad</code> service, if email is enabled.</li>
+</ul>
+
+
+<p>Suggested:</p>
+
+<ul>
+<li>Nodes with <code>couchdb</code> service communicate heavily with <code>webapp</code> and <code>mx</code>.</li>
+</ul>
+
+
+<p><code>couchdb</code> nodes do not need to be reachable from the public internet, although the <code>soledad</code> service does require this.</p>
+
+<h2><a name="configuration"></a>Configuration</h2>
+
+<h3><a name="nighly-dumps"></a>Nighly dumps</h3>
+
+<p>You can do a nightly couchdb data dump by adding this to your node config:</p>
+
+<pre><code>"couch": {
+ "backup": true
+}
+</code></pre>
+
+<p>Data will get dumped to <code>/var/backups/couchdb</code>.</p>
+
+<h3><a name="plain-couchdb"></a>Plain CouchDB</h3>
+
+<p>BigCouch is not supported on Platform version 0.8 and higher: only plain CouchDB is possible. For earlier versions, you must do this in order to use plain CouchDB:</p>
+
+<pre><code>"couch": {
+ "master": true,
+ "pwhash_alg": "pbkdf2"
+}
+</code></pre>
+
+<h2><a name="various-tasks"></a>Various Tasks</h2>
+
+<h3><a name="re-enabling-blocked-account"></a>Re-enabling blocked account</h3>
+
+<p>When a user account gets destroyed from the webapp, there&rsquo;s still a leftover doc in the identities db so other people can&rsquo;t claim that account without an admin&rsquo;s intervention. You can remove this username reservation through the webapp.</p>
+
+<p>However, here is how you could do it manually, if you wanted to:</p>
+
+<p>grep the identities db for the email address:</p>
+
+<pre><code>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
+</code></pre>
+
+<p>lookup &ldquo;id&rdquo; and &ldquo;rev&rdquo; to delete the doc:</p>
+
+<pre><code>curl -s --netrc-file /etc/couchdb/couchdb.netrc -X DELETE 'http://127.0.0.1:5984/identities/b25cf10f935b58088f0d547fca823265?rev=2-715a9beba597a2ab01851676f12c3e4a'
+</code></pre>
+
+<h3><a name="how-to-find-out-which-userstore-belongs-to-which-identity"></a>How to find out which userstore belongs to which identity?</h3>
+
+<pre><code>/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": ...
+</code></pre>
+
+<ul>
+<li>search for the &ldquo;user_id&rdquo; field</li>
+<li>in this example <a href="&#x6d;&#x61;&#x69;&#108;&#x74;&#x6f;&#58;&#x74;&#101;&#x73;&#116;&#x75;&#115;&#101;&#114;&#64;&#x65;&#x78;&#97;&#109;&#x70;&#108;&#x65;&#x2e;&#111;&#114;&#x67;">&#x74;&#101;&#x73;&#116;&#x75;&#115;&#x65;&#x72;&#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;&#101;&#x2e;&#x6f;&#114;&#x67;</a> uses the database user-665e004870ee17aa4c94331ff3cd59eb</li>
+</ul>
+
+
+<h3><a name="how-much-disk-space-is-used-by-a-userstore"></a>How much disk space is used by a userstore</h3>
+
+<p>Beware that this returns the uncompacted disk size (see <a href="http://wiki.apache.org/couchdb/Compaction">http://wiki.apache.org/couchdb/Compaction</a>)</p>
+
+<pre><code>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
+</code></pre>
+
+<h3><a name="migrating-from-bigcouch-to-plain-couchdb"></a>Migrating from BigCouch to plain CouchDB</h3>
+
+<p><p>At the end of this process, you will have just <em>one</em> node with <code>services</code> property equal to <code>couchdb</code>. If you had a BigCouch cluster before, you will be removing all but one of those machines to consolidate them into one CouchDB machine.</p>
+
+<ol>
+<li><p>if you have multiple nodes with the <code>couchdb</code> service on them, pick one of them to be your CouchDB server, and remove the service from the others. If these machines were only doing BigCouch before, you can remove the nodes completely with <code>leap node rm &lt;nodename&gt;</code> and then you can decommission the servers</p></li>
+<li><p>put the webapp into <a href="webapp.html#maintenance-mode">maintenance mode</a></p></li>
+<li><p>turn off daemons that access the database. For example:</p>
+
+<pre><code class="`"> workstation$ leap ssh &lt;each soledad-node&gt;
+ server# /etc/init.d/soledad-server stop
+
+ workstation$ leap ssh &lt;mx-node&gt;
+ server# /etc/init.d/postfix stop
+ server# /etc/init.d/leap-mx stop
+
+ workstation$ leap ssh &lt;webapp-node&gt;
+ server# /etc/init.d/nickserver stop
+</code></pre>
+
+<p> Alternately, you can create a temporary firewall rule to block access (run on couchdb server):</p>
+
+<pre><code class="`"> server# iptables -A INPUT -p tcp --dport 5984 --jump REJECT
+</code></pre></li>
+<li><p>remove orphaned databases and do a backup of all remaining, active databases. This can take some time and will place several hundred megabytes of data into /var/backups/couchdb. The size and time depends on how many users there are on your system. For example, 15k users took approximately 25 minutes and 308M of space:</p>
+
+<pre><code class="`"> workstation$ leap ssh &lt;couchdb-node&gt;
+ server# cd /srv/leap/couchdb/scripts
+ server# ./cleanup-user-dbs
+ server# time ./couchdb_dumpall.sh
+</code></pre></li>
+<li><p>stop bigcouch:</p>
+
+<pre><code class="`"> server# /etc/init.d/bigcouch stop
+ server# pkill epmd
+</code></pre></li>
+<li><p>remove bigcouch:</p>
+
+<pre><code class="`"> server# apt-get remove bigcouch
+</code></pre></li>
+<li><p>configure your couch node to use plain couchdb instead of bigcouch, you can do this by editing nodes/<couch-node>.json, look for this section:</p>
+
+<pre><code class="`"> "couch": {
+ "mode": "plain"
+ }
+</code></pre>
+
+<p>change it, so it looks like this instead:</p>
+
+<pre><code class="``"> "couch": {
+ "mode": "plain",
+ "pwhash_alg": "pbkdf2"
+ }
+</code></pre></li>
+</ol>
+
+</p>
+
+<p><ol>
+<li><p>restore the backup, this will take approximately the same amount of time as the backup took above:</p>
+
+<pre><code class="`"> server# cd /srv/leap/couchdb/scripts
+ server# time ./couchdb_restoreall.sh
+</code></pre></li>
+<li><p>start services again that were stopped in the beginning:</p>
+
+<pre><code class="`"> workstation$ leap ssh soledad-nodes
+ server# /etc/init.d/soledad-server start
+
+ workstation$ leap ssh mx-node
+ server# /etc/init.d/postfix start
+ server# /etc/init.d/leap-mx start
+
+ workstation$ leap ssh webapp
+ server# /etc/init.d/nickserver start
+</code></pre>
+
+<p> Or, alternately, if you set up the firewall rule instead, now remove it:</p>
+
+<pre><code class="`"> server# iptables -D INPUT -p tcp --dport 5984 --jump REJECT
+</code></pre></li>
+</ol>
+
+</p>
+
+<p><ol>
+<li><p>check if everything is working, including running the test on your deployment machine:</p>
+
+<pre><code class="`"> workstation$ leap test
+</code></pre></li>
+<li><p>Remove old bigcouch data dir <code>/opt</code> after you double checked everything is in place</p></li>
+<li><p>Relax, enjoy a refreshing beverage.</p></li>
+</ol>
+
+</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/services/couchdb/index.html b/docs/en/services/couchdb/index.html
new file mode 100644
index 00000000..10043db6
--- /dev/null
+++ b/docs/en/services/couchdb/index.html
@@ -0,0 +1,328 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+couchdb - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../tutorials.html'>Tutorials</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../../services.html'>Services</a>
+</li>
+<li class='active level1'>
+<a class='' href='../couchdb.html'>couchdb</a>
+</li>
+<li class=' level1'>
+<a class='' href='../openvpn.html'>openvpn</a>
+</li>
+<li class=' level1'>
+<a class='' href='../monitor.html'>monitor</a>
+</li>
+<li class=' level1'>
+<a class='' href='../mx.html'>mx</a>
+</li>
+<li class=' level1'>
+<a class='' href='../soledad.html'>soledad</a>
+</li>
+<li class=' level1'>
+<a class='' href='../tor.html'>tor</a>
+</li>
+<li class=' level1'>
+<a class='' href='../webapp.html'>webapp</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>couchdb</h1>
+
+<div id='summary'>Data storage for all user data.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="index.html#topology">Topology</a>
+ </li>
+ <li>
+ <a href="index.html#configuration">Configuration</a>
+ <ol>
+ <li>
+ <a href="index.html#nighly-dumps">Nighly dumps</a>
+ </li>
+ <li>
+ <a href="index.html#plain-couchdb">Plain CouchDB</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#various-tasks">Various Tasks</a>
+ <ol>
+ <li>
+ <a href="index.html#re-enabling-blocked-account">Re-enabling blocked account</a>
+ </li>
+ <li>
+ <a href="index.html#how-to-find-out-which-userstore-belongs-to-which-identity">How to find out which userstore belongs to which identity?</a>
+ </li>
+ <li>
+ <a href="index.html#how-much-disk-space-is-used-by-a-userstore">How much disk space is used by a userstore</a>
+ </li>
+ <li>
+ <a href="index.html#migrating-from-bigcouch-to-plain-couchdb">Migrating from BigCouch to plain CouchDB</a>
+ </li>
+ </ol>
+ </li>
+</ol></div>
+
+<h2><a name="topology"></a>Topology</h2>
+
+<p>Required:</p>
+
+<ul>
+<li>Nodes with <code>couchdb</code> service must also have <code>soledad</code> service, if email is enabled.</li>
+</ul>
+
+
+<p>Suggested:</p>
+
+<ul>
+<li>Nodes with <code>couchdb</code> service communicate heavily with <code>webapp</code> and <code>mx</code>.</li>
+</ul>
+
+
+<p><code>couchdb</code> nodes do not need to be reachable from the public internet, although the <code>soledad</code> service does require this.</p>
+
+<h2><a name="configuration"></a>Configuration</h2>
+
+<h3><a name="nighly-dumps"></a>Nighly dumps</h3>
+
+<p>You can do a nightly couchdb data dump by adding this to your node config:</p>
+
+<pre><code>"couch": {
+ "backup": true
+}
+</code></pre>
+
+<p>Data will get dumped to <code>/var/backups/couchdb</code>.</p>
+
+<h3><a name="plain-couchdb"></a>Plain CouchDB</h3>
+
+<p>BigCouch is not supported on Platform version 0.8 and higher: only plain CouchDB is possible. For earlier versions, you must do this in order to use plain CouchDB:</p>
+
+<pre><code>"couch": {
+ "master": true,
+ "pwhash_alg": "pbkdf2"
+}
+</code></pre>
+
+<h2><a name="various-tasks"></a>Various Tasks</h2>
+
+<h3><a name="re-enabling-blocked-account"></a>Re-enabling blocked account</h3>
+
+<p>When a user account gets destroyed from the webapp, there&rsquo;s still a leftover doc in the identities db so other people can&rsquo;t claim that account without an admin&rsquo;s intervention. You can remove this username reservation through the webapp.</p>
+
+<p>However, here is how you could do it manually, if you wanted to:</p>
+
+<p>grep the identities db for the email address:</p>
+
+<pre><code>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
+</code></pre>
+
+<p>lookup &ldquo;id&rdquo; and &ldquo;rev&rdquo; to delete the doc:</p>
+
+<pre><code>curl -s --netrc-file /etc/couchdb/couchdb.netrc -X DELETE 'http://127.0.0.1:5984/identities/b25cf10f935b58088f0d547fca823265?rev=2-715a9beba597a2ab01851676f12c3e4a'
+</code></pre>
+
+<h3><a name="how-to-find-out-which-userstore-belongs-to-which-identity"></a>How to find out which userstore belongs to which identity?</h3>
+
+<pre><code>/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": ...
+</code></pre>
+
+<ul>
+<li>search for the &ldquo;user_id&rdquo; field</li>
+<li>in this example <a href="&#x6d;&#x61;&#x69;&#x6c;&#x74;&#x6f;&#58;&#x74;&#x65;&#115;&#116;&#117;&#x73;&#x65;&#x72;&#x40;&#101;&#x78;&#97;&#109;&#x70;&#108;&#101;&#46;&#111;&#114;&#x67;">&#116;&#x65;&#115;&#116;&#x75;&#x73;&#101;&#114;&#x40;&#x65;&#120;&#97;&#109;&#112;&#x6c;&#x65;&#x2e;&#111;&#114;&#103;</a> uses the database user-665e004870ee17aa4c94331ff3cd59eb</li>
+</ul>
+
+
+<h3><a name="how-much-disk-space-is-used-by-a-userstore"></a>How much disk space is used by a userstore</h3>
+
+<p>Beware that this returns the uncompacted disk size (see <a href="http://wiki.apache.org/couchdb/Compaction">http://wiki.apache.org/couchdb/Compaction</a>)</p>
+
+<pre><code>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
+</code></pre>
+
+<h3><a name="migrating-from-bigcouch-to-plain-couchdb"></a>Migrating from BigCouch to plain CouchDB</h3>
+
+<p><p>At the end of this process, you will have just <em>one</em> node with <code>services</code> property equal to <code>couchdb</code>. If you had a BigCouch cluster before, you will be removing all but one of those machines to consolidate them into one CouchDB machine.</p>
+
+<ol>
+<li><p>if you have multiple nodes with the <code>couchdb</code> service on them, pick one of them to be your CouchDB server, and remove the service from the others. If these machines were only doing BigCouch before, you can remove the nodes completely with <code>leap node rm &lt;nodename&gt;</code> and then you can decommission the servers</p></li>
+<li><p>put the webapp into <a href="../webapp.html#maintenance-mode">maintenance mode</a></p></li>
+<li><p>turn off daemons that access the database. For example:</p>
+
+<pre><code class="`"> workstation$ leap ssh &lt;each soledad-node&gt;
+ server# /etc/init.d/soledad-server stop
+
+ workstation$ leap ssh &lt;mx-node&gt;
+ server# /etc/init.d/postfix stop
+ server# /etc/init.d/leap-mx stop
+
+ workstation$ leap ssh &lt;webapp-node&gt;
+ server# /etc/init.d/nickserver stop
+</code></pre>
+
+<p> Alternately, you can create a temporary firewall rule to block access (run on couchdb server):</p>
+
+<pre><code class="`"> server# iptables -A INPUT -p tcp --dport 5984 --jump REJECT
+</code></pre></li>
+<li><p>remove orphaned databases and do a backup of all remaining, active databases. This can take some time and will place several hundred megabytes of data into /var/backups/couchdb. The size and time depends on how many users there are on your system. For example, 15k users took approximately 25 minutes and 308M of space:</p>
+
+<pre><code class="`"> workstation$ leap ssh &lt;couchdb-node&gt;
+ server# cd /srv/leap/couchdb/scripts
+ server# ./cleanup-user-dbs
+ server# time ./couchdb_dumpall.sh
+</code></pre></li>
+<li><p>stop bigcouch:</p>
+
+<pre><code class="`"> server# /etc/init.d/bigcouch stop
+ server# pkill epmd
+</code></pre></li>
+<li><p>remove bigcouch:</p>
+
+<pre><code class="`"> server# apt-get remove bigcouch
+</code></pre></li>
+<li><p>configure your couch node to use plain couchdb instead of bigcouch, you can do this by editing nodes/<couch-node>.json, look for this section:</p>
+
+<pre><code class="`"> "couch": {
+ "mode": "plain"
+ }
+</code></pre>
+
+<p>change it, so it looks like this instead:</p>
+
+<pre><code class="``"> "couch": {
+ "mode": "plain",
+ "pwhash_alg": "pbkdf2"
+ }
+</code></pre></li>
+</ol>
+
+</p>
+
+<p><ol>
+<li><p>restore the backup, this will take approximately the same amount of time as the backup took above:</p>
+
+<pre><code class="`"> server# cd /srv/leap/couchdb/scripts
+ server# time ./couchdb_restoreall.sh
+</code></pre></li>
+<li><p>start services again that were stopped in the beginning:</p>
+
+<pre><code class="`"> workstation$ leap ssh soledad-nodes
+ server# /etc/init.d/soledad-server start
+
+ workstation$ leap ssh mx-node
+ server# /etc/init.d/postfix start
+ server# /etc/init.d/leap-mx start
+
+ workstation$ leap ssh webapp
+ server# /etc/init.d/nickserver start
+</code></pre>
+
+<p> Or, alternately, if you set up the firewall rule instead, now remove it:</p>
+
+<pre><code class="`"> server# iptables -D INPUT -p tcp --dport 5984 --jump REJECT
+</code></pre></li>
+</ol>
+
+</p>
+
+<p><ol>
+<li><p>check if everything is working, including running the test on your deployment machine:</p>
+
+<pre><code class="`"> workstation$ leap test
+</code></pre></li>
+<li><p>Remove old bigcouch data dir <code>/opt</code> after you double checked everything is in place</p></li>
+<li><p>Relax, enjoy a refreshing beverage.</p></li>
+</ol>
+
+</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/services/index.html b/docs/en/services/index.html
new file mode 100644
index 00000000..6d5c68e1
--- /dev/null
+++ b/docs/en/services/index.html
@@ -0,0 +1,251 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Services - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class='active level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class=' level1'>
+<a class='' href='couchdb.html'>couchdb</a>
+</li>
+<li class=' level1'>
+<a class='' href='openvpn.html'>openvpn</a>
+</li>
+<li class=' level1'>
+<a class='' href='monitor.html'>monitor</a>
+</li>
+<li class=' level1'>
+<a class='' href='mx.html'>mx</a>
+</li>
+<li class=' level1'>
+<a class='' href='soledad.html'>soledad</a>
+</li>
+<li class=' level1'>
+<a class='' href='tor.html'>tor</a>
+</li>
+<li class=' level1'>
+<a class='' href='webapp.html'>webapp</a>
+</li>
+<li class=' level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Guide to node services</h1>
+
+<div id='summary'></div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="index.html#introduction">Introduction</a>
+ </li>
+ <li>
+ <a href="index.html#available-services">Available services</a>
+ </li>
+</ol></div>
+
+<h1><a name="introduction"></a>Introduction</h1>
+
+<p>Every node (server) must have one or more <code>services</code> defined that determines what role the node performs. For example:</p>
+
+<pre><code>workstation$ cat nodes/stallman.json
+{
+ "ip_address": "199.99.99.1",
+ "services": ["webapp", "tor"]
+}
+</code></pre>
+
+<p>Here are common questions to ask when adding a new node to your provider:</p>
+
+<ul>
+<li><strong>many or few?</strong> Some services benefit from having many nodes, while some services are best run on only one or two nodes.</li>
+<li><strong>required or optional?</strong> Some services are required, while others can be left out.</li>
+<li><strong>who does the node communicate with?</strong> Some services communicate very heavily with other particular services. Nodes running these services should be close together.</li>
+<li><strong>public or private network?</strong> Some services communicate with the public internet, while others only need to communicate with other nodes in the infrastructure.</li>
+</ul>
+
+
+<h1><a name="available-services"></a>Available services</h1>
+
+<table class="table table-striped">
+<tr>
+ <th>Service</th>
+ <th>VPN</th>
+ <th>Email</th>
+ <th>Notes</th>
+</tr>
+<tr>
+ <td>webapp</td>
+ <td><i class="fa fa-circle"></i></td>
+ <td><i class="fa fa-circle"></i></td>
+ <td>User control panel, provider API, and support system.</td>
+</tr>
+<tr>
+ <td>couchdb</td>
+ <td><i class="fa fa-circle"></i></td>
+ <td><i class="fa fa-circle"></i></td>
+ <td>Data storage for everything. Private node.</td>
+<td></td>
+</tr>
+<tr>
+ <td>soledad</td>
+ <td><i class="fa fa-circle-o"></i></td>
+ <td><i class="fa fa-circle"></i></td>
+ <td>User data synchronization daemon. Usually paired with <code>couchdb</code> nodes.</td>
+<td></td>
+</tr>
+<tr>
+ <td>mx</td>
+ <td><i class="fa fa-circle-o"></i></td>
+ <td><i class="fa fa-circle"></i></td>
+ <td>Incoming and outgoing MX servers.</td>
+</tr>
+<tr>
+ <td>openvpn</td>
+ <td><i class="fa fa-circle"></i></td>
+ <td><i class="fa fa-circle-o"></i></td>
+ <td>OpenVPN gateways.</td>
+</tr>
+<tr>
+ <td>monitor</td>
+ <td><i class="fa fa-dot-circle-o"></i></td>
+ <td><i class="fa fa-dot-circle-o"></i></td>
+ <td>Nagios monitoring. This service must be on the webapp node.</td>
+</tr>
+<tr>
+ <td>tor</td>
+ <td><i class="fa fa-dot-circle-o"></i></td>
+ <td><i class="fa fa-dot-circle-o"></i></td>
+ <td>Tor exit node.</td>
+</tr>
+</table>
+
+
+<p>Key: <i class="fa fa-circle"> Required</i>, <i class="fa fa-dot-circle-o"> Optional</i>, <i class="fa fa-circle-o"> Not Used</i></p>
+
+<p><div class=' page-summary'>
+<h2>
+<a href='couchdb.html'>couchdb</a>
+</h2>
+<div class='summary'>Data storage for all user data.</div>
+</div>
+<div class=' page-summary'>
+<h2>
+<a href='openvpn.html'>openvpn</a>
+</h2>
+<div class='summary'>OpenVPN egress gateways</div>
+</div>
+<div class=' page-summary'>
+<h2>
+<a href='monitor.html'>monitor</a>
+</h2>
+<div class='summary'>Nagios monitoring and continuous testing.</div>
+</div>
+<div class=' page-summary'>
+<h2>
+<a href='mx.html'>mx</a>
+</h2>
+<div class='summary'>Incoming and outgoing MX servers.</div>
+</div>
+<div class=' page-summary'>
+<h2>
+<a href='soledad.html'>soledad</a>
+</h2>
+<div class='summary'>User data synchronization daemon</div>
+</div>
+<div class=' page-summary'>
+<h2>
+<a href='tor.html'>tor</a>
+</h2>
+<div class='summary'>Tor exit node or hidden service</div>
+</div>
+<div class=' page-summary'>
+<h2>
+<a href='webapp.html'>webapp</a>
+</h2>
+<div class='summary'>leap_web user management application and provider API.</div>
+</div>
+</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/services/monitor.html b/docs/en/services/monitor.html
new file mode 100644
index 00000000..5ed2e2fc
--- /dev/null
+++ b/docs/en/services/monitor.html
@@ -0,0 +1,186 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+monitor - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class=' level1'>
+<a class='' href='couchdb.html'>couchdb</a>
+</li>
+<li class=' level1'>
+<a class='' href='openvpn.html'>openvpn</a>
+</li>
+<li class='active level1'>
+<a class='' href='monitor.html'>monitor</a>
+</li>
+<li class=' level1'>
+<a class='' href='mx.html'>mx</a>
+</li>
+<li class=' level1'>
+<a class='' href='soledad.html'>soledad</a>
+</li>
+<li class=' level1'>
+<a class='' href='tor.html'>tor</a>
+</li>
+<li class=' level1'>
+<a class='' href='webapp.html'>webapp</a>
+</li>
+<li class=' level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>monitor</h1>
+
+<div id='summary'>Nagios monitoring and continuous testing.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="monitor/index.html#topology">Topology</a>
+ </li>
+ <li>
+ <a href="monitor/index.html#configuration">Configuration</a>
+ </li>
+ <li>
+ <a href="monitor/index.html#access-nagios-web">Access nagios web</a>
+ </li>
+</ol></div>
+
+<p>The <code>monitor</code> node provides a nagios control panel that will give you a view into the health and status of all the servers and all the services. It will also spam you with alerts if something goes down.</p>
+
+<h2><a name="topology"></a>Topology</h2>
+
+<p>Currently, you can have zero or one <code>monitor</code> nodes defined. It is required that the monitor be on the webapp node. It was not designed to be run as a separate node service.</p>
+
+<h2><a name="configuration"></a>Configuration</h2>
+
+<ul>
+<li><code>nagios.environments</code>: By default, the monitor node will monitor all servers in all environments. You can <strong>optionally</strong> restrict the environments to the ones you specify.</li>
+</ul>
+
+
+<p>For example:</p>
+
+<pre><code>{
+ "nagios": {
+ "environments": ["unstable", "production"]
+ }
+}
+</code></pre>
+
+<h2><a name="access-nagios-web"></a>Access nagios web</h2>
+
+<p>To open the nagios control panel:</p>
+
+<pre><code>workstation$ leap open monitor
+</code></pre>
+
+<p>This will open a web browser window with the appropriate URL, including the nagios username and password.</p>
+
+<p>If the URL does not open because of HSTS or DNS problems, pass the <code>--ip</code> option to <code>leap</code>.</p>
+
+<p>If you are using an older version of <code>leap</code> command that doesn&rsquo;t include <code>leap open</code>, you can determine the nagio parameters manually:</p>
+
+<p>Step 1. find the domain:</p>
+
+<pre><code>workstation$ export DOMAIN=$(leap ls --print webapp.domain monitor | grep . | cut -f3 -d' ')
+</code></pre>
+
+<p>Step 2. find the username:</p>
+
+<pre><code>workstation$ export USERNAME="nagiosadmin"
+</code></pre>
+
+<p>Step 3. find the password:</p>
+
+<pre><code>workstation$ export PASSWORD=$(grep nagios_admin_password secrets.json | cut -f4 -d\")
+</code></pre>
+
+<p>Step 4. put it all together:</p>
+
+<pre><code>workstation$ sensible-browser "https://$USERNAME:$PASSWORD@$DOMAIN/nagios3"
+</code></pre>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/services/monitor/index.html b/docs/en/services/monitor/index.html
new file mode 100644
index 00000000..f6a16cdf
--- /dev/null
+++ b/docs/en/services/monitor/index.html
@@ -0,0 +1,186 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+monitor - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../tutorials.html'>Tutorials</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../../services.html'>Services</a>
+</li>
+<li class=' level1'>
+<a class='' href='../couchdb.html'>couchdb</a>
+</li>
+<li class=' level1'>
+<a class='' href='../openvpn.html'>openvpn</a>
+</li>
+<li class='active level1'>
+<a class='' href='../monitor.html'>monitor</a>
+</li>
+<li class=' level1'>
+<a class='' href='../mx.html'>mx</a>
+</li>
+<li class=' level1'>
+<a class='' href='../soledad.html'>soledad</a>
+</li>
+<li class=' level1'>
+<a class='' href='../tor.html'>tor</a>
+</li>
+<li class=' level1'>
+<a class='' href='../webapp.html'>webapp</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>monitor</h1>
+
+<div id='summary'>Nagios monitoring and continuous testing.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="index.html#topology">Topology</a>
+ </li>
+ <li>
+ <a href="index.html#configuration">Configuration</a>
+ </li>
+ <li>
+ <a href="index.html#access-nagios-web">Access nagios web</a>
+ </li>
+</ol></div>
+
+<p>The <code>monitor</code> node provides a nagios control panel that will give you a view into the health and status of all the servers and all the services. It will also spam you with alerts if something goes down.</p>
+
+<h2><a name="topology"></a>Topology</h2>
+
+<p>Currently, you can have zero or one <code>monitor</code> nodes defined. It is required that the monitor be on the webapp node. It was not designed to be run as a separate node service.</p>
+
+<h2><a name="configuration"></a>Configuration</h2>
+
+<ul>
+<li><code>nagios.environments</code>: By default, the monitor node will monitor all servers in all environments. You can <strong>optionally</strong> restrict the environments to the ones you specify.</li>
+</ul>
+
+
+<p>For example:</p>
+
+<pre><code>{
+ "nagios": {
+ "environments": ["unstable", "production"]
+ }
+}
+</code></pre>
+
+<h2><a name="access-nagios-web"></a>Access nagios web</h2>
+
+<p>To open the nagios control panel:</p>
+
+<pre><code>workstation$ leap open monitor
+</code></pre>
+
+<p>This will open a web browser window with the appropriate URL, including the nagios username and password.</p>
+
+<p>If the URL does not open because of HSTS or DNS problems, pass the <code>--ip</code> option to <code>leap</code>.</p>
+
+<p>If you are using an older version of <code>leap</code> command that doesn&rsquo;t include <code>leap open</code>, you can determine the nagio parameters manually:</p>
+
+<p>Step 1. find the domain:</p>
+
+<pre><code>workstation$ export DOMAIN=$(leap ls --print webapp.domain monitor | grep . | cut -f3 -d' ')
+</code></pre>
+
+<p>Step 2. find the username:</p>
+
+<pre><code>workstation$ export USERNAME="nagiosadmin"
+</code></pre>
+
+<p>Step 3. find the password:</p>
+
+<pre><code>workstation$ export PASSWORD=$(grep nagios_admin_password secrets.json | cut -f4 -d\")
+</code></pre>
+
+<p>Step 4. put it all together:</p>
+
+<pre><code>workstation$ sensible-browser "https://$USERNAME:$PASSWORD@$DOMAIN/nagios3"
+</code></pre>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/services/mx.html b/docs/en/services/mx.html
new file mode 100644
index 00000000..8e08cfe0
--- /dev/null
+++ b/docs/en/services/mx.html
@@ -0,0 +1,168 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+mx - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class=' level1'>
+<a class='' href='couchdb.html'>couchdb</a>
+</li>
+<li class=' level1'>
+<a class='' href='openvpn.html'>openvpn</a>
+</li>
+<li class=' level1'>
+<a class='' href='monitor.html'>monitor</a>
+</li>
+<li class='active level1'>
+<a class='' href='mx.html'>mx</a>
+</li>
+<li class=' level1'>
+<a class='' href='soledad.html'>soledad</a>
+</li>
+<li class=' level1'>
+<a class='' href='tor.html'>tor</a>
+</li>
+<li class=' level1'>
+<a class='' href='webapp.html'>webapp</a>
+</li>
+<li class=' level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>mx</h1>
+
+<div id='summary'>Incoming and outgoing MX servers.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="mx/index.html#topology">Topology</a>
+ </li>
+ <li>
+ <a href="mx/index.html#configuration">Configuration</a>
+ <ol>
+ <li>
+ <a href="mx/index.html#aliases">Aliases</a>
+ </li>
+ </ol>
+ </li>
+</ol></div>
+
+<h2><a name="topology"></a>Topology</h2>
+
+<p><code>mx</code> nodes communicate with the public internet, clients, and <code>couchdb</code> nodes.</p>
+
+<h2><a name="configuration"></a>Configuration</h2>
+
+<h3><a name="aliases"></a>Aliases</h3>
+
+<p>Using the <code>mx.aliases</code> property, you can specify your own hard-coded email aliases that precedence over the aliases in the user database. The <code>mx.aliases</code> property consists of a hash, where source address points to one or more destination addresses.</p>
+
+<p>For example:</p>
+
+<p><code>services/mx.json</code>:</p>
+
+<pre><code>"mx": {
+ "aliases": {
+ "rook": "crow",
+ "robin": "robin@bird.org",
+ "flock": ["junco@bird.org", "robin", "crow"],
+ "chickadee@avian.org": "chickadee@bird.org",
+ "flicker": ["flicker@bird.org", "flicker@deliver.local"]
+ }
+}
+</code></pre>
+
+<p>This example demonstrates several of the features with <code>mx.aliases</code>:</p>
+
+<ol>
+<li>alias lists: by specifying an array of destination addresses, as in the case of &ldquo;flock&rdquo;, the single email will get copied to each address.</li>
+<li>chained resolution: alias resolution will recursively continue until there are no more matching aliases. For example, &ldquo;flock&rdquo; is resolved to &ldquo;robin&rdquo;, which then gets resolved to &ldquo;<a href="&#x6d;&#x61;&#105;&#x6c;&#x74;&#111;&#58;&#114;&#111;&#x62;&#x69;&#110;&#x40;&#98;&#105;&#x72;&#100;&#x2e;&#111;&#114;&#103;">&#114;&#111;&#x62;&#105;&#x6e;&#x40;&#x62;&#x69;&#114;&#x64;&#46;&#x6f;&#x72;&#x67;</a>&rdquo;.</li>
+<li>virtual domains: by specifying the full domain, as in the case of &ldquo;<a href="&#109;&#x61;&#105;&#108;&#x74;&#x6f;&#x3a;&#x63;&#104;&#x69;&#x63;&#x6b;&#97;&#x64;&#101;&#101;&#x40;&#97;&#118;&#105;&#97;&#110;&#x2e;&#111;&#x72;&#x67;">&#x63;&#x68;&#x69;&#99;&#107;&#x61;&#x64;&#101;&#101;&#64;&#x61;&#x76;&#105;&#97;&#x6e;&#x2e;&#x6f;&#x72;&#103;</a>&rdquo;, the alias will work for any domain you want. Of course, the MX record for that domain must point to appropriate MX servers, but otherwise you don&rsquo;t need to do any additional configuration.</li>
+<li>local delivery: for testing purposes, it is often useful to copy all incoming mail for a particular address and send those copies to another address. You can do this by adding &ldquo;@deliver.local&rdquo; as one of the destination addresses. When &ldquo;@local.delivery&rdquo; is found, alias resolution stops and the mail is delivered to that username.</li>
+</ol>
+
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/services/mx/index.html b/docs/en/services/mx/index.html
new file mode 100644
index 00000000..6899e0cc
--- /dev/null
+++ b/docs/en/services/mx/index.html
@@ -0,0 +1,168 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+mx - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../tutorials.html'>Tutorials</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../../services.html'>Services</a>
+</li>
+<li class=' level1'>
+<a class='' href='../couchdb.html'>couchdb</a>
+</li>
+<li class=' level1'>
+<a class='' href='../openvpn.html'>openvpn</a>
+</li>
+<li class=' level1'>
+<a class='' href='../monitor.html'>monitor</a>
+</li>
+<li class='active level1'>
+<a class='' href='../mx.html'>mx</a>
+</li>
+<li class=' level1'>
+<a class='' href='../soledad.html'>soledad</a>
+</li>
+<li class=' level1'>
+<a class='' href='../tor.html'>tor</a>
+</li>
+<li class=' level1'>
+<a class='' href='../webapp.html'>webapp</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>mx</h1>
+
+<div id='summary'>Incoming and outgoing MX servers.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="index.html#topology">Topology</a>
+ </li>
+ <li>
+ <a href="index.html#configuration">Configuration</a>
+ <ol>
+ <li>
+ <a href="index.html#aliases">Aliases</a>
+ </li>
+ </ol>
+ </li>
+</ol></div>
+
+<h2><a name="topology"></a>Topology</h2>
+
+<p><code>mx</code> nodes communicate with the public internet, clients, and <code>couchdb</code> nodes.</p>
+
+<h2><a name="configuration"></a>Configuration</h2>
+
+<h3><a name="aliases"></a>Aliases</h3>
+
+<p>Using the <code>mx.aliases</code> property, you can specify your own hard-coded email aliases that precedence over the aliases in the user database. The <code>mx.aliases</code> property consists of a hash, where source address points to one or more destination addresses.</p>
+
+<p>For example:</p>
+
+<p><code>services/mx.json</code>:</p>
+
+<pre><code>"mx": {
+ "aliases": {
+ "rook": "crow",
+ "robin": "robin@bird.org",
+ "flock": ["junco@bird.org", "robin", "crow"],
+ "chickadee@avian.org": "chickadee@bird.org",
+ "flicker": ["flicker@bird.org", "flicker@deliver.local"]
+ }
+}
+</code></pre>
+
+<p>This example demonstrates several of the features with <code>mx.aliases</code>:</p>
+
+<ol>
+<li>alias lists: by specifying an array of destination addresses, as in the case of &ldquo;flock&rdquo;, the single email will get copied to each address.</li>
+<li>chained resolution: alias resolution will recursively continue until there are no more matching aliases. For example, &ldquo;flock&rdquo; is resolved to &ldquo;robin&rdquo;, which then gets resolved to &ldquo;<a href="&#109;&#x61;&#105;&#x6c;&#116;&#x6f;&#x3a;&#x72;&#x6f;&#x62;&#105;&#x6e;&#64;&#x62;&#105;&#x72;&#100;&#46;&#111;&#x72;&#x67;">&#114;&#111;&#98;&#x69;&#x6e;&#x40;&#98;&#x69;&#x72;&#x64;&#46;&#x6f;&#x72;&#x67;</a>&rdquo;.</li>
+<li>virtual domains: by specifying the full domain, as in the case of &ldquo;<a href="&#x6d;&#97;&#105;&#108;&#116;&#x6f;&#x3a;&#x63;&#x68;&#105;&#x63;&#x6b;&#97;&#x64;&#x65;&#101;&#x40;&#x61;&#118;&#x69;&#97;&#x6e;&#46;&#x6f;&#114;&#x67;">&#99;&#104;&#105;&#x63;&#x6b;&#97;&#100;&#x65;&#101;&#64;&#97;&#x76;&#x69;&#97;&#x6e;&#x2e;&#x6f;&#114;&#103;</a>&rdquo;, the alias will work for any domain you want. Of course, the MX record for that domain must point to appropriate MX servers, but otherwise you don&rsquo;t need to do any additional configuration.</li>
+<li>local delivery: for testing purposes, it is often useful to copy all incoming mail for a particular address and send those copies to another address. You can do this by adding &ldquo;@deliver.local&rdquo; as one of the destination addresses. When &ldquo;@local.delivery&rdquo; is found, alias resolution stops and the mail is delivered to that username.</li>
+</ol>
+
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/services/openvpn.html b/docs/en/services/openvpn.html
new file mode 100644
index 00000000..e5fe1128
--- /dev/null
+++ b/docs/en/services/openvpn.html
@@ -0,0 +1,178 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+openvpn - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class=' level1'>
+<a class='' href='couchdb.html'>couchdb</a>
+</li>
+<li class='active level1'>
+<a class='' href='openvpn.html'>openvpn</a>
+</li>
+<li class=' level1'>
+<a class='' href='monitor.html'>monitor</a>
+</li>
+<li class=' level1'>
+<a class='' href='mx.html'>mx</a>
+</li>
+<li class=' level1'>
+<a class='' href='soledad.html'>soledad</a>
+</li>
+<li class=' level1'>
+<a class='' href='tor.html'>tor</a>
+</li>
+<li class=' level1'>
+<a class='' href='webapp.html'>webapp</a>
+</li>
+<li class=' level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>openvpn</h1>
+
+<div id='summary'>OpenVPN egress gateways</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="openvpn/index.html#topology">Topology</a>
+ </li>
+ <li>
+ <a href="openvpn/index.html#configuration">Configuration</a>
+ </li>
+</ol></div>
+
+<h2><a name="topology"></a>Topology</h2>
+
+<p>Currently, <code>openvpn</code> service should not be combined with other services on the same node.</p>
+
+<p>Unlike most of the other node types, the <code>openvpn</code> nodes do not need access to the database and does not ever communicate with any other nodes (except for the <code>monitor</code> node, if used). So, <code>openvpn</code> nodes can be placed anywhere without regard to the other nodes.</p>
+
+<h2><a name="configuration"></a>Configuration</h2>
+
+<p><em>Essential configuration</em></p>
+
+<ul>
+<li><code>openvpn.gateway_address</code>: The address that OpenVPN daemon is bound to and that VPN clients connect to.</li>
+<li><code>ip_address</code>: The main IP of the server, and the egress address for outgoing traffic.</li>
+</ul>
+
+
+<p>For example:</p>
+
+<pre><code>{
+ "ip_address": "1.1.1.1",
+ "openvpn": {
+ "gateway_address": "2.2.2.2"
+ }
+}
+</code></pre>
+
+<p>In this example, VPN clients will connect to 2.2.2.2, but their traffic will appear to come from 1.1.1.1.</p>
+
+<p>Why are two IP addresses needed? Without this, traffic between two VPN users on the same gateway will not get encrypted. This is because the VPN on every client must be configured to allow cleartext traffic for the IP address that is the VPN gateway.</p>
+
+<p><em>Optional configuration</em></p>
+
+<p>Here is the default configuration:</p>
+
+<pre><code>"openvpn": {
+ "configuration": {
+ "auth": "SHA1",
+ "cipher": "AES-128-CBC",
+ "fragment": 1400,
+ "keepalive": "10 30",
+ "tls-cipher": "DHE-RSA-AES128-SHA",
+ "tun-ipv6": true
+ },
+ "ports": ["80", "443", "53", "1194"],
+ "protocols": ["tcp", "udp"]
+}
+</code></pre>
+
+<p>You may want to change the ports so that only 443 or 80 are used. It is probably best to not modify the <code>openvpn.configuration</code> options for now.</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/services/openvpn/index.html b/docs/en/services/openvpn/index.html
new file mode 100644
index 00000000..4a9dc993
--- /dev/null
+++ b/docs/en/services/openvpn/index.html
@@ -0,0 +1,178 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+openvpn - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../tutorials.html'>Tutorials</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../../services.html'>Services</a>
+</li>
+<li class=' level1'>
+<a class='' href='../couchdb.html'>couchdb</a>
+</li>
+<li class='active level1'>
+<a class='' href='../openvpn.html'>openvpn</a>
+</li>
+<li class=' level1'>
+<a class='' href='../monitor.html'>monitor</a>
+</li>
+<li class=' level1'>
+<a class='' href='../mx.html'>mx</a>
+</li>
+<li class=' level1'>
+<a class='' href='../soledad.html'>soledad</a>
+</li>
+<li class=' level1'>
+<a class='' href='../tor.html'>tor</a>
+</li>
+<li class=' level1'>
+<a class='' href='../webapp.html'>webapp</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>openvpn</h1>
+
+<div id='summary'>OpenVPN egress gateways</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="index.html#topology">Topology</a>
+ </li>
+ <li>
+ <a href="index.html#configuration">Configuration</a>
+ </li>
+</ol></div>
+
+<h2><a name="topology"></a>Topology</h2>
+
+<p>Currently, <code>openvpn</code> service should not be combined with other services on the same node.</p>
+
+<p>Unlike most of the other node types, the <code>openvpn</code> nodes do not need access to the database and does not ever communicate with any other nodes (except for the <code>monitor</code> node, if used). So, <code>openvpn</code> nodes can be placed anywhere without regard to the other nodes.</p>
+
+<h2><a name="configuration"></a>Configuration</h2>
+
+<p><em>Essential configuration</em></p>
+
+<ul>
+<li><code>openvpn.gateway_address</code>: The address that OpenVPN daemon is bound to and that VPN clients connect to.</li>
+<li><code>ip_address</code>: The main IP of the server, and the egress address for outgoing traffic.</li>
+</ul>
+
+
+<p>For example:</p>
+
+<pre><code>{
+ "ip_address": "1.1.1.1",
+ "openvpn": {
+ "gateway_address": "2.2.2.2"
+ }
+}
+</code></pre>
+
+<p>In this example, VPN clients will connect to 2.2.2.2, but their traffic will appear to come from 1.1.1.1.</p>
+
+<p>Why are two IP addresses needed? Without this, traffic between two VPN users on the same gateway will not get encrypted. This is because the VPN on every client must be configured to allow cleartext traffic for the IP address that is the VPN gateway.</p>
+
+<p><em>Optional configuration</em></p>
+
+<p>Here is the default configuration:</p>
+
+<pre><code>"openvpn": {
+ "configuration": {
+ "auth": "SHA1",
+ "cipher": "AES-128-CBC",
+ "fragment": 1400,
+ "keepalive": "10 30",
+ "tls-cipher": "DHE-RSA-AES128-SHA",
+ "tun-ipv6": true
+ },
+ "ports": ["80", "443", "53", "1194"],
+ "protocols": ["tcp", "udp"]
+}
+</code></pre>
+
+<p>You may want to change the ports so that only 443 or 80 are used. It is probably best to not modify the <code>openvpn.configuration</code> options for now.</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/services/soledad.html b/docs/en/services/soledad.html
new file mode 100644
index 00000000..be372401
--- /dev/null
+++ b/docs/en/services/soledad.html
@@ -0,0 +1,136 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+soledad - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class=' level1'>
+<a class='' href='couchdb.html'>couchdb</a>
+</li>
+<li class=' level1'>
+<a class='' href='openvpn.html'>openvpn</a>
+</li>
+<li class=' level1'>
+<a class='' href='monitor.html'>monitor</a>
+</li>
+<li class=' level1'>
+<a class='' href='mx.html'>mx</a>
+</li>
+<li class='active level1'>
+<a class='' href='soledad.html'>soledad</a>
+</li>
+<li class=' level1'>
+<a class='' href='tor.html'>tor</a>
+</li>
+<li class=' level1'>
+<a class='' href='webapp.html'>webapp</a>
+</li>
+<li class=' level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>soledad</h1>
+
+<div id='summary'>User data synchronization daemon</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="soledad/index.html#topology">Topology</a>
+ </li>
+ <li>
+ <a href="soledad/index.html#configuration">Configuration</a>
+ </li>
+</ol></div>
+
+<h2><a name="topology"></a>Topology</h2>
+
+<p>Currently, the platform is designed for <code>soledad</code> and <code>couchdb</code> services to be combined (e.g. every <code>soledad</code> node should also be a <code>couchdb</code> node). <code>soledad</code> nodes might work in isolation, but this is not tested.</p>
+
+<h2><a name="configuration"></a>Configuration</h2>
+
+<p>There are no options to configure for <code>soledad</code> nodes.</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/services/soledad/index.html b/docs/en/services/soledad/index.html
new file mode 100644
index 00000000..33e72046
--- /dev/null
+++ b/docs/en/services/soledad/index.html
@@ -0,0 +1,136 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+soledad - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../tutorials.html'>Tutorials</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../../services.html'>Services</a>
+</li>
+<li class=' level1'>
+<a class='' href='../couchdb.html'>couchdb</a>
+</li>
+<li class=' level1'>
+<a class='' href='../openvpn.html'>openvpn</a>
+</li>
+<li class=' level1'>
+<a class='' href='../monitor.html'>monitor</a>
+</li>
+<li class=' level1'>
+<a class='' href='../mx.html'>mx</a>
+</li>
+<li class='active level1'>
+<a class='' href='../soledad.html'>soledad</a>
+</li>
+<li class=' level1'>
+<a class='' href='../tor.html'>tor</a>
+</li>
+<li class=' level1'>
+<a class='' href='../webapp.html'>webapp</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>soledad</h1>
+
+<div id='summary'>User data synchronization daemon</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="index.html#topology">Topology</a>
+ </li>
+ <li>
+ <a href="index.html#configuration">Configuration</a>
+ </li>
+</ol></div>
+
+<h2><a name="topology"></a>Topology</h2>
+
+<p>Currently, the platform is designed for <code>soledad</code> and <code>couchdb</code> services to be combined (e.g. every <code>soledad</code> node should also be a <code>couchdb</code> node). <code>soledad</code> nodes might work in isolation, but this is not tested.</p>
+
+<h2><a name="configuration"></a>Configuration</h2>
+
+<p>There are no options to configure for <code>soledad</code> nodes.</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/services/tor.html b/docs/en/services/tor.html
new file mode 100644
index 00000000..f649c086
--- /dev/null
+++ b/docs/en/services/tor.html
@@ -0,0 +1,161 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+tor - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class=' level1'>
+<a class='' href='couchdb.html'>couchdb</a>
+</li>
+<li class=' level1'>
+<a class='' href='openvpn.html'>openvpn</a>
+</li>
+<li class=' level1'>
+<a class='' href='monitor.html'>monitor</a>
+</li>
+<li class=' level1'>
+<a class='' href='mx.html'>mx</a>
+</li>
+<li class=' level1'>
+<a class='' href='soledad.html'>soledad</a>
+</li>
+<li class='active level1'>
+<a class='' href='tor.html'>tor</a>
+</li>
+<li class=' level1'>
+<a class='' href='webapp.html'>webapp</a>
+</li>
+<li class=' level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>tor</h1>
+
+<div id='summary'>Tor exit node or hidden service</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="tor/index.html#topology">Topology</a>
+ </li>
+ <li>
+ <a href="tor/index.html#configuration">Configuration</a>
+ </li>
+</ol></div>
+
+<h2><a name="topology"></a>Topology</h2>
+
+<p>Nodes with <code>tor</code> service will run a Tor exit or hidden service, depending on what other service it is paired with:</p>
+
+<ul>
+<li><code>tor</code> + <code>openvpn</code>: when combined with <code>openvpn</code> nodes, <code>tor</code> will create a Tor exit node to provide extra cover traffic for the VPN. This can be especially useful if there are VPN gateways without much traffic.</li>
+<li><code>tor</code> + <code>webapp</code>: when combined with a <code>webapp</code> node, the <code>tor</code> service will make the webapp and the API available via .onion hidden service.</li>
+<li><code>tor</code> stand alone: a regular Tor exit node.</li>
+</ul>
+
+
+<p>If activated, you can list the hidden service .onion addresses this way:</p>
+
+<p> leap ls &ndash;print tor.hidden_service.address tor</p>
+
+<p>Then just add &lsquo;.onion&rsquo; to the end of the printed addresses.</p>
+
+<h2><a name="configuration"></a>Configuration</h2>
+
+<ul>
+<li><code>tor.bandwidth_rate</code>: the max bandwidth allocated to Tor, in KB per second, when used as an exit node.</li>
+</ul>
+
+
+<p>For example:</p>
+
+<pre><code>{
+ "tor": {
+ "bandwidth_rate": 6550
+ }
+}
+</code></pre>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/services/tor/index.html b/docs/en/services/tor/index.html
new file mode 100644
index 00000000..8fecf152
--- /dev/null
+++ b/docs/en/services/tor/index.html
@@ -0,0 +1,161 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+tor - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../tutorials.html'>Tutorials</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../../services.html'>Services</a>
+</li>
+<li class=' level1'>
+<a class='' href='../couchdb.html'>couchdb</a>
+</li>
+<li class=' level1'>
+<a class='' href='../openvpn.html'>openvpn</a>
+</li>
+<li class=' level1'>
+<a class='' href='../monitor.html'>monitor</a>
+</li>
+<li class=' level1'>
+<a class='' href='../mx.html'>mx</a>
+</li>
+<li class=' level1'>
+<a class='' href='../soledad.html'>soledad</a>
+</li>
+<li class='active level1'>
+<a class='' href='../tor.html'>tor</a>
+</li>
+<li class=' level1'>
+<a class='' href='../webapp.html'>webapp</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>tor</h1>
+
+<div id='summary'>Tor exit node or hidden service</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="index.html#topology">Topology</a>
+ </li>
+ <li>
+ <a href="index.html#configuration">Configuration</a>
+ </li>
+</ol></div>
+
+<h2><a name="topology"></a>Topology</h2>
+
+<p>Nodes with <code>tor</code> service will run a Tor exit or hidden service, depending on what other service it is paired with:</p>
+
+<ul>
+<li><code>tor</code> + <code>openvpn</code>: when combined with <code>openvpn</code> nodes, <code>tor</code> will create a Tor exit node to provide extra cover traffic for the VPN. This can be especially useful if there are VPN gateways without much traffic.</li>
+<li><code>tor</code> + <code>webapp</code>: when combined with a <code>webapp</code> node, the <code>tor</code> service will make the webapp and the API available via .onion hidden service.</li>
+<li><code>tor</code> stand alone: a regular Tor exit node.</li>
+</ul>
+
+
+<p>If activated, you can list the hidden service .onion addresses this way:</p>
+
+<p> leap ls &ndash;print tor.hidden_service.address tor</p>
+
+<p>Then just add &lsquo;.onion&rsquo; to the end of the printed addresses.</p>
+
+<h2><a name="configuration"></a>Configuration</h2>
+
+<ul>
+<li><code>tor.bandwidth_rate</code>: the max bandwidth allocated to Tor, in KB per second, when used as an exit node.</li>
+</ul>
+
+
+<p>For example:</p>
+
+<pre><code>{
+ "tor": {
+ "bandwidth_rate": 6550
+ }
+}
+</code></pre>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/services/webapp.html b/docs/en/services/webapp.html
new file mode 100644
index 00000000..6c853c22
--- /dev/null
+++ b/docs/en/services/webapp.html
@@ -0,0 +1,479 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+webapp - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class=' level1'>
+<a class='' href='couchdb.html'>couchdb</a>
+</li>
+<li class=' level1'>
+<a class='' href='openvpn.html'>openvpn</a>
+</li>
+<li class=' level1'>
+<a class='' href='monitor.html'>monitor</a>
+</li>
+<li class=' level1'>
+<a class='' href='mx.html'>mx</a>
+</li>
+<li class=' level1'>
+<a class='' href='soledad.html'>soledad</a>
+</li>
+<li class=' level1'>
+<a class='' href='tor.html'>tor</a>
+</li>
+<li class='active level1'>
+<a class='' href='webapp.html'>webapp</a>
+</li>
+<li class=' level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>webapp</h1>
+
+<div id='summary'>leap_web user management application and provider API.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="webapp/index.html#introduction">Introduction</a>
+ </li>
+ <li>
+ <a href="webapp/index.html#topology">Topology</a>
+ </li>
+ <li>
+ <a href="webapp/index.html#configuration">Configuration</a>
+ </li>
+ <li>
+ <a href="webapp/index.html#invite-codes">Invite codes</a>
+ </li>
+ <li>
+ <a href="webapp/index.html#customization">Customization</a>
+ </li>
+ <li>
+ <a href="webapp/index.html#customization-tutorial">Customization tutorial</a>
+ <ol>
+ <li>
+ <a href="webapp/index.html#override-translations">Override translations</a>
+ </li>
+ <li>
+ <a href="webapp/index.html#override-static-pages">Override static pages</a>
+ </li>
+ <li>
+ <a href="webapp/index.html#add-a-custom-header">Add a custom header</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="webapp/index.html#custom-fork">Custom Fork</a>
+ </li>
+ <li>
+ <a href="webapp/index.html#maintenance-mode">Maintenance mode</a>
+ </li>
+ <li>
+ <a href="webapp/index.html#known-problems">Known problems</a>
+ </li>
+</ol></div>
+
+<h2><a name="introduction"></a>Introduction</h2>
+
+<p>The service <code>webapp</code> will install the web application <a href="https://leap.se/git/leap_web.git">leap_web</a>. It has performs the following functions:</p>
+
+<ul>
+<li>REST API for user registration and authentication via the Bitmask client.</li>
+<li>Admin interface to manage users.</li>
+<li>Client certificate distribution and renewal.</li>
+<li>User support help tickets.</li>
+</ul>
+
+
+<p>Coming soon:</p>
+
+<ul>
+<li>Billing.</li>
+<li>Customizable and localized user documentation.</li>
+</ul>
+
+
+<p>The leap_web application is written in Ruby on Rails 3, using CouchDB as the backend data store.</p>
+
+<h2><a name="topology"></a>Topology</h2>
+
+<p>Currently, the platform only supports a single <code>webapp</code> node, although we hope to change this in the future.</p>
+
+<ul>
+<li><code>webapp</code> nodes communicate heavily with <code>couchdb</code> nodes, but the two can be on separate servers.</li>
+<li>The <code>monitor</code> service, if enabled, must be on the same node as <code>webapp</code>.</li>
+</ul>
+
+
+<h2><a name="configuration"></a>Configuration</h2>
+
+<p>Essential options:</p>
+
+<ul>
+<li><code>webapp.admin</code>: An array of usernames that will be blessed with administrative permissions. These admins can delete users, answer help tickets, and so on. These usernames are for users that have registered through the webapp or through the Bitmask client application, NOT the sysadmin usernames lists in the provider directory <code>users</code>.</li>
+</ul>
+
+
+<p>Other options:</p>
+
+<ul>
+<li><code>webapp.engines</code>: A list of the engines you want enabled in leap_web. Currently, only &ldquo;support&rdquo; is available, and it is enabled by default.</li>
+<li><code>webapp.invite_required</code>: If true, registration requires an invite code. Default is <code>false</code>.</li>
+</ul>
+
+
+<p>For example, <code>services/webapp.json</code>:</p>
+
+<pre><code>{
+ "webapp": {
+ "admins": ["joehill", "ali", "mack_the_turtle"]
+ }
+}
+</code></pre>
+
+<p>By putting this in <code>services/webapp.json</code>, all the <code>webapp</code> nodes will inherit the same admin list.</p>
+
+<p>There are many options in <code>provider.json</code> that also control how the webapp behaves. See <a href="../guide/provider-configuration.html">Provider Configuration</a> for details.</p>
+
+<h2><a name="invite-codes"></a>Invite codes</h2>
+
+<p>Enabling the invite code functionality will require new users to provide a valid invite code while signing up for a new account. This is turned off by default, allowing all new users to create an account.</p>
+
+<p>Set the <code>invite_code</code> option to <code>true</code> in <code>services/webapp.json</code>:</p>
+
+<pre><code>{
+ "webapp": {
+ "invite_required": true
+ }
+}
+</code></pre>
+
+<p>This only works with LEAP platform 0.8 or higher.</p>
+
+<p>Run <code>leap deploy</code> to enable the option.</p>
+
+<p>You can then generate invite codes by logging into the web application with an admin user.</p>
+
+<p>Alternately, you can also generate invite codes with the command line:</p>
+
+<pre><code>workstation$ leap ssh bumblebee
+bumblebee# cd /srv/leap/webapp/
+bumblebee# sudo -u leap-webapp RAILS_ENV=production bundle exec rake "generate_invites[NUM,USES]"
+</code></pre>
+
+<p>Where <code>bumblebee</code> should be replaced with the name of your webapp node.</p>
+
+<p>The <strong>NUM</strong> specifies the amount of codes to generate. The <strong>USES</strong> parameter is optional: By default, all new invite codes can be used once and will then become invalid. If you provide another value for <strong>USES</strong>, you can set a different amount of maximum uses for the codes you generate.</p>
+
+<h2><a name="customization"></a>Customization</h2>
+
+<p>The provider directory <code>files/webapp</code> can be used to customize the appearance of the webapp. All the files in this directory will get sync'ed to the <code>/srv/leap/webapp/config/customization</code> directory of the deployed webapp node.</p>
+
+<p>Files in the <code>files/webapp</code> can override view files, locales, and stylesheets in the leap_web app:</p>
+
+<p>For example:</p>
+
+<pre><code>stylesheets/ -- override files in Rails.root/app/assets/stylesheets
+ tail.scss -- included before all others
+ head.scss -- included after all others
+
+public/ -- overrides files in Rails.root/public
+ favicon.ico -- custom favicon
+ img/ -- customary directory to put images in
+
+views/ -- overrides files Rails.root/app/views
+ home/
+ index.html.haml -- this file is what shows up on
+ the home page
+ pages/
+ privacy-policy.en.md -- this file will override
+ the default privacy policy
+ terms-of-service.en.md -- this file will override
+ the default TOS.
+
+locales/ -- overrides files in Rails.root/config/locales
+ en.yml -- overrides for English
+ de.yml -- overrides for German
+ and so on...
+</code></pre>
+
+<p>To interactively develop your customizations before you deploy them, you have two options:</p>
+
+<ol>
+<li>Edit a <code>webapp</code> node. This approach involves directly modifying the contents of the directory <code>/srv/leap/webapp/config/customization</code> on a deployed <code>webapp</code> node. This can, and probably should be, a &ldquo;local&rdquo; node. When doing this, you may need to restart leap_web in order for changes to take effect (<code>touch /srv/leap/webapp/tmp/restart.txt</code>).</li>
+<li>Alternately, you can install leap_web to run on your computer and edit files in <code>config/customization</code> locally. This approach does not require a provider or a <code>webapp</code> node. For more information, see the <a href="https://github.com/leapcode/leap_web">leap_web README</a>.</li>
+</ol>
+
+
+<p>NOTE: If you add a <code>tails.scss</code> or <code>head.scss</code> file, then you usually need to run <code>rake tmp:clear</code> and restart rails in order for the new stylesheet to get recognized. You should only need to do this once.</p>
+
+<p>Once you have what you want, then copy these files to the local provider directory <code>files/webapp</code> so that they will be installed each time you deploy.</p>
+
+<h2><a name="customization-tutorial"></a>Customization tutorial</h2>
+
+<p>This mini-tutorial will walk you through creating a custom &ldquo;branding&rdquo; of the leap_web application. We will be creating a provider called &ldquo;Prehistoric Computer.&rdquo;</p>
+
+<p>Here are the files we are going to create:</p>
+
+<pre><code>leap_web/config/customization
+├── locales
+│   ├── en.yml
+│   └── es.yml
+├── public
+│   ├── favicon.ico
+│   └── img
+│   └── masthead.png
+├── stylesheets
+│   └── tail.scss
+└── views
+ └── pages
+ ├── privacy-policy.en.md
+ └── privacy-policy.es.md
+</code></pre>
+
+<p>All these files are available in the source code in the <a href="https://github.com/leapcode/leap_web/tree/develop/config/customization.example">customization.example</a> directory.</p>
+
+<p>Remember, these files may live different places:</p>
+
+<ul>
+<li><code>user@localmachine$ leap_web/config/customization</code>: This will be the path if you have checked out a local copy of leap_web.git and are running <code>rails server</code> locally in order to test your customizations.</li>
+<li><code>user@localmachine$ PROVIDER/files/webapp</code>: This is the local provider directory where the files should be put so that they get correctly deployed to webapp nodes.</li>
+<li><code>root@webappnode# /srv/leap/webapp/config/customization</code>: This is where the files in the local provider directory <code>PROVIDER/files/webapp</code> get copied to after a <code>leap deploy</code> to a live webapp nodes.</li>
+</ul>
+
+
+<h3><a name="override-translations"></a>Override translations</h3>
+
+<p>You can add additional locale files in order to change the text used in the existing application and to add translations for string that you added to the application.</p>
+
+<p>In this example, we will be altering the default text for the &ldquo;login_info&rdquo; string. In <code>config/locales/en/home.en.yml</code> there is this entry:</p>
+
+<pre><code>en:
+ login_info: "Log in to change your account settings, create support tickets, and manage payments."
+</code></pre>
+
+<p>We are going to override this with some custom text in English and Spanish:</p>
+
+<p><code>leap_web/config/customization/locale/en.yml</code>:</p>
+
+<pre><code>en:
+ login_info: Authenticate to change your "Prehistoric Computer" settings.
+</code></pre>
+
+<p><code>leap_web/config/customization/locale/es.yml</code>:</p>
+
+<pre><code>es:
+ login_info: Autenticar a cambiar la configuración de "Computer Prehistoria."
+</code></pre>
+
+<p>Now, the home page of leap_web will use these new strings instead of the default. Remember that you must restart rails in order for new locale files to take effect.</p>
+
+<h3><a name="override-static-pages"></a>Override static pages</h3>
+
+<p>You can also override any of the static files included with leap_web, such as the privacy policy or terms of service.</p>
+
+<p>Here is how we would create a custom privacy policy in English and Spanish:</p>
+
+<p><code>leap_web/config/customization/views/pages/privacy-policy.en.md</code>:</p>
+
+<pre><code># Custom Privacy Policy
+This is our privacy policy.
+</code></pre>
+
+<p><code>leap_web/config/customization/views/pages/privacy-policy.es.md</code>:</p>
+
+<pre><code># Custom Política de Privacidad
+Esta es nuestra política de privacidad.
+</code></pre>
+
+<h3><a name="add-a-custom-header"></a>Add a custom header</h3>
+
+<p>Now we will add a custom header to every page. First, we add the images:</p>
+
+<pre><code>leap_web/config/customization
+ ├── public
+ ├── favicon.ico
+ └── img
+ └── masthead.png
+</code></pre>
+
+<p>You can create your own, or use the example files in <a href="https://github.com/leapcode/leap_web/tree/develop/config/customization.example">https://github.com/leapcode/leap_web/tree/develop/config/customization.example</a></p>
+
+<p>Now, we add some custom CSS so that we can style the masthead:</p>
+
+<p><code>leap_web/config/customization/stylesheets/tail.scss</code></p>
+
+<pre><code>$custom-color: #66bbaa;
+
+a {
+ color: $custom-color;
+}
+
+//
+// MASTHEAD
+//
+
+#masthead {
+ background-color: $custom-color;
+ border-bottom: none;
+
+ // make the masthead clickable by replacing the
+ // site name link with the masthead image:
+ .title {
+ padding: 0px;
+ .sitename a {
+ display: block;
+ background: url(/img/masthead.png) 0 0 no-repeat;
+ font-size: 0px;
+ height: 100px;
+ background-size: auto 100px;
+ }
+ }
+}
+
+// make the home page masthead slightly larger
+body.home #masthead {
+ .sitename a {
+ height: 150px;
+ background-size: auto 150px;
+ }
+}
+
+//
+// FOOTER
+//
+
+#footer .links {
+ background-color: $custom-color;
+}
+</code></pre>
+
+<p>NOTE: If you add a <code>tails.scss</code> or <code>head.scss</code> file, then you usually need to run <code>rake tmp:clear</code> and restart rails in order for the new stylesheet to get recognized. You should only need to do this once.</p>
+
+<h2><a name="custom-fork"></a>Custom Fork</h2>
+
+<p>Sometimes it is easier to maintain your own fork of the leap_web app. You can keep your customizations in that fork instead of in the provider <code>files/webapp</code> directory. Or, perhaps you want to add an engine to the application that modifies the app&rsquo;s behavior.</p>
+
+<p>To deploy your own leap_web, modify the provider file <code>common.json</code>:</p>
+
+<pre><code>{
+ "sources": {
+ "webapp": {
+ "revision": "origin/develop",
+ "source": "https://github.com/leapcode/leap_web",
+ "type": "git"
+ }
+ }
+}
+</code></pre>
+
+<p>To target only particular environment, modify instead <code>common.ENV.json</code>, where ENV is the name of the environment.</p>
+
+<p>See <a href="https://github.com/leapcode/leap_web/blob/develop/doc/DEVELOP.md">https://github.com/leapcode/leap_web/blob/develop/doc/DEVELOP.md</a> for notes on getting started hacking on leap_web.</p>
+
+<h2><a name="maintenance-mode"></a>Maintenance mode</h2>
+
+<p>You can put the webapp into maintenance mode by simply dropping a html file to <code>/srv/leap/webapp/public/system/maintenance.html</code>. For example:</p>
+
+<pre><code>workstation$ leap ssh webappnode
+server# echo "Temporarily down for maintenance. We will be back soon." &gt; /srv/leap/webapp/public/system/maintenance.html
+</code></pre>
+
+<h2><a name="known-problems"></a>Known problems</h2>
+
+<ul>
+<li>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.</li>
+<li>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.</li>
+</ul>
+
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/services/webapp/index.html b/docs/en/services/webapp/index.html
new file mode 100644
index 00000000..acdc098c
--- /dev/null
+++ b/docs/en/services/webapp/index.html
@@ -0,0 +1,479 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+webapp - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../tutorials.html'>Tutorials</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../../services.html'>Services</a>
+</li>
+<li class=' level1'>
+<a class='' href='../couchdb.html'>couchdb</a>
+</li>
+<li class=' level1'>
+<a class='' href='../openvpn.html'>openvpn</a>
+</li>
+<li class=' level1'>
+<a class='' href='../monitor.html'>monitor</a>
+</li>
+<li class=' level1'>
+<a class='' href='../mx.html'>mx</a>
+</li>
+<li class=' level1'>
+<a class='' href='../soledad.html'>soledad</a>
+</li>
+<li class=' level1'>
+<a class='' href='../tor.html'>tor</a>
+</li>
+<li class='active level1'>
+<a class='' href='../webapp.html'>webapp</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>webapp</h1>
+
+<div id='summary'>leap_web user management application and provider API.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="index.html#introduction">Introduction</a>
+ </li>
+ <li>
+ <a href="index.html#topology">Topology</a>
+ </li>
+ <li>
+ <a href="index.html#configuration">Configuration</a>
+ </li>
+ <li>
+ <a href="index.html#invite-codes">Invite codes</a>
+ </li>
+ <li>
+ <a href="index.html#customization">Customization</a>
+ </li>
+ <li>
+ <a href="index.html#customization-tutorial">Customization tutorial</a>
+ <ol>
+ <li>
+ <a href="index.html#override-translations">Override translations</a>
+ </li>
+ <li>
+ <a href="index.html#override-static-pages">Override static pages</a>
+ </li>
+ <li>
+ <a href="index.html#add-a-custom-header">Add a custom header</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#custom-fork">Custom Fork</a>
+ </li>
+ <li>
+ <a href="index.html#maintenance-mode">Maintenance mode</a>
+ </li>
+ <li>
+ <a href="index.html#known-problems">Known problems</a>
+ </li>
+</ol></div>
+
+<h2><a name="introduction"></a>Introduction</h2>
+
+<p>The service <code>webapp</code> will install the web application <a href="https://leap.se/git/leap_web.git">leap_web</a>. It has performs the following functions:</p>
+
+<ul>
+<li>REST API for user registration and authentication via the Bitmask client.</li>
+<li>Admin interface to manage users.</li>
+<li>Client certificate distribution and renewal.</li>
+<li>User support help tickets.</li>
+</ul>
+
+
+<p>Coming soon:</p>
+
+<ul>
+<li>Billing.</li>
+<li>Customizable and localized user documentation.</li>
+</ul>
+
+
+<p>The leap_web application is written in Ruby on Rails 3, using CouchDB as the backend data store.</p>
+
+<h2><a name="topology"></a>Topology</h2>
+
+<p>Currently, the platform only supports a single <code>webapp</code> node, although we hope to change this in the future.</p>
+
+<ul>
+<li><code>webapp</code> nodes communicate heavily with <code>couchdb</code> nodes, but the two can be on separate servers.</li>
+<li>The <code>monitor</code> service, if enabled, must be on the same node as <code>webapp</code>.</li>
+</ul>
+
+
+<h2><a name="configuration"></a>Configuration</h2>
+
+<p>Essential options:</p>
+
+<ul>
+<li><code>webapp.admin</code>: An array of usernames that will be blessed with administrative permissions. These admins can delete users, answer help tickets, and so on. These usernames are for users that have registered through the webapp or through the Bitmask client application, NOT the sysadmin usernames lists in the provider directory <code>users</code>.</li>
+</ul>
+
+
+<p>Other options:</p>
+
+<ul>
+<li><code>webapp.engines</code>: A list of the engines you want enabled in leap_web. Currently, only &ldquo;support&rdquo; is available, and it is enabled by default.</li>
+<li><code>webapp.invite_required</code>: If true, registration requires an invite code. Default is <code>false</code>.</li>
+</ul>
+
+
+<p>For example, <code>services/webapp.json</code>:</p>
+
+<pre><code>{
+ "webapp": {
+ "admins": ["joehill", "ali", "mack_the_turtle"]
+ }
+}
+</code></pre>
+
+<p>By putting this in <code>services/webapp.json</code>, all the <code>webapp</code> nodes will inherit the same admin list.</p>
+
+<p>There are many options in <code>provider.json</code> that also control how the webapp behaves. See <a href="../../guide/provider-configuration.html">Provider Configuration</a> for details.</p>
+
+<h2><a name="invite-codes"></a>Invite codes</h2>
+
+<p>Enabling the invite code functionality will require new users to provide a valid invite code while signing up for a new account. This is turned off by default, allowing all new users to create an account.</p>
+
+<p>Set the <code>invite_code</code> option to <code>true</code> in <code>services/webapp.json</code>:</p>
+
+<pre><code>{
+ "webapp": {
+ "invite_required": true
+ }
+}
+</code></pre>
+
+<p>This only works with LEAP platform 0.8 or higher.</p>
+
+<p>Run <code>leap deploy</code> to enable the option.</p>
+
+<p>You can then generate invite codes by logging into the web application with an admin user.</p>
+
+<p>Alternately, you can also generate invite codes with the command line:</p>
+
+<pre><code>workstation$ leap ssh bumblebee
+bumblebee# cd /srv/leap/webapp/
+bumblebee# sudo -u leap-webapp RAILS_ENV=production bundle exec rake "generate_invites[NUM,USES]"
+</code></pre>
+
+<p>Where <code>bumblebee</code> should be replaced with the name of your webapp node.</p>
+
+<p>The <strong>NUM</strong> specifies the amount of codes to generate. The <strong>USES</strong> parameter is optional: By default, all new invite codes can be used once and will then become invalid. If you provide another value for <strong>USES</strong>, you can set a different amount of maximum uses for the codes you generate.</p>
+
+<h2><a name="customization"></a>Customization</h2>
+
+<p>The provider directory <code>files/webapp</code> can be used to customize the appearance of the webapp. All the files in this directory will get sync'ed to the <code>/srv/leap/webapp/config/customization</code> directory of the deployed webapp node.</p>
+
+<p>Files in the <code>files/webapp</code> can override view files, locales, and stylesheets in the leap_web app:</p>
+
+<p>For example:</p>
+
+<pre><code>stylesheets/ -- override files in Rails.root/app/assets/stylesheets
+ tail.scss -- included before all others
+ head.scss -- included after all others
+
+public/ -- overrides files in Rails.root/public
+ favicon.ico -- custom favicon
+ img/ -- customary directory to put images in
+
+views/ -- overrides files Rails.root/app/views
+ home/
+ index.html.haml -- this file is what shows up on
+ the home page
+ pages/
+ privacy-policy.en.md -- this file will override
+ the default privacy policy
+ terms-of-service.en.md -- this file will override
+ the default TOS.
+
+locales/ -- overrides files in Rails.root/config/locales
+ en.yml -- overrides for English
+ de.yml -- overrides for German
+ and so on...
+</code></pre>
+
+<p>To interactively develop your customizations before you deploy them, you have two options:</p>
+
+<ol>
+<li>Edit a <code>webapp</code> node. This approach involves directly modifying the contents of the directory <code>/srv/leap/webapp/config/customization</code> on a deployed <code>webapp</code> node. This can, and probably should be, a &ldquo;local&rdquo; node. When doing this, you may need to restart leap_web in order for changes to take effect (<code>touch /srv/leap/webapp/tmp/restart.txt</code>).</li>
+<li>Alternately, you can install leap_web to run on your computer and edit files in <code>config/customization</code> locally. This approach does not require a provider or a <code>webapp</code> node. For more information, see the <a href="https://github.com/leapcode/leap_web">leap_web README</a>.</li>
+</ol>
+
+
+<p>NOTE: If you add a <code>tails.scss</code> or <code>head.scss</code> file, then you usually need to run <code>rake tmp:clear</code> and restart rails in order for the new stylesheet to get recognized. You should only need to do this once.</p>
+
+<p>Once you have what you want, then copy these files to the local provider directory <code>files/webapp</code> so that they will be installed each time you deploy.</p>
+
+<h2><a name="customization-tutorial"></a>Customization tutorial</h2>
+
+<p>This mini-tutorial will walk you through creating a custom &ldquo;branding&rdquo; of the leap_web application. We will be creating a provider called &ldquo;Prehistoric Computer.&rdquo;</p>
+
+<p>Here are the files we are going to create:</p>
+
+<pre><code>leap_web/config/customization
+├── locales
+│   ├── en.yml
+│   └── es.yml
+├── public
+│   ├── favicon.ico
+│   └── img
+│   └── masthead.png
+├── stylesheets
+│   └── tail.scss
+└── views
+ └── pages
+ ├── privacy-policy.en.md
+ └── privacy-policy.es.md
+</code></pre>
+
+<p>All these files are available in the source code in the <a href="https://github.com/leapcode/leap_web/tree/develop/config/customization.example">customization.example</a> directory.</p>
+
+<p>Remember, these files may live different places:</p>
+
+<ul>
+<li><code>user@localmachine$ leap_web/config/customization</code>: This will be the path if you have checked out a local copy of leap_web.git and are running <code>rails server</code> locally in order to test your customizations.</li>
+<li><code>user@localmachine$ PROVIDER/files/webapp</code>: This is the local provider directory where the files should be put so that they get correctly deployed to webapp nodes.</li>
+<li><code>root@webappnode# /srv/leap/webapp/config/customization</code>: This is where the files in the local provider directory <code>PROVIDER/files/webapp</code> get copied to after a <code>leap deploy</code> to a live webapp nodes.</li>
+</ul>
+
+
+<h3><a name="override-translations"></a>Override translations</h3>
+
+<p>You can add additional locale files in order to change the text used in the existing application and to add translations for string that you added to the application.</p>
+
+<p>In this example, we will be altering the default text for the &ldquo;login_info&rdquo; string. In <code>config/locales/en/home.en.yml</code> there is this entry:</p>
+
+<pre><code>en:
+ login_info: "Log in to change your account settings, create support tickets, and manage payments."
+</code></pre>
+
+<p>We are going to override this with some custom text in English and Spanish:</p>
+
+<p><code>leap_web/config/customization/locale/en.yml</code>:</p>
+
+<pre><code>en:
+ login_info: Authenticate to change your "Prehistoric Computer" settings.
+</code></pre>
+
+<p><code>leap_web/config/customization/locale/es.yml</code>:</p>
+
+<pre><code>es:
+ login_info: Autenticar a cambiar la configuración de "Computer Prehistoria."
+</code></pre>
+
+<p>Now, the home page of leap_web will use these new strings instead of the default. Remember that you must restart rails in order for new locale files to take effect.</p>
+
+<h3><a name="override-static-pages"></a>Override static pages</h3>
+
+<p>You can also override any of the static files included with leap_web, such as the privacy policy or terms of service.</p>
+
+<p>Here is how we would create a custom privacy policy in English and Spanish:</p>
+
+<p><code>leap_web/config/customization/views/pages/privacy-policy.en.md</code>:</p>
+
+<pre><code># Custom Privacy Policy
+This is our privacy policy.
+</code></pre>
+
+<p><code>leap_web/config/customization/views/pages/privacy-policy.es.md</code>:</p>
+
+<pre><code># Custom Política de Privacidad
+Esta es nuestra política de privacidad.
+</code></pre>
+
+<h3><a name="add-a-custom-header"></a>Add a custom header</h3>
+
+<p>Now we will add a custom header to every page. First, we add the images:</p>
+
+<pre><code>leap_web/config/customization
+ ├── public
+ ├── favicon.ico
+ └── img
+ └── masthead.png
+</code></pre>
+
+<p>You can create your own, or use the example files in <a href="https://github.com/leapcode/leap_web/tree/develop/config/customization.example">https://github.com/leapcode/leap_web/tree/develop/config/customization.example</a></p>
+
+<p>Now, we add some custom CSS so that we can style the masthead:</p>
+
+<p><code>leap_web/config/customization/stylesheets/tail.scss</code></p>
+
+<pre><code>$custom-color: #66bbaa;
+
+a {
+ color: $custom-color;
+}
+
+//
+// MASTHEAD
+//
+
+#masthead {
+ background-color: $custom-color;
+ border-bottom: none;
+
+ // make the masthead clickable by replacing the
+ // site name link with the masthead image:
+ .title {
+ padding: 0px;
+ .sitename a {
+ display: block;
+ background: url(/img/masthead.png) 0 0 no-repeat;
+ font-size: 0px;
+ height: 100px;
+ background-size: auto 100px;
+ }
+ }
+}
+
+// make the home page masthead slightly larger
+body.home #masthead {
+ .sitename a {
+ height: 150px;
+ background-size: auto 150px;
+ }
+}
+
+//
+// FOOTER
+//
+
+#footer .links {
+ background-color: $custom-color;
+}
+</code></pre>
+
+<p>NOTE: If you add a <code>tails.scss</code> or <code>head.scss</code> file, then you usually need to run <code>rake tmp:clear</code> and restart rails in order for the new stylesheet to get recognized. You should only need to do this once.</p>
+
+<h2><a name="custom-fork"></a>Custom Fork</h2>
+
+<p>Sometimes it is easier to maintain your own fork of the leap_web app. You can keep your customizations in that fork instead of in the provider <code>files/webapp</code> directory. Or, perhaps you want to add an engine to the application that modifies the app&rsquo;s behavior.</p>
+
+<p>To deploy your own leap_web, modify the provider file <code>common.json</code>:</p>
+
+<pre><code>{
+ "sources": {
+ "webapp": {
+ "revision": "origin/develop",
+ "source": "https://github.com/leapcode/leap_web",
+ "type": "git"
+ }
+ }
+}
+</code></pre>
+
+<p>To target only particular environment, modify instead <code>common.ENV.json</code>, where ENV is the name of the environment.</p>
+
+<p>See <a href="https://github.com/leapcode/leap_web/blob/develop/doc/DEVELOP.md">https://github.com/leapcode/leap_web/blob/develop/doc/DEVELOP.md</a> for notes on getting started hacking on leap_web.</p>
+
+<h2><a name="maintenance-mode"></a>Maintenance mode</h2>
+
+<p>You can put the webapp into maintenance mode by simply dropping a html file to <code>/srv/leap/webapp/public/system/maintenance.html</code>. For example:</p>
+
+<pre><code>workstation$ leap ssh webappnode
+server# echo "Temporarily down for maintenance. We will be back soon." &gt; /srv/leap/webapp/public/system/maintenance.html
+</code></pre>
+
+<h2><a name="known-problems"></a>Known problems</h2>
+
+<ul>
+<li>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.</li>
+<li>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.</li>
+</ul>
+
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/troubleshooting.html b/docs/en/troubleshooting.html
new file mode 100644
index 00000000..916cf229
--- /dev/null
+++ b/docs/en/troubleshooting.html
@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Troubleshooting - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='upgrading.html'>Upgrading</a>
+</li>
+<li class='active level0'>
+<a class='' href='troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level1'>
+<a class='' href='troubleshooting/tests.html'>Tests and Monitoring</a>
+</li>
+<li class=' level1'>
+<a class='' href='troubleshooting/known-issues.html'>Known issues</a>
+</li>
+<li class=' level1'>
+<a class='' href='troubleshooting/where-to-look.html'>Where to look</a>
+</li>
+<li class=' level0'>
+<a class='' href='details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Troubleshooting</h1>
+
+<div id='summary'>The LEAP Platform is set of complementary packages and server recipes to automate the maintenance of LEAP services in a hardened Debian environment.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+</ol></div>
+
+<div class=' page-summary'>
+ <h2>
+ <a href='troubleshooting/tests.html'>Tests and Monitoring</a>
+ </h2>
+ <div class='summary'>Testing and monitoring your infrastructure.</div>
+</div>
+<div class=' page-summary'>
+ <h2>
+ <a href='troubleshooting/known-issues.html'>Known issues</a>
+ </h2>
+ <div class='summary'>Known issues in the Leap Platform.</div>
+</div>
+<div class=' page-summary'>
+ <h2>
+ <a href='troubleshooting/where-to-look.html'>Where to look</a>
+ </h2>
+ <div class='summary'>The LEAP Platform is set of complementary packages and server recipes to automate the maintenance of LEAP services in a hardened Debian environment.</div>
+</div>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/troubleshooting/known-issues.html b/docs/en/troubleshooting/known-issues.html
new file mode 100644
index 00000000..607970b1
--- /dev/null
+++ b/docs/en/troubleshooting/known-issues.html
@@ -0,0 +1,238 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Known issues - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level1'>
+<a class='' href='tests.html'>Tests and Monitoring</a>
+</li>
+<li class='active level1'>
+<a class='' href='known-issues.html'>Known issues</a>
+</li>
+<li class=' level1'>
+<a class='' href='where-to-look.html'>Where to look</a>
+</li>
+<li class=' level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Leap Platform Release Notes</h1>
+
+<div id='summary'>Known issues in the Leap Platform.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="known-issues/index.html#060">0.6.0</a>
+ <ol>
+ <li>
+ <a href="known-issues/index.html#upgrading">Upgrading</a>
+ </li>
+ <li>
+ <a href="known-issues/index.html#openvpn">OpenVPN</a>
+ </li>
+ <li>
+ <a href="known-issues/index.html#couchdb">CouchDB</a>
+ </li>
+ <li>
+ <a href="known-issues/index.html#user-setup-and-ssh">User setup and ssh</a>
+ </li>
+ <li>
+ <a href="known-issues/index.html#deploying">Deploying</a>
+ </li>
+ <li>
+ <a href="known-issues/index.html#ipv6">IPv6</a>
+ </li>
+ <li>
+ <a href="known-issues/index.html#special-environments">Special Environments</a>
+ </li>
+ </ol>
+ </li>
+</ol></div>
+
+<p>Here you can find documentation about known issues and potential work-arounds in the current Leap Platform release.</p>
+
+<h1><a name="060"></a>0.6.0</h1>
+
+<h2><a name="upgrading"></a>Upgrading</h2>
+
+<p>Upgrade your leap_platform to 0.6 and make sure you have the latest leap_cli.</p>
+
+<p><strong>Update leap_platform:</strong></p>
+
+<pre><code>cd leap_platform
+git pull
+git checkout -b 0.6.0 0.6.0
+</code></pre>
+
+<p><strong>Update leap_cli:</strong></p>
+
+<p>If it is installed as a gem from rubygems:</p>
+
+<pre><code>sudo gem update leap_cli
+</code></pre>
+
+<p>If it is installed as a gem from source:</p>
+
+<pre><code>cd leap_cli
+git pull
+git checkout master
+rake build
+sudo rake install
+</code></pre>
+
+<p>If it is run directly from source:</p>
+
+<pre><code>cd leap_cli
+git pull
+git checkout master
+</code></pre>
+
+<p>To upgrade:</p>
+
+<pre><code>leap --version # must be at least 1.6.2
+leap cert update
+leap deploy
+leap test
+</code></pre>
+
+<p>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).</p>
+
+<h2><a name="openvpn"></a>OpenVPN</h2>
+
+<p>On deployment to a openvpn node, if the following happens:</p>
+
+<pre><code>- 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
+</code></pre>
+
+<p>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:</p>
+
+<pre><code># 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
+</code></pre>
+
+<p>if you see this error, simply restart the node.</p>
+
+<h2><a name="couchdb"></a>CouchDB</h2>
+
+<p>At the moment, we only support one couchdb server for stability purposes.</p>
+
+<h2><a name="user-setup-and-ssh"></a>User setup and ssh</h2>
+
+<p>At the moment, it is only possible to add an admin who will have access to all LEAP servers (see: <a href="https://leap.se/code/issues/2280">https://leap.se/code/issues/2280</a>)</p>
+
+<p>The command <code>leap add-user --self</code> allows only one SSH key. If you want to specify more than one key for a user, you can do it manually:</p>
+
+<pre><code>users/userx/userx_ssh.pub
+users/userx/otherkey_ssh.pub
+</code></pre>
+
+<p>All keys matching &lsquo;userx/*_ssh.pub&rsquo; will be used for that user.</p>
+
+<h2><a name="deploying"></a>Deploying</h2>
+
+<p>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 <code>leap -v2 deploy</code> to get more verbose logs and capture the complete output to provide to us for debugging.</p>
+
+<p>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: <a href="https://leap.se/code/issues/1091">https://leap.se/code/issues/1091</a>)</p>
+
+<p>Deployment gives &lsquo;error: in <code>%</code>: too few arguments (ArgumentError)&rsquo; - 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: <a href="https://leap.se/code/issues/2550">https://leap.se/code/issues/2550</a>)</p>
+
+<p>This release has no ability to custom configure apt sources or proxies (see: <a href="https://leap.se/code/issues/1971">https://leap.se/code/issues/1971</a>)</p>
+
+<p>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</p>
+
+<h2><a name="ipv6"></a>IPv6</h2>
+
+<p>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.</p>
+
+<h2><a name="special-environments"></a>Special Environments</h2>
+
+<p>When deploying to OpenStack release &ldquo;nova&rdquo; or newer, you will need to do an initial deploy, then when it has finished run <code>leap facts update</code> and then deploy again (see: <a href="https://leap.se/code/issues/3020">https://leap.se/code/issues/3020</a>)</p>
+
+<p>It is not possible to actually use the EIP openvpn server on vagrant nodes (see: <a href="https://leap.se/code/issues/2401">https://leap.se/code/issues/2401</a>)</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/troubleshooting/known-issues/index.html b/docs/en/troubleshooting/known-issues/index.html
new file mode 100644
index 00000000..eee3b120
--- /dev/null
+++ b/docs/en/troubleshooting/known-issues/index.html
@@ -0,0 +1,238 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Known issues - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../upgrading.html'>Upgrading</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level1'>
+<a class='' href='../tests.html'>Tests and Monitoring</a>
+</li>
+<li class='active level1'>
+<a class='' href='../known-issues.html'>Known issues</a>
+</li>
+<li class=' level1'>
+<a class='' href='../where-to-look.html'>Where to look</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Leap Platform Release Notes</h1>
+
+<div id='summary'>Known issues in the Leap Platform.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="index.html#060">0.6.0</a>
+ <ol>
+ <li>
+ <a href="index.html#upgrading">Upgrading</a>
+ </li>
+ <li>
+ <a href="index.html#openvpn">OpenVPN</a>
+ </li>
+ <li>
+ <a href="index.html#couchdb">CouchDB</a>
+ </li>
+ <li>
+ <a href="index.html#user-setup-and-ssh">User setup and ssh</a>
+ </li>
+ <li>
+ <a href="index.html#deploying">Deploying</a>
+ </li>
+ <li>
+ <a href="index.html#ipv6">IPv6</a>
+ </li>
+ <li>
+ <a href="index.html#special-environments">Special Environments</a>
+ </li>
+ </ol>
+ </li>
+</ol></div>
+
+<p>Here you can find documentation about known issues and potential work-arounds in the current Leap Platform release.</p>
+
+<h1><a name="060"></a>0.6.0</h1>
+
+<h2><a name="upgrading"></a>Upgrading</h2>
+
+<p>Upgrade your leap_platform to 0.6 and make sure you have the latest leap_cli.</p>
+
+<p><strong>Update leap_platform:</strong></p>
+
+<pre><code>cd leap_platform
+git pull
+git checkout -b 0.6.0 0.6.0
+</code></pre>
+
+<p><strong>Update leap_cli:</strong></p>
+
+<p>If it is installed as a gem from rubygems:</p>
+
+<pre><code>sudo gem update leap_cli
+</code></pre>
+
+<p>If it is installed as a gem from source:</p>
+
+<pre><code>cd leap_cli
+git pull
+git checkout master
+rake build
+sudo rake install
+</code></pre>
+
+<p>If it is run directly from source:</p>
+
+<pre><code>cd leap_cli
+git pull
+git checkout master
+</code></pre>
+
+<p>To upgrade:</p>
+
+<pre><code>leap --version # must be at least 1.6.2
+leap cert update
+leap deploy
+leap test
+</code></pre>
+
+<p>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).</p>
+
+<h2><a name="openvpn"></a>OpenVPN</h2>
+
+<p>On deployment to a openvpn node, if the following happens:</p>
+
+<pre><code>- 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
+</code></pre>
+
+<p>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:</p>
+
+<pre><code># 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
+</code></pre>
+
+<p>if you see this error, simply restart the node.</p>
+
+<h2><a name="couchdb"></a>CouchDB</h2>
+
+<p>At the moment, we only support one couchdb server for stability purposes.</p>
+
+<h2><a name="user-setup-and-ssh"></a>User setup and ssh</h2>
+
+<p>At the moment, it is only possible to add an admin who will have access to all LEAP servers (see: <a href="https://leap.se/code/issues/2280">https://leap.se/code/issues/2280</a>)</p>
+
+<p>The command <code>leap add-user --self</code> allows only one SSH key. If you want to specify more than one key for a user, you can do it manually:</p>
+
+<pre><code>users/userx/userx_ssh.pub
+users/userx/otherkey_ssh.pub
+</code></pre>
+
+<p>All keys matching &lsquo;userx/*_ssh.pub&rsquo; will be used for that user.</p>
+
+<h2><a name="deploying"></a>Deploying</h2>
+
+<p>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 <code>leap -v2 deploy</code> to get more verbose logs and capture the complete output to provide to us for debugging.</p>
+
+<p>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: <a href="https://leap.se/code/issues/1091">https://leap.se/code/issues/1091</a>)</p>
+
+<p>Deployment gives &lsquo;error: in <code>%</code>: too few arguments (ArgumentError)&rsquo; - 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: <a href="https://leap.se/code/issues/2550">https://leap.se/code/issues/2550</a>)</p>
+
+<p>This release has no ability to custom configure apt sources or proxies (see: <a href="https://leap.se/code/issues/1971">https://leap.se/code/issues/1971</a>)</p>
+
+<p>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</p>
+
+<h2><a name="ipv6"></a>IPv6</h2>
+
+<p>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.</p>
+
+<h2><a name="special-environments"></a>Special Environments</h2>
+
+<p>When deploying to OpenStack release &ldquo;nova&rdquo; or newer, you will need to do an initial deploy, then when it has finished run <code>leap facts update</code> and then deploy again (see: <a href="https://leap.se/code/issues/3020">https://leap.se/code/issues/3020</a>)</p>
+
+<p>It is not possible to actually use the EIP openvpn server on vagrant nodes (see: <a href="https://leap.se/code/issues/2401">https://leap.se/code/issues/2401</a>)</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/troubleshooting/tests.html b/docs/en/troubleshooting/tests.html
new file mode 100644
index 00000000..e4c2fdc2
--- /dev/null
+++ b/docs/en/troubleshooting/tests.html
@@ -0,0 +1,201 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Tests and Monitoring - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class='active level1'>
+<a class='' href='tests.html'>Tests and Monitoring</a>
+</li>
+<li class=' level1'>
+<a class='' href='known-issues.html'>Known issues</a>
+</li>
+<li class=' level1'>
+<a class='' href='where-to-look.html'>Where to look</a>
+</li>
+<li class=' level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Tests and Monitoring</h1>
+
+<div id='summary'>Testing and monitoring your infrastructure.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="tests/index.html#troubleshooting-tests">Troubleshooting Tests</a>
+ </li>
+ <li>
+ <a href="tests/index.html#testing-with-the-bitmask-client">Testing with the bitmask client</a>
+ </li>
+ <li>
+ <a href="tests/index.html#testing-recieving-mail">Testing Recieving Mail</a>
+ </li>
+ <li>
+ <a href="tests/index.html#monitoring">Monitoring</a>
+ </li>
+ <li>
+ <a href="tests/index.html#nagios-frontends">Nagios Frontends</a>
+ <ol>
+ <li>
+ <a href="tests/index.html#log-monitoring">Log Monitoring</a>
+ </li>
+ </ol>
+ </li>
+</ol></div>
+
+<h2><a name="troubleshooting-tests"></a>Troubleshooting Tests</h2>
+
+<p>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.</p>
+
+<p>To run tests on FILTER node list:</p>
+
+<pre><code>workstation$ leap test run FILTER
+</code></pre>
+
+<p>For example, you can also test a single node (<code>leap test elephant</code>); test a specific environment (<code>leap test development</code>), or any tag (<code>leap test soledad</code>).</p>
+
+<p>Alternately, you can run test on all nodes (probably only useful if you have pinned the environment):</p>
+
+<pre><code>workstation$ leap test
+</code></pre>
+
+<p>The tests that are performed are located in the platform under the tests directory.</p>
+
+<h2><a name="testing-with-the-bitmask-client"></a>Testing with the bitmask client</h2>
+
+<p>Download the provider ca:</p>
+
+<pre><code>wget --no-check-certificate https://example.org/ca.crt -O /tmp/ca.crt
+</code></pre>
+
+<p>Start bitmask:</p>
+
+<pre><code>bitmask --ca-cert-file /tmp/ca.crt
+</code></pre>
+
+<h2><a name="testing-recieving-mail"></a>Testing Recieving Mail</h2>
+
+<p>Use i.e. swaks to send a testmail</p>
+
+<pre><code>swaks -f noone@example.org -t testuser@example.org -s example.org
+</code></pre>
+
+<p>and use your favorite mail client to examine your inbox.</p>
+
+<p>You can also use <a href="http://offlineimap.org/">offlineimap</a> to fetch mails:</p>
+
+<pre><code> offlineimap -c vagrant/.offlineimaprc.example.org
+</code></pre>
+
+<p>WARNING: Use offlineimap <em>only</em> for testing/debugging,
+because it will save the mails <em>decrypted</em> locally to
+your disk !</p>
+
+<h2><a name="monitoring"></a>Monitoring</h2>
+
+<p>In order to set up a monitoring node, you simply add a <code>monitor</code> 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.</p>
+
+<p>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 <code>leap test</code>, plus many others.</p>
+
+<p>We use <a href="https://www.nagios.org/">Nagios</a> together with <a href="https://en.wikipedia.org/wiki/Check_MK">Check MK agent</a> for running checks on remote hosts.</p>
+
+<p>One nagios installation will monitor all nodes in all your environments. You can log into the monitoring web interface via <a href="https://DOMAIN/nagios3/">https://DOMAIN/nagios3/</a>. The username is <code>nagiosadmin</code> and the password is found in the secrets.json file in your provider directory.
+Nagios will send out mails to the <code>contacts</code> address provided in <code>provider.json</code>.</p>
+
+<h2><a name="nagios-frontends"></a>Nagios Frontends</h2>
+
+<p>There are other ways to check and get notified by Nagios besides regularly checking the Nagios webinterface or reading email notifications. Check out the <a href="http://exchange.nagios.org/directory/Addons/Frontends-%28GUIs-and-CLIs%29">Frontends (GUIs and CLIs)</a> on the Nagios project website.
+A recommended status tray application is <a href="https://nagstamon.ifw-dresden.de/">Nagstamon</a>, which is available for Linux, MacOS X and Windows. It can not only notify you of hosts/services failures, you can also acknowledge or recheck them.</p>
+
+<h3><a name="log-monitoring"></a>Log Monitoring</h3>
+
+<p>At the moment, we use <a href="https://mathias-kettner.de/checkmk_check_logwatch.html">check-mk-agent-logwatch</a> for searching logs for irregularities.
+Logs are parsed for patterns using a blacklist, and are stored in <code>/var/lib/check_mk/logwatch/&lt;Nodename&gt;</code>.</p>
+
+<p>In order to &ldquo;acknowledge&rdquo; a log warning, you need to log in to the monitoring server, and delete the corresponding file in <code>/var/lib/check_mk/logwatch/&lt;Nodename&gt;</code>. This should be done via the nagios webinterface in the future.</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/troubleshooting/tests/index.html b/docs/en/troubleshooting/tests/index.html
new file mode 100644
index 00000000..f46eddc7
--- /dev/null
+++ b/docs/en/troubleshooting/tests/index.html
@@ -0,0 +1,201 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Tests and Monitoring - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../upgrading.html'>Upgrading</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class='active level1'>
+<a class='' href='../tests.html'>Tests and Monitoring</a>
+</li>
+<li class=' level1'>
+<a class='' href='../known-issues.html'>Known issues</a>
+</li>
+<li class=' level1'>
+<a class='' href='../where-to-look.html'>Where to look</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Tests and Monitoring</h1>
+
+<div id='summary'>Testing and monitoring your infrastructure.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="index.html#troubleshooting-tests">Troubleshooting Tests</a>
+ </li>
+ <li>
+ <a href="index.html#testing-with-the-bitmask-client">Testing with the bitmask client</a>
+ </li>
+ <li>
+ <a href="index.html#testing-recieving-mail">Testing Recieving Mail</a>
+ </li>
+ <li>
+ <a href="index.html#monitoring">Monitoring</a>
+ </li>
+ <li>
+ <a href="index.html#nagios-frontends">Nagios Frontends</a>
+ <ol>
+ <li>
+ <a href="index.html#log-monitoring">Log Monitoring</a>
+ </li>
+ </ol>
+ </li>
+</ol></div>
+
+<h2><a name="troubleshooting-tests"></a>Troubleshooting Tests</h2>
+
+<p>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.</p>
+
+<p>To run tests on FILTER node list:</p>
+
+<pre><code>workstation$ leap test run FILTER
+</code></pre>
+
+<p>For example, you can also test a single node (<code>leap test elephant</code>); test a specific environment (<code>leap test development</code>), or any tag (<code>leap test soledad</code>).</p>
+
+<p>Alternately, you can run test on all nodes (probably only useful if you have pinned the environment):</p>
+
+<pre><code>workstation$ leap test
+</code></pre>
+
+<p>The tests that are performed are located in the platform under the tests directory.</p>
+
+<h2><a name="testing-with-the-bitmask-client"></a>Testing with the bitmask client</h2>
+
+<p>Download the provider ca:</p>
+
+<pre><code>wget --no-check-certificate https://example.org/ca.crt -O /tmp/ca.crt
+</code></pre>
+
+<p>Start bitmask:</p>
+
+<pre><code>bitmask --ca-cert-file /tmp/ca.crt
+</code></pre>
+
+<h2><a name="testing-recieving-mail"></a>Testing Recieving Mail</h2>
+
+<p>Use i.e. swaks to send a testmail</p>
+
+<pre><code>swaks -f noone@example.org -t testuser@example.org -s example.org
+</code></pre>
+
+<p>and use your favorite mail client to examine your inbox.</p>
+
+<p>You can also use <a href="http://offlineimap.org/">offlineimap</a> to fetch mails:</p>
+
+<pre><code> offlineimap -c vagrant/.offlineimaprc.example.org
+</code></pre>
+
+<p>WARNING: Use offlineimap <em>only</em> for testing/debugging,
+because it will save the mails <em>decrypted</em> locally to
+your disk !</p>
+
+<h2><a name="monitoring"></a>Monitoring</h2>
+
+<p>In order to set up a monitoring node, you simply add a <code>monitor</code> 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.</p>
+
+<p>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 <code>leap test</code>, plus many others.</p>
+
+<p>We use <a href="https://www.nagios.org/">Nagios</a> together with <a href="https://en.wikipedia.org/wiki/Check_MK">Check MK agent</a> for running checks on remote hosts.</p>
+
+<p>One nagios installation will monitor all nodes in all your environments. You can log into the monitoring web interface via <a href="https://DOMAIN/nagios3/">https://DOMAIN/nagios3/</a>. The username is <code>nagiosadmin</code> and the password is found in the secrets.json file in your provider directory.
+Nagios will send out mails to the <code>contacts</code> address provided in <code>provider.json</code>.</p>
+
+<h2><a name="nagios-frontends"></a>Nagios Frontends</h2>
+
+<p>There are other ways to check and get notified by Nagios besides regularly checking the Nagios webinterface or reading email notifications. Check out the <a href="http://exchange.nagios.org/directory/Addons/Frontends-%28GUIs-and-CLIs%29">Frontends (GUIs and CLIs)</a> on the Nagios project website.
+A recommended status tray application is <a href="https://nagstamon.ifw-dresden.de/">Nagstamon</a>, which is available for Linux, MacOS X and Windows. It can not only notify you of hosts/services failures, you can also acknowledge or recheck them.</p>
+
+<h3><a name="log-monitoring"></a>Log Monitoring</h3>
+
+<p>At the moment, we use <a href="https://mathias-kettner.de/checkmk_check_logwatch.html">check-mk-agent-logwatch</a> for searching logs for irregularities.
+Logs are parsed for patterns using a blacklist, and are stored in <code>/var/lib/check_mk/logwatch/&lt;Nodename&gt;</code>.</p>
+
+<p>In order to &ldquo;acknowledge&rdquo; a log warning, you need to log in to the monitoring server, and delete the corresponding file in <code>/var/lib/check_mk/logwatch/&lt;Nodename&gt;</code>. This should be done via the nagios webinterface in the future.</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/troubleshooting/where-to-look.html b/docs/en/troubleshooting/where-to-look.html
new file mode 100644
index 00000000..a1207aca
--- /dev/null
+++ b/docs/en/troubleshooting/where-to-look.html
@@ -0,0 +1,451 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Where to look - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level1'>
+<a class='' href='tests.html'>Tests and Monitoring</a>
+</li>
+<li class=' level1'>
+<a class='' href='known-issues.html'>Known issues</a>
+</li>
+<li class='active level1'>
+<a class='' href='where-to-look.html'>Where to look</a>
+</li>
+<li class=' level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Where to look for errors</h1>
+
+<div id='summary'>The LEAP Platform is set of complementary packages and server recipes to automate the maintenance of LEAP services in a hardened Debian environment.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="where-to-look/index.html#general">General</a>
+ </li>
+ <li>
+ <a href="where-to-look/index.html#firewall">Firewall</a>
+ </li>
+ <li>
+ <a href="where-to-look/index.html#webapp">Webapp</a>
+ <ol>
+ <li>
+ <a href="where-to-look/index.html#places-to-look-for-errors">Places to look for errors</a>
+ </li>
+ <li>
+ <a href="where-to-look/index.html#is-haproxy-ok">Is haproxy ok ?</a>
+ </li>
+ <li>
+ <a href="where-to-look/index.html#is-couchdb-accessible-through-stunnel">Is couchdb accessible through stunnel ?</a>
+ </li>
+ <li>
+ <a href="where-to-look/index.html#check-couchdb-acl-as-admin">Check couchdb acl as admin</a>
+ </li>
+ <li>
+ <a href="where-to-look/index.html#check-couchdb-acl-as-unpriviledged-user">Check couchdb acl as unpriviledged user</a>
+ </li>
+ <li>
+ <a href="where-to-look/index.html#all-urls-accessible">All URLs accessible ?</a>
+ </li>
+ <li>
+ <a href="where-to-look/index.html#check-client-config-files">Check client config files</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="where-to-look/index.html#soledad">Soledad</a>
+ </li>
+ <li>
+ <a href="where-to-look/index.html#couchdb">Couchdb</a>
+ <ol>
+ <li>
+ <a href="where-to-look/index.html#places-to-look-for-errors-2">Places to look for errors</a>
+ </li>
+ <li>
+ <a href="where-to-look/index.html#databases">Databases</a>
+ </li>
+ <li>
+ <a href="where-to-look/index.html#design-documents">Design Documents</a>
+ </li>
+ <li>
+ <a href="where-to-look/index.html#is-couchdb-cluster-backend-accessible-through-stunnel">Is couchdb cluster backend accessible through stunnel ?</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="where-to-look/index.html#mx">MX</a>
+ <ol>
+ <li>
+ <a href="where-to-look/index.html#places-to-look-for-errors-3">Places to look for errors</a>
+ </li>
+ <li>
+ <a href="where-to-look/index.html#is-couchdb-accessible-through-stunnel-2">Is couchdb accessible through stunnel ?</a>
+ </li>
+ <li>
+ <a href="where-to-look/index.html#query-leap-mx">Query leap-mx</a>
+ </li>
+ <li>
+ <a href="where-to-look/index.html#check-couchdb-acl-as-unpriviledged-user-2">Check couchdb acl as unpriviledged user</a>
+ </li>
+ <li>
+ <a href="where-to-look/index.html#mailspool">Mailspool</a>
+ </li>
+ <li>
+ <a href="where-to-look/index.html#testing-mail-delivery">Testing mail delivery</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="where-to-look/index.html#vpn">VPN</a>
+ <ol>
+ <li>
+ <a href="where-to-look/index.html#places-to-look-for-errors-4">Places to look for errors</a>
+ </li>
+ </ol>
+ </li>
+</ol></div>
+
+<h1><a name="general"></a>General</h1>
+
+<ul>
+<li>Please increase verbosity when debugging / filing issues in our issue tracker. You can do this with adding i.e. <code>-v 5</code> after the <code>leap</code> cmd, i.e. <code>leap -v 2 deploy</code>.</li>
+<li>We use the <code>example.org</code> domain for documentation purposes here, please replace it with the you domain.</li>
+</ul>
+
+
+<h1><a name="firewall"></a>Firewall</h1>
+
+<p>Every node in your provider has its own restrictive firewall, but you might have a network firewall in place as well that is not managed by LEAP platform. To see what ports and addresses must be open, run this command:</p>
+
+<pre><code>workstation$ leap compile firewall
+</code></pre>
+
+<p>If any of those are blocked, then your provider will not work.</p>
+
+<h1><a name="webapp"></a>Webapp</h1>
+
+<h2><a name="places-to-look-for-errors"></a>Places to look for errors</h2>
+
+<ul>
+<li><code>/var/log/apache2/error.log</code></li>
+<li><code>/srv/leap/webapp/log/production.log</code></li>
+<li><code>/var/log/syslog</code> (watch out for stunnel issues)</li>
+<li><code>/var/log/leap/*</code></li>
+</ul>
+
+
+<h2><a name="is-haproxy-ok"></a>Is haproxy ok ?</h2>
+
+<pre><code>curl -s -X GET "http://127.0.0.1:4096"
+</code></pre>
+
+<h2><a name="is-couchdb-accessible-through-stunnel"></a>Is couchdb accessible through stunnel ?</h2>
+
+<ul>
+<li><p>Depending on how many couch nodes you have, increase the port for every test
+(see /etc/haproxy/haproxy.cfg for the server/port mapping):</p>
+
+<p> curl -s -X GET &ldquo;<a href="http://127.0.0.1:4000">http://127.0.0.1:4000</a>&rdquo;
+ curl -s -X GET &ldquo;<a href="http://127.0.0.1:4001">http://127.0.0.1:4001</a>&rdquo;
+ &hellip;</p></li>
+</ul>
+
+
+<h2><a name="check-couchdb-acl-as-admin"></a>Check couchdb acl as admin</h2>
+
+<pre><code>mkdir /etc/couchdb
+cat /srv/leap/webapp/config/couchdb.yml.admin # see username and password
+echo "machine 127.0.0.1 login admin password &lt;PASSWORD&gt;" &gt; /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"
+</code></pre>
+
+<h2><a name="check-couchdb-acl-as-unpriviledged-user"></a>Check couchdb acl as unpriviledged user</h2>
+
+<pre><code>cat /srv/leap/webapp/config/couchdb.yml # see username and password
+echo "machine 127.0.0.1 login webapp password &lt;PASSWORD&gt;" &gt; /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"
+</code></pre>
+
+<h2><a name="all-urls-accessible"></a>All URLs accessible ?</h2>
+
+<ul>
+<li><a href="https://example.org">https://example.org</a></li>
+<li><a href="https://api.example.org:4430/provider.json">https://api.example.org:4430/provider.json</a></li>
+<li><a href="https://example.org/ca.crt">https://example.org/ca.crt</a></li>
+</ul>
+
+
+<h2><a name="check-client-config-files"></a>Check client config files</h2>
+
+<ul>
+<li><a href="https://example.net/provider.json">https://example.net/provider.json</a></li>
+<li><a href="https://example.net/1/config/smtp-service.json">https://example.net/1/config/smtp-service.json</a></li>
+<li><a href="https://example.net/1/config/soledad-service.json">https://example.net/1/config/soledad-service.json</a></li>
+<li><a href="https://example.net/1/config/eip-service.json">https://example.net/1/config/eip-service.json</a></li>
+</ul>
+
+
+<h1><a name="soledad"></a>Soledad</h1>
+
+<pre><code>/var/log/soledad.log
+</code></pre>
+
+<h1><a name="couchdb"></a>Couchdb</h1>
+
+<h2><a name="places-to-look-for-errors-2"></a>Places to look for errors</h2>
+
+<ul>
+<li><code>/var/log/couchdb/couch.log</code></li>
+<li><code>/var/log/syslog</code> (watch out for stunnel issues)</li>
+</ul>
+
+
+<h2><a name="databases"></a>Databases</h2>
+
+<ul>
+<li>Following output shows all neccessary DBs that should be present. Note that the <code>user-0123456....</code> DBs are the data stores for a particular user.</li>
+</ul>
+
+
+<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>
+
+
+<h2><a name="design-documents"></a>Design Documents</h2>
+
+<ul>
+<li>Is User <code>_design doc</code> available ?</li>
+</ul>
+
+
+<pre>
+ curl -s --netrc-file /etc/couchdb/couchdb.netrc -X GET "http://127.0.0.1:5984/users/_design/User"
+</pre>
+
+
+<h2><a name="is-couchdb-cluster-backend-accessible-through-stunnel"></a>Is couchdb cluster backend accessible through stunnel ?</h2>
+
+<ul>
+<li>Find out how many connections are set up for the couchdb cluster backend:</li>
+</ul>
+
+
+<pre>
+ grep "accept = 127.0.0.1" /etc/stunnel/*
+</pre>
+
+
+<ul>
+<li>Now connect to all of those local endpoints to see if they up. All these tests should return &ldquo;localhost [127.0.0.1] 4000 (?) open&rdquo;</li>
+</ul>
+
+
+<pre>
+ nc -v 127.0.0.1 4000
+ nc -v 127.0.0.1 4001
+ ...
+</pre>
+
+
+<h1><a name="mx"></a>MX</h1>
+
+<h2><a name="places-to-look-for-errors-3"></a>Places to look for errors</h2>
+
+<ul>
+<li><code>/var/log/mail.log</code></li>
+<li><code>/var/log/leap_mx.log</code></li>
+<li><code>/var/log/syslog</code> (watch out for stunnel issues)</li>
+</ul>
+
+
+<h2><a name="is-couchdb-accessible-through-stunnel-2"></a>Is couchdb accessible through stunnel ?</h2>
+
+<ul>
+<li><p>Depending on how many couch nodes you have, increase the port for every test
+(see /etc/haproxy/haproxy.cfg for the server/port mapping):</p>
+
+<p> curl -s -X GET &ldquo;<a href="http://127.0.0.1:4000">http://127.0.0.1:4000</a>&rdquo;
+ curl -s -X GET &ldquo;<a href="http://127.0.0.1:4001">http://127.0.0.1:4001</a>&rdquo;
+ &hellip;</p></li>
+</ul>
+
+
+<h2><a name="query-leap-mx"></a>Query leap-mx</h2>
+
+<ul>
+<li>for useraccount</li>
+</ul>
+
+
+<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>
+
+
+<ul>
+<li>for mailalias</li>
+</ul>
+
+
+<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>
+
+
+<h2><a name="check-couchdb-acl-as-unpriviledged-user-2"></a>Check couchdb acl as unpriviledged user</h2>
+
+<pre><code>cat /etc/leap/mx.conf # see username and password
+echo "machine 127.0.0.1 login leap_mx password &lt;PASSWORD&gt;" &gt; /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-&lt;hash&gt;" db
+curl -s --netrc-file /etc/couchdb/couchdb-leap_mx.netrc -X GET "http://127.0.0.1:4096/user-de9c77a3d7efbc779c6c20da88e8fb9c"
+</code></pre>
+
+<ul>
+<li>you may check multiple times, cause 127.0.0.1:4096 is haproxy load-balancing the different couchdb nodes</li>
+</ul>
+
+
+<h2><a name="mailspool"></a>Mailspool</h2>
+
+<ul>
+<li>Any file in the leap_mx mailspool longer for a few seconds ?</li>
+</ul>
+
+
+<pre>
+ ls -la /var/mail/vmail/Maildir/cur/
+</pre>
+
+
+<ul>
+<li>Any mails in postfix mailspool longer than a few seconds ?</li>
+</ul>
+
+
+<pre>
+ mailq
+</pre>
+
+
+<h2><a name="testing-mail-delivery"></a>Testing mail delivery</h2>
+
+<pre><code>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
+</code></pre>
+
+<h1><a name="vpn"></a>VPN</h1>
+
+<h2><a name="places-to-look-for-errors-4"></a>Places to look for errors</h2>
+
+<ul>
+<li><code>/var/log/syslog</code> (watch out for openvpn issues)</li>
+</ul>
+
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/troubleshooting/where-to-look/index.html b/docs/en/troubleshooting/where-to-look/index.html
new file mode 100644
index 00000000..ab3115af
--- /dev/null
+++ b/docs/en/troubleshooting/where-to-look/index.html
@@ -0,0 +1,451 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Where to look - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../upgrading.html'>Upgrading</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level1'>
+<a class='' href='../tests.html'>Tests and Monitoring</a>
+</li>
+<li class=' level1'>
+<a class='' href='../known-issues.html'>Known issues</a>
+</li>
+<li class='active level1'>
+<a class='' href='../where-to-look.html'>Where to look</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Where to look for errors</h1>
+
+<div id='summary'>The LEAP Platform is set of complementary packages and server recipes to automate the maintenance of LEAP services in a hardened Debian environment.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="index.html#general">General</a>
+ </li>
+ <li>
+ <a href="index.html#firewall">Firewall</a>
+ </li>
+ <li>
+ <a href="index.html#webapp">Webapp</a>
+ <ol>
+ <li>
+ <a href="index.html#places-to-look-for-errors">Places to look for errors</a>
+ </li>
+ <li>
+ <a href="index.html#is-haproxy-ok">Is haproxy ok ?</a>
+ </li>
+ <li>
+ <a href="index.html#is-couchdb-accessible-through-stunnel">Is couchdb accessible through stunnel ?</a>
+ </li>
+ <li>
+ <a href="index.html#check-couchdb-acl-as-admin">Check couchdb acl as admin</a>
+ </li>
+ <li>
+ <a href="index.html#check-couchdb-acl-as-unpriviledged-user">Check couchdb acl as unpriviledged user</a>
+ </li>
+ <li>
+ <a href="index.html#all-urls-accessible">All URLs accessible ?</a>
+ </li>
+ <li>
+ <a href="index.html#check-client-config-files">Check client config files</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#soledad">Soledad</a>
+ </li>
+ <li>
+ <a href="index.html#couchdb">Couchdb</a>
+ <ol>
+ <li>
+ <a href="index.html#places-to-look-for-errors-2">Places to look for errors</a>
+ </li>
+ <li>
+ <a href="index.html#databases">Databases</a>
+ </li>
+ <li>
+ <a href="index.html#design-documents">Design Documents</a>
+ </li>
+ <li>
+ <a href="index.html#is-couchdb-cluster-backend-accessible-through-stunnel">Is couchdb cluster backend accessible through stunnel ?</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#mx">MX</a>
+ <ol>
+ <li>
+ <a href="index.html#places-to-look-for-errors-3">Places to look for errors</a>
+ </li>
+ <li>
+ <a href="index.html#is-couchdb-accessible-through-stunnel-2">Is couchdb accessible through stunnel ?</a>
+ </li>
+ <li>
+ <a href="index.html#query-leap-mx">Query leap-mx</a>
+ </li>
+ <li>
+ <a href="index.html#check-couchdb-acl-as-unpriviledged-user-2">Check couchdb acl as unpriviledged user</a>
+ </li>
+ <li>
+ <a href="index.html#mailspool">Mailspool</a>
+ </li>
+ <li>
+ <a href="index.html#testing-mail-delivery">Testing mail delivery</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#vpn">VPN</a>
+ <ol>
+ <li>
+ <a href="index.html#places-to-look-for-errors-4">Places to look for errors</a>
+ </li>
+ </ol>
+ </li>
+</ol></div>
+
+<h1><a name="general"></a>General</h1>
+
+<ul>
+<li>Please increase verbosity when debugging / filing issues in our issue tracker. You can do this with adding i.e. <code>-v 5</code> after the <code>leap</code> cmd, i.e. <code>leap -v 2 deploy</code>.</li>
+<li>We use the <code>example.org</code> domain for documentation purposes here, please replace it with the you domain.</li>
+</ul>
+
+
+<h1><a name="firewall"></a>Firewall</h1>
+
+<p>Every node in your provider has its own restrictive firewall, but you might have a network firewall in place as well that is not managed by LEAP platform. To see what ports and addresses must be open, run this command:</p>
+
+<pre><code>workstation$ leap compile firewall
+</code></pre>
+
+<p>If any of those are blocked, then your provider will not work.</p>
+
+<h1><a name="webapp"></a>Webapp</h1>
+
+<h2><a name="places-to-look-for-errors"></a>Places to look for errors</h2>
+
+<ul>
+<li><code>/var/log/apache2/error.log</code></li>
+<li><code>/srv/leap/webapp/log/production.log</code></li>
+<li><code>/var/log/syslog</code> (watch out for stunnel issues)</li>
+<li><code>/var/log/leap/*</code></li>
+</ul>
+
+
+<h2><a name="is-haproxy-ok"></a>Is haproxy ok ?</h2>
+
+<pre><code>curl -s -X GET "http://127.0.0.1:4096"
+</code></pre>
+
+<h2><a name="is-couchdb-accessible-through-stunnel"></a>Is couchdb accessible through stunnel ?</h2>
+
+<ul>
+<li><p>Depending on how many couch nodes you have, increase the port for every test
+(see /etc/haproxy/haproxy.cfg for the server/port mapping):</p>
+
+<p> curl -s -X GET &ldquo;<a href="http://127.0.0.1:4000">http://127.0.0.1:4000</a>&rdquo;
+ curl -s -X GET &ldquo;<a href="http://127.0.0.1:4001">http://127.0.0.1:4001</a>&rdquo;
+ &hellip;</p></li>
+</ul>
+
+
+<h2><a name="check-couchdb-acl-as-admin"></a>Check couchdb acl as admin</h2>
+
+<pre><code>mkdir /etc/couchdb
+cat /srv/leap/webapp/config/couchdb.yml.admin # see username and password
+echo "machine 127.0.0.1 login admin password &lt;PASSWORD&gt;" &gt; /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"
+</code></pre>
+
+<h2><a name="check-couchdb-acl-as-unpriviledged-user"></a>Check couchdb acl as unpriviledged user</h2>
+
+<pre><code>cat /srv/leap/webapp/config/couchdb.yml # see username and password
+echo "machine 127.0.0.1 login webapp password &lt;PASSWORD&gt;" &gt; /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"
+</code></pre>
+
+<h2><a name="all-urls-accessible"></a>All URLs accessible ?</h2>
+
+<ul>
+<li><a href="https://example.org">https://example.org</a></li>
+<li><a href="https://api.example.org:4430/provider.json">https://api.example.org:4430/provider.json</a></li>
+<li><a href="https://example.org/ca.crt">https://example.org/ca.crt</a></li>
+</ul>
+
+
+<h2><a name="check-client-config-files"></a>Check client config files</h2>
+
+<ul>
+<li><a href="https://example.net/provider.json">https://example.net/provider.json</a></li>
+<li><a href="https://example.net/1/config/smtp-service.json">https://example.net/1/config/smtp-service.json</a></li>
+<li><a href="https://example.net/1/config/soledad-service.json">https://example.net/1/config/soledad-service.json</a></li>
+<li><a href="https://example.net/1/config/eip-service.json">https://example.net/1/config/eip-service.json</a></li>
+</ul>
+
+
+<h1><a name="soledad"></a>Soledad</h1>
+
+<pre><code>/var/log/soledad.log
+</code></pre>
+
+<h1><a name="couchdb"></a>Couchdb</h1>
+
+<h2><a name="places-to-look-for-errors-2"></a>Places to look for errors</h2>
+
+<ul>
+<li><code>/var/log/couchdb/couch.log</code></li>
+<li><code>/var/log/syslog</code> (watch out for stunnel issues)</li>
+</ul>
+
+
+<h2><a name="databases"></a>Databases</h2>
+
+<ul>
+<li>Following output shows all neccessary DBs that should be present. Note that the <code>user-0123456....</code> DBs are the data stores for a particular user.</li>
+</ul>
+
+
+<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>
+
+
+<h2><a name="design-documents"></a>Design Documents</h2>
+
+<ul>
+<li>Is User <code>_design doc</code> available ?</li>
+</ul>
+
+
+<pre>
+ curl -s --netrc-file /etc/couchdb/couchdb.netrc -X GET "http://127.0.0.1:5984/users/_design/User"
+</pre>
+
+
+<h2><a name="is-couchdb-cluster-backend-accessible-through-stunnel"></a>Is couchdb cluster backend accessible through stunnel ?</h2>
+
+<ul>
+<li>Find out how many connections are set up for the couchdb cluster backend:</li>
+</ul>
+
+
+<pre>
+ grep "accept = 127.0.0.1" /etc/stunnel/*
+</pre>
+
+
+<ul>
+<li>Now connect to all of those local endpoints to see if they up. All these tests should return &ldquo;localhost [127.0.0.1] 4000 (?) open&rdquo;</li>
+</ul>
+
+
+<pre>
+ nc -v 127.0.0.1 4000
+ nc -v 127.0.0.1 4001
+ ...
+</pre>
+
+
+<h1><a name="mx"></a>MX</h1>
+
+<h2><a name="places-to-look-for-errors-3"></a>Places to look for errors</h2>
+
+<ul>
+<li><code>/var/log/mail.log</code></li>
+<li><code>/var/log/leap_mx.log</code></li>
+<li><code>/var/log/syslog</code> (watch out for stunnel issues)</li>
+</ul>
+
+
+<h2><a name="is-couchdb-accessible-through-stunnel-2"></a>Is couchdb accessible through stunnel ?</h2>
+
+<ul>
+<li><p>Depending on how many couch nodes you have, increase the port for every test
+(see /etc/haproxy/haproxy.cfg for the server/port mapping):</p>
+
+<p> curl -s -X GET &ldquo;<a href="http://127.0.0.1:4000">http://127.0.0.1:4000</a>&rdquo;
+ curl -s -X GET &ldquo;<a href="http://127.0.0.1:4001">http://127.0.0.1:4001</a>&rdquo;
+ &hellip;</p></li>
+</ul>
+
+
+<h2><a name="query-leap-mx"></a>Query leap-mx</h2>
+
+<ul>
+<li>for useraccount</li>
+</ul>
+
+
+<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>
+
+
+<ul>
+<li>for mailalias</li>
+</ul>
+
+
+<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>
+
+
+<h2><a name="check-couchdb-acl-as-unpriviledged-user-2"></a>Check couchdb acl as unpriviledged user</h2>
+
+<pre><code>cat /etc/leap/mx.conf # see username and password
+echo "machine 127.0.0.1 login leap_mx password &lt;PASSWORD&gt;" &gt; /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-&lt;hash&gt;" db
+curl -s --netrc-file /etc/couchdb/couchdb-leap_mx.netrc -X GET "http://127.0.0.1:4096/user-de9c77a3d7efbc779c6c20da88e8fb9c"
+</code></pre>
+
+<ul>
+<li>you may check multiple times, cause 127.0.0.1:4096 is haproxy load-balancing the different couchdb nodes</li>
+</ul>
+
+
+<h2><a name="mailspool"></a>Mailspool</h2>
+
+<ul>
+<li>Any file in the leap_mx mailspool longer for a few seconds ?</li>
+</ul>
+
+
+<pre>
+ ls -la /var/mail/vmail/Maildir/cur/
+</pre>
+
+
+<ul>
+<li>Any mails in postfix mailspool longer than a few seconds ?</li>
+</ul>
+
+
+<pre>
+ mailq
+</pre>
+
+
+<h2><a name="testing-mail-delivery"></a>Testing mail delivery</h2>
+
+<pre><code>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
+</code></pre>
+
+<h1><a name="vpn"></a>VPN</h1>
+
+<h2><a name="places-to-look-for-errors-4"></a>Places to look for errors</h2>
+
+<ul>
+<li><code>/var/log/syslog</code> (watch out for openvpn issues)</li>
+</ul>
+
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/tutorials.html b/docs/en/tutorials.html
new file mode 100644
index 00000000..3eeac685
--- /dev/null
+++ b/docs/en/tutorials.html
@@ -0,0 +1,138 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Tutorials - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='guide.html'>Guide</a>
+</li>
+<li class='active level0'>
+<a class='' href='tutorials.html'>Tutorials</a>
+</li>
+<li class=' level1'>
+<a class='' href='tutorials/quick-start.html'>Quick Start Tutorial</a>
+</li>
+<li class=' level1'>
+<a class='' href='tutorials/single-node-vpn.html'>Quick VPN</a>
+</li>
+<li class=' level1'>
+<a class='' href='tutorials/single-node-email.html'>Quick email</a>
+</li>
+<li class=' level1'>
+<a class='' href='tutorials/vagrant.html'>Vagrant</a>
+</li>
+<li class=' level0'>
+<a class='' href='services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Platform Tutorials</h1>
+
+<div id='summary'>The LEAP Platform is set of complementary packages and server recipes to automate the maintenance of LEAP services in a hardened Debian environment.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+</ol></div>
+
+<div class=' page-summary'>
+ <h2>
+ <a href='tutorials/quick-start.html'>Quick Start Tutorial</a>
+ </h2>
+ <div class='summary'>This tutorial walks you through the initial process of creating and deploying a minimal service provider running the LEAP Platform.</div>
+</div>
+<div class=' page-summary'>
+ <h2>
+ <a href='tutorials/single-node-vpn.html'>Quick VPN</a>
+ </h2>
+ <div class='summary'>Tutorial for setting up a simple VPN provider.</div>
+</div>
+<div class=' page-summary'>
+ <h2>
+ <a href='tutorials/single-node-email.html'>Quick email</a>
+ </h2>
+ <div class='summary'>Tutorial for setting up a simple email provider.</div>
+</div>
+<div class=' page-summary'>
+ <h2>
+ <a href='tutorials/vagrant.html'>Vagrant</a>
+ </h2>
+ <div class='summary'>Running a local provider with Vagrant</div>
+</div>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/tutorials/quick-start.html b/docs/en/tutorials/quick-start.html
new file mode 100644
index 00000000..d2670b30
--- /dev/null
+++ b/docs/en/tutorials/quick-start.html
@@ -0,0 +1,446 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Quick Start Tutorial - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class='active level1'>
+<a class='' href='quick-start.html'>Quick Start Tutorial</a>
+</li>
+<li class=' level1'>
+<a class='' href='single-node-vpn.html'>Quick VPN</a>
+</li>
+<li class=' level1'>
+<a class='' href='single-node-email.html'>Quick email</a>
+</li>
+<li class=' level1'>
+<a class='' href='vagrant.html'>Vagrant</a>
+</li>
+<li class=' level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Quick Start Tutorial</h1>
+
+<div id='summary'>This tutorial walks you through the initial process of creating and deploying a minimal service provider running the LEAP Platform.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="quick-start/index.html#introduction">Introduction</a>
+ <ol>
+ <li>
+ <a href="quick-start/index.html#our-goal">Our goal</a>
+ </li>
+ <li>
+ <a href="quick-start/index.html#requirements">Requirements</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="quick-start/index.html#prepare-your-workstation">Prepare your workstation</a>
+ <ol>
+ <li>
+ <a href="quick-start/index.html#install-pre-requisites">Install pre-requisites</a>
+ </li>
+ <li>
+ <a href="quick-start/index.html#install-the-leap-command-line-utility">Install the LEAP command-line utility</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="quick-start/index.html#create-a-provider-instance">Create a provider instance</a>
+ </li>
+ <li>
+ <a href="quick-start/index.html#add-a-node-to-the-provider">Add a node to the provider</a>
+ <ol>
+ <li>
+ <a href="quick-start/index.html#option-a-add-a-real-node">Option A: Add a real node</a>
+ </li>
+ <li>
+ <a href="quick-start/index.html#option-b-add-a-local-node">Option B: Add a local node</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="quick-start/index.html#deploy-your-provider">Deploy your provider</a>
+ <ol>
+ <li>
+ <a href="quick-start/index.html#initialize-the-node">Initialize the node</a>
+ </li>
+ <li>
+ <a href="quick-start/index.html#deploy-to-the-node">Deploy to the node</a>
+ </li>
+ <li>
+ <a href="quick-start/index.html#setup-dns">Setup DNS</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="quick-start/index.html#test-that-things-worked-correctly">Test that things worked correctly</a>
+ </li>
+ <li>
+ <a href="quick-start/index.html#create-an-administrator">Create an administrator</a>
+ </li>
+ <li>
+ <a href="quick-start/index.html#what-is-next">What is next?</a>
+ <ol>
+ <li>
+ <a href="quick-start/index.html#add-an-end-user-service">Add an end-user service</a>
+ </li>
+ <li>
+ <a href="quick-start/index.html#learn-more">Learn more</a>
+ </li>
+ </ol>
+ </li>
+</ol></div>
+
+<h1><a name="introduction"></a>Introduction</h1>
+
+<h3><a name="our-goal"></a>Our goal</h3>
+
+<p>We are going to create a minimal LEAP provider, but one that does not offer any actual services. Check out the other tutorials for adding VPN or email services.</p>
+
+<p>Our goal is something like this:</p>
+
+<pre><code>$ leap list
+ NODES SERVICES TAGS
+ wildebeest couchdb, webapp
+</code></pre>
+
+<p>NOTE: You won&rsquo;t be able to run that <code>leap list</code> command yet, not until we actually create the node configurations.</p>
+
+<h3><a name="requirements"></a>Requirements</h3>
+
+<ol>
+<li>A workstation: This is your local machine that you will run commands on.</li>
+<li>A server: This is the machine that you will deploy to. The server can be either:
+
+<ol>
+<li>A local Vagrant virtual machine: a Vagrant machine can only be useful for testing.</li>
+<li>A real or paravirtualized server: The server must have Debian Jessie installed, and you must be able to SSH into the machine as root. Paravirtualization includes KVM, Xen, OpenStack, Amazon, but not VirtualBox or OpenVZ.</li>
+</ol>
+</li>
+</ol>
+
+
+<p>Other things to keep in mind:</p>
+
+<ul>
+<li>The ability to create/modify DNS entries for your domain is preferable, but not needed. If you don&rsquo;t have access to DNS, you can workaround this by modifying your local resolver, i.e. editing <code>/etc/hosts</code>.</li>
+<li>You need to be aware that this process will make changes to your servers, so please be sure that these machines are a basic install with nothing configured or running for other purposes.</li>
+<li>Your servers will need to be connected to the internet, and not behind a restrictive firewall.</li>
+</ul>
+
+
+<h1><a name="prepare-your-workstation"></a>Prepare your workstation</h1>
+
+<p>In order to be able to manage your servers, you need to install the <code>leap</code> command on your workstation:</p>
+
+<h3><a name="install-pre-requisites"></a>Install pre-requisites</h3>
+
+<p>Install core prerequisites on your workstation.</p>
+
+<p><em>Debian &amp; Ubuntu</em></p>
+
+<pre><code>workstation$ sudo apt-get install git ruby ruby-dev rsync openssh-client openssl rake make bzip2
+</code></pre>
+
+<p><em>Mac OS</em></p>
+
+<pre><code>workstation$ brew install ruby-install
+workstation$ ruby-install ruby
+</code></pre>
+
+<h3><a name="install-the-leap-command-line-utility"></a>Install the LEAP command-line utility</h3>
+
+<p>Install the <code>leap</code> command system-wide:</p>
+
+<pre><code>workstation$ sudo gem install leap_cli
+</code></pre>
+
+<p>Alternately, you can install <code>leap</code> locally without root privileges:</p>
+
+<pre><code>workstation$ gem install --user-install leap_cli
+workstation$ PATH="$PATH:$(ruby -e 'puts Gem.user_dir')/bin"
+</code></pre>
+
+<p>If you choose a local install, you probably want to permanently add the &ndash;user-install directory to your PATH by adding this to your <code>~/.profile</code> file (requires logout):</p>
+
+<pre><code>[ $(which ruby) ] &amp;&amp; PATH="$PATH:$(ruby -e 'puts Gem.user_dir')/bin"
+</code></pre>
+
+<p>To confirm that you installed <code>leap</code> correctly, try running <code>leap --version</code>.</p>
+
+<h1><a name="create-a-provider-instance"></a>Create a provider instance</h1>
+
+<p>A provider instance is a directory tree, residing on your workstation, that contains everything you need to manage an infrastructure for a service provider.</p>
+
+<p>In this case, we create one for example.org and call the instance directory &lsquo;example&rsquo;.</p>
+
+<pre><code>workstation$ leap new ~/example
+</code></pre>
+
+<p>The <code>leap new</code> command will ask you for several required values:</p>
+
+<ul>
+<li>domain: The primary domain name of your service provider. In this tutorial, we will be using &ldquo;example.org&rdquo;.</li>
+<li>name: The name of your service provider (we use &ldquo;Example&rdquo;).</li>
+<li>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).</li>
+<li>platform: The directory where you have a copy of the <code>leap_platform</code> git repository checked out. If the platform directory does not yet exist, the <code>leap_platform</code> will be downloaded and placed in that directory.</li>
+</ul>
+
+
+<p>You could also have passed these configuration options on the command-line, like so:</p>
+
+<pre><code>workstation$ leap new --contacts your@email.here --domain example.org --name Example --platform=~/leap/leap_platform .
+</code></pre>
+
+<p>You should now have the following files:</p>
+
+<pre><code>workstation$ tree example
+example
+├── common.json
+├── Leapfile
+├── nodes/
+├── provider.json
+├── services/
+└── tags/
+</code></pre>
+
+<p>Now add yourself as a privileged sysadmin who will have access to deploy to servers:</p>
+
+<pre><code>workstation$ cd example
+workstation$ leap add-user louise --self
+</code></pre>
+
+<p>Replace &ldquo;louise&rdquo; with whatever you want your sysadmin username to be.</p>
+
+<p>NOTE: Make sure you change directories so that the <code>leap</code> command is run from within the provider instance directory. Most <code>leap</code> commands only work when run from a provider instance.</p>
+
+<p>Now create the necessary keys and certificates:</p>
+
+<pre><code>workstation$ leap cert ca
+workstation$ leap cert csr
+</code></pre>
+
+<p>What do these commands do? The first command will create two Certificate Authorities, one that clients will use to authenticate with the servers and one for backend servers to authenticate with each other. The second command creates a Certificate Signing Request suitable for submission to a commercial CA. It also creates two &ldquo;dummy&rdquo; files for you to use temporarily:</p>
+
+<ul>
+<li><code>files/cert/example.org.crt</code> &ndash; This is a &ldquo;dummy&rdquo; certificate for your domain that can be used temporarily for testing. Once you get a real certificate from a CA, you should replace this file.</li>
+<li><code>files/cert/commercial_ca.crt</code> &ndash; This is &ldquo;dummy&rdquo; CA cert the corresponds to the dummy domain certificate. Once you replace the domain certificate, also replace this file with the CA cert from the real Certificate Authority.</li>
+</ul>
+
+
+<p>If you plan to run a real service provider, see important information on <a href="../guide/keys-and-certificates.html">managing keys and certificates</a>.</p>
+
+<h1><a name="add-a-node-to-the-provider"></a>Add a node to the provider</h1>
+
+<p>A &ldquo;node&rdquo; is a server that is part of your infrastructure. Every node can have one or more services associated with it. We will now add a single node with two services, &ldquo;webapp&rdquo; and &ldquo;couchdb&rdquo;.</p>
+
+<p>You have two choices for node type: a real node or a local node.</p>
+
+<ul>
+<li>Real Node: A real node is any physical or paravirtualized server, including KVM, Xen, OpenStack Compute, Amazon EC2, but not VirtualBox or OpenVZ (VirtualBox and OpenVZ use a more limited form of virtualization). The server must be running Debian Jessie.</li>
+<li>Local Node: A local node is a virtual machine created by Vagrant, useful for local testing on your workstation.</li>
+</ul>
+
+
+<p>Getting Vagrant working can be a pain and is <a href="vagrant.html">covered in other tutorials</a>. If you have a real server available, we suggest you try this tutorial with a real node first.</p>
+
+<h3><a name="option-a-add-a-real-node"></a>Option A: Add a real node</h3>
+
+<p>Note: Installing LEAP Platform on this server will potentially destroy anything you have previously installed on this machine.</p>
+
+<p>Create a node, with the services &ldquo;webapp&rdquo; and &ldquo;couchdb&rdquo;:</p>
+
+<pre><code>workstation$ leap node add wildebeest ip_address:x.x.x.w services:webapp,couchdb
+</code></pre>
+
+<p>NOTE: replace x.x.x.x with the actual IP address of this server.</p>
+
+<h3><a name="option-b-add-a-local-node"></a>Option B: Add a local node</h3>
+
+<p>Create a node, with the services &ldquo;webapp&rdquo; and &ldquo;couchdb&rdquo;, and then start the local virtual machine:</p>
+
+<pre><code>workstation$ leap node add --local wildebeest services:webapp,couchdb
+workstation$ leap local start wildebeest
+</code></pre>
+
+<p>It will take a while to download the Virtualbox base box and create the virtual machine.</p>
+
+<h1><a name="deploy-your-provider"></a>Deploy your provider</h1>
+
+<h3><a name="initialize-the-node"></a>Initialize the node</h3>
+
+<p>Node initialization only needs to be done once, but there is no harm in doing it multiple times:</p>
+
+<pre><code>workstation$ leap node init wildebeest
+</code></pre>
+
+<p>This will initialize the node <code>wildebeest</code>.</p>
+
+<p>For non-local nodes, when <code>leap node init</code> 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.</p>
+
+<h3><a name="deploy-to-the-node"></a>Deploy to the node</h3>
+
+<p>The next step is to deploy the LEAP platform to your node. <a href="https://xkcd.com/303/">Deployment can take a while to run</a>, especially on the first run, as it needs to update the packages on the new machine.</p>
+
+<pre><code>workstation$ leap deploy wildebeest
+</code></pre>
+
+<p>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.</p>
+
+<h3><a name="setup-dns"></a>Setup DNS</h3>
+
+<p>The next step is to configure the DNS for your provider. For testing purposes, you can just modify your <code>/etc/hosts</code> file. Please don&rsquo;t forget about these entries, they will override DNS queries if you setup your DNS later. For a list of what entries to add to <code>/etc/hosts</code>, run this command:</p>
+
+<pre><code>workstation$ leap compile hosts
+</code></pre>
+
+<p>Alternately, if you have access to modify the DNS zone entries for your domain:</p>
+
+<pre><code>workstation$ leap compile zone
+</code></pre>
+
+<p>NOTE: The resulting zone file is incomplete because it is missing a serial number. Use the output of <code>leap compile zone</code> as a guide, but do not just copy and paste the output. Also, the <code>compile zone</code> output will always exclude mention of local nodes.</p>
+
+<p>The DNS method will not work for local nodes created with Vagrant.</p>
+
+<h1><a name="test-that-things-worked-correctly"></a>Test that things worked correctly</h1>
+
+<p>To run troubleshooting tests:</p>
+
+<pre><code>workstation$ leap test
+</code></pre>
+
+<p>Alternately, you can run these same tests from the server itself:</p>
+
+<pre><code>workstation$ leap ssh wildebeest
+wildebeest# run_tests
+</code></pre>
+
+<h1><a name="create-an-administrator"></a>Create an administrator</h1>
+
+<p>Assuming that you set up your DNS or <code>/etc/hosts</code> file, you should be able to load <code>https://example.org</code> in your web browser (where example.org is whatever domain name you actually used).</p>
+
+<p>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.</p>
+
+<p>Once you have created a user, you can now make this user an administrator. For example, if you created a user <code>kangaroo</code>, you would create the file <code>services/webapp.json</code> with the following content:</p>
+
+<pre><code>{
+ "webapp": {
+ "admins": ["kangaroo"]
+ }
+}
+</code></pre>
+
+<p>Save that file and run <code>leap deploy</code> again. When you next log on to the web application, the user kangaroo will now be an admin.</p>
+
+<p>If you want to restrict who can register a new user, see <a href="../services/webapp.html">webapp</a> for configuration options.</p>
+
+<h1><a name="what-is-next"></a>What is next?</h1>
+
+<h2><a name="add-an-end-user-service"></a>Add an end-user service</h2>
+
+<p>You should now have a minimal service provider with a single node. This service provider is pointless at the moment, because it does not include any end-user services like VPN or email. To add one of these services, continue with one of the following tutorials:</p>
+
+<ul>
+<li><a href="single-node-email.html">Quick email</a></li>
+<li><a href="single-node-vpn.html">Quick VPN</a></li>
+</ul>
+
+
+<h2><a name="learn-more"></a>Learn more</h2>
+
+<p>We have only just scratched the surface of the possible ways to configure and deploy your service provider. Your next step should be:</p>
+
+<ul>
+<li>Read <a href="../guide/getting-started.html">Getting Started</a> for more details on using the LEAP platform.</li>
+<li>See <a href="../guide/commands.html">Command Line Reference</a> for a list of possible commands.</li>
+</ul>
+
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/tutorials/quick-start/index.html b/docs/en/tutorials/quick-start/index.html
new file mode 100644
index 00000000..27b21238
--- /dev/null
+++ b/docs/en/tutorials/quick-start/index.html
@@ -0,0 +1,446 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Quick Start Tutorial - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../guide.html'>Guide</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../../tutorials.html'>Tutorials</a>
+</li>
+<li class='active level1'>
+<a class='' href='../quick-start.html'>Quick Start Tutorial</a>
+</li>
+<li class=' level1'>
+<a class='' href='../single-node-vpn.html'>Quick VPN</a>
+</li>
+<li class=' level1'>
+<a class='' href='../single-node-email.html'>Quick email</a>
+</li>
+<li class=' level1'>
+<a class='' href='../vagrant.html'>Vagrant</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Quick Start Tutorial</h1>
+
+<div id='summary'>This tutorial walks you through the initial process of creating and deploying a minimal service provider running the LEAP Platform.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="index.html#introduction">Introduction</a>
+ <ol>
+ <li>
+ <a href="index.html#our-goal">Our goal</a>
+ </li>
+ <li>
+ <a href="index.html#requirements">Requirements</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#prepare-your-workstation">Prepare your workstation</a>
+ <ol>
+ <li>
+ <a href="index.html#install-pre-requisites">Install pre-requisites</a>
+ </li>
+ <li>
+ <a href="index.html#install-the-leap-command-line-utility">Install the LEAP command-line utility</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#create-a-provider-instance">Create a provider instance</a>
+ </li>
+ <li>
+ <a href="index.html#add-a-node-to-the-provider">Add a node to the provider</a>
+ <ol>
+ <li>
+ <a href="index.html#option-a-add-a-real-node">Option A: Add a real node</a>
+ </li>
+ <li>
+ <a href="index.html#option-b-add-a-local-node">Option B: Add a local node</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#deploy-your-provider">Deploy your provider</a>
+ <ol>
+ <li>
+ <a href="index.html#initialize-the-node">Initialize the node</a>
+ </li>
+ <li>
+ <a href="index.html#deploy-to-the-node">Deploy to the node</a>
+ </li>
+ <li>
+ <a href="index.html#setup-dns">Setup DNS</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#test-that-things-worked-correctly">Test that things worked correctly</a>
+ </li>
+ <li>
+ <a href="index.html#create-an-administrator">Create an administrator</a>
+ </li>
+ <li>
+ <a href="index.html#what-is-next">What is next?</a>
+ <ol>
+ <li>
+ <a href="index.html#add-an-end-user-service">Add an end-user service</a>
+ </li>
+ <li>
+ <a href="index.html#learn-more">Learn more</a>
+ </li>
+ </ol>
+ </li>
+</ol></div>
+
+<h1><a name="introduction"></a>Introduction</h1>
+
+<h3><a name="our-goal"></a>Our goal</h3>
+
+<p>We are going to create a minimal LEAP provider, but one that does not offer any actual services. Check out the other tutorials for adding VPN or email services.</p>
+
+<p>Our goal is something like this:</p>
+
+<pre><code>$ leap list
+ NODES SERVICES TAGS
+ wildebeest couchdb, webapp
+</code></pre>
+
+<p>NOTE: You won&rsquo;t be able to run that <code>leap list</code> command yet, not until we actually create the node configurations.</p>
+
+<h3><a name="requirements"></a>Requirements</h3>
+
+<ol>
+<li>A workstation: This is your local machine that you will run commands on.</li>
+<li>A server: This is the machine that you will deploy to. The server can be either:
+
+<ol>
+<li>A local Vagrant virtual machine: a Vagrant machine can only be useful for testing.</li>
+<li>A real or paravirtualized server: The server must have Debian Jessie installed, and you must be able to SSH into the machine as root. Paravirtualization includes KVM, Xen, OpenStack, Amazon, but not VirtualBox or OpenVZ.</li>
+</ol>
+</li>
+</ol>
+
+
+<p>Other things to keep in mind:</p>
+
+<ul>
+<li>The ability to create/modify DNS entries for your domain is preferable, but not needed. If you don&rsquo;t have access to DNS, you can workaround this by modifying your local resolver, i.e. editing <code>/etc/hosts</code>.</li>
+<li>You need to be aware that this process will make changes to your servers, so please be sure that these machines are a basic install with nothing configured or running for other purposes.</li>
+<li>Your servers will need to be connected to the internet, and not behind a restrictive firewall.</li>
+</ul>
+
+
+<h1><a name="prepare-your-workstation"></a>Prepare your workstation</h1>
+
+<p>In order to be able to manage your servers, you need to install the <code>leap</code> command on your workstation:</p>
+
+<h3><a name="install-pre-requisites"></a>Install pre-requisites</h3>
+
+<p>Install core prerequisites on your workstation.</p>
+
+<p><em>Debian &amp; Ubuntu</em></p>
+
+<pre><code>workstation$ sudo apt-get install git ruby ruby-dev rsync openssh-client openssl rake make bzip2
+</code></pre>
+
+<p><em>Mac OS</em></p>
+
+<pre><code>workstation$ brew install ruby-install
+workstation$ ruby-install ruby
+</code></pre>
+
+<h3><a name="install-the-leap-command-line-utility"></a>Install the LEAP command-line utility</h3>
+
+<p>Install the <code>leap</code> command system-wide:</p>
+
+<pre><code>workstation$ sudo gem install leap_cli
+</code></pre>
+
+<p>Alternately, you can install <code>leap</code> locally without root privileges:</p>
+
+<pre><code>workstation$ gem install --user-install leap_cli
+workstation$ PATH="$PATH:$(ruby -e 'puts Gem.user_dir')/bin"
+</code></pre>
+
+<p>If you choose a local install, you probably want to permanently add the &ndash;user-install directory to your PATH by adding this to your <code>~/.profile</code> file (requires logout):</p>
+
+<pre><code>[ $(which ruby) ] &amp;&amp; PATH="$PATH:$(ruby -e 'puts Gem.user_dir')/bin"
+</code></pre>
+
+<p>To confirm that you installed <code>leap</code> correctly, try running <code>leap --version</code>.</p>
+
+<h1><a name="create-a-provider-instance"></a>Create a provider instance</h1>
+
+<p>A provider instance is a directory tree, residing on your workstation, that contains everything you need to manage an infrastructure for a service provider.</p>
+
+<p>In this case, we create one for example.org and call the instance directory &lsquo;example&rsquo;.</p>
+
+<pre><code>workstation$ leap new ~/example
+</code></pre>
+
+<p>The <code>leap new</code> command will ask you for several required values:</p>
+
+<ul>
+<li>domain: The primary domain name of your service provider. In this tutorial, we will be using &ldquo;example.org&rdquo;.</li>
+<li>name: The name of your service provider (we use &ldquo;Example&rdquo;).</li>
+<li>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).</li>
+<li>platform: The directory where you have a copy of the <code>leap_platform</code> git repository checked out. If the platform directory does not yet exist, the <code>leap_platform</code> will be downloaded and placed in that directory.</li>
+</ul>
+
+
+<p>You could also have passed these configuration options on the command-line, like so:</p>
+
+<pre><code>workstation$ leap new --contacts your@email.here --domain example.org --name Example --platform=~/leap/leap_platform .
+</code></pre>
+
+<p>You should now have the following files:</p>
+
+<pre><code>workstation$ tree example
+example
+├── common.json
+├── Leapfile
+├── nodes/
+├── provider.json
+├── services/
+└── tags/
+</code></pre>
+
+<p>Now add yourself as a privileged sysadmin who will have access to deploy to servers:</p>
+
+<pre><code>workstation$ cd example
+workstation$ leap add-user louise --self
+</code></pre>
+
+<p>Replace &ldquo;louise&rdquo; with whatever you want your sysadmin username to be.</p>
+
+<p>NOTE: Make sure you change directories so that the <code>leap</code> command is run from within the provider instance directory. Most <code>leap</code> commands only work when run from a provider instance.</p>
+
+<p>Now create the necessary keys and certificates:</p>
+
+<pre><code>workstation$ leap cert ca
+workstation$ leap cert csr
+</code></pre>
+
+<p>What do these commands do? The first command will create two Certificate Authorities, one that clients will use to authenticate with the servers and one for backend servers to authenticate with each other. The second command creates a Certificate Signing Request suitable for submission to a commercial CA. It also creates two &ldquo;dummy&rdquo; files for you to use temporarily:</p>
+
+<ul>
+<li><code>files/cert/example.org.crt</code> &ndash; This is a &ldquo;dummy&rdquo; certificate for your domain that can be used temporarily for testing. Once you get a real certificate from a CA, you should replace this file.</li>
+<li><code>files/cert/commercial_ca.crt</code> &ndash; This is &ldquo;dummy&rdquo; CA cert the corresponds to the dummy domain certificate. Once you replace the domain certificate, also replace this file with the CA cert from the real Certificate Authority.</li>
+</ul>
+
+
+<p>If you plan to run a real service provider, see important information on <a href="../../guide/keys-and-certificates.html">managing keys and certificates</a>.</p>
+
+<h1><a name="add-a-node-to-the-provider"></a>Add a node to the provider</h1>
+
+<p>A &ldquo;node&rdquo; is a server that is part of your infrastructure. Every node can have one or more services associated with it. We will now add a single node with two services, &ldquo;webapp&rdquo; and &ldquo;couchdb&rdquo;.</p>
+
+<p>You have two choices for node type: a real node or a local node.</p>
+
+<ul>
+<li>Real Node: A real node is any physical or paravirtualized server, including KVM, Xen, OpenStack Compute, Amazon EC2, but not VirtualBox or OpenVZ (VirtualBox and OpenVZ use a more limited form of virtualization). The server must be running Debian Jessie.</li>
+<li>Local Node: A local node is a virtual machine created by Vagrant, useful for local testing on your workstation.</li>
+</ul>
+
+
+<p>Getting Vagrant working can be a pain and is <a href="../vagrant.html">covered in other tutorials</a>. If you have a real server available, we suggest you try this tutorial with a real node first.</p>
+
+<h3><a name="option-a-add-a-real-node"></a>Option A: Add a real node</h3>
+
+<p>Note: Installing LEAP Platform on this server will potentially destroy anything you have previously installed on this machine.</p>
+
+<p>Create a node, with the services &ldquo;webapp&rdquo; and &ldquo;couchdb&rdquo;:</p>
+
+<pre><code>workstation$ leap node add wildebeest ip_address:x.x.x.w services:webapp,couchdb
+</code></pre>
+
+<p>NOTE: replace x.x.x.x with the actual IP address of this server.</p>
+
+<h3><a name="option-b-add-a-local-node"></a>Option B: Add a local node</h3>
+
+<p>Create a node, with the services &ldquo;webapp&rdquo; and &ldquo;couchdb&rdquo;, and then start the local virtual machine:</p>
+
+<pre><code>workstation$ leap node add --local wildebeest services:webapp,couchdb
+workstation$ leap local start wildebeest
+</code></pre>
+
+<p>It will take a while to download the Virtualbox base box and create the virtual machine.</p>
+
+<h1><a name="deploy-your-provider"></a>Deploy your provider</h1>
+
+<h3><a name="initialize-the-node"></a>Initialize the node</h3>
+
+<p>Node initialization only needs to be done once, but there is no harm in doing it multiple times:</p>
+
+<pre><code>workstation$ leap node init wildebeest
+</code></pre>
+
+<p>This will initialize the node <code>wildebeest</code>.</p>
+
+<p>For non-local nodes, when <code>leap node init</code> 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.</p>
+
+<h3><a name="deploy-to-the-node"></a>Deploy to the node</h3>
+
+<p>The next step is to deploy the LEAP platform to your node. <a href="https://xkcd.com/303/">Deployment can take a while to run</a>, especially on the first run, as it needs to update the packages on the new machine.</p>
+
+<pre><code>workstation$ leap deploy wildebeest
+</code></pre>
+
+<p>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.</p>
+
+<h3><a name="setup-dns"></a>Setup DNS</h3>
+
+<p>The next step is to configure the DNS for your provider. For testing purposes, you can just modify your <code>/etc/hosts</code> file. Please don&rsquo;t forget about these entries, they will override DNS queries if you setup your DNS later. For a list of what entries to add to <code>/etc/hosts</code>, run this command:</p>
+
+<pre><code>workstation$ leap compile hosts
+</code></pre>
+
+<p>Alternately, if you have access to modify the DNS zone entries for your domain:</p>
+
+<pre><code>workstation$ leap compile zone
+</code></pre>
+
+<p>NOTE: The resulting zone file is incomplete because it is missing a serial number. Use the output of <code>leap compile zone</code> as a guide, but do not just copy and paste the output. Also, the <code>compile zone</code> output will always exclude mention of local nodes.</p>
+
+<p>The DNS method will not work for local nodes created with Vagrant.</p>
+
+<h1><a name="test-that-things-worked-correctly"></a>Test that things worked correctly</h1>
+
+<p>To run troubleshooting tests:</p>
+
+<pre><code>workstation$ leap test
+</code></pre>
+
+<p>Alternately, you can run these same tests from the server itself:</p>
+
+<pre><code>workstation$ leap ssh wildebeest
+wildebeest# run_tests
+</code></pre>
+
+<h1><a name="create-an-administrator"></a>Create an administrator</h1>
+
+<p>Assuming that you set up your DNS or <code>/etc/hosts</code> file, you should be able to load <code>https://example.org</code> in your web browser (where example.org is whatever domain name you actually used).</p>
+
+<p>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.</p>
+
+<p>Once you have created a user, you can now make this user an administrator. For example, if you created a user <code>kangaroo</code>, you would create the file <code>services/webapp.json</code> with the following content:</p>
+
+<pre><code>{
+ "webapp": {
+ "admins": ["kangaroo"]
+ }
+}
+</code></pre>
+
+<p>Save that file and run <code>leap deploy</code> again. When you next log on to the web application, the user kangaroo will now be an admin.</p>
+
+<p>If you want to restrict who can register a new user, see <a href="../../services/webapp.html">webapp</a> for configuration options.</p>
+
+<h1><a name="what-is-next"></a>What is next?</h1>
+
+<h2><a name="add-an-end-user-service"></a>Add an end-user service</h2>
+
+<p>You should now have a minimal service provider with a single node. This service provider is pointless at the moment, because it does not include any end-user services like VPN or email. To add one of these services, continue with one of the following tutorials:</p>
+
+<ul>
+<li><a href="../single-node-email.html">Quick email</a></li>
+<li><a href="../single-node-vpn.html">Quick VPN</a></li>
+</ul>
+
+
+<h2><a name="learn-more"></a>Learn more</h2>
+
+<p>We have only just scratched the surface of the possible ways to configure and deploy your service provider. Your next step should be:</p>
+
+<ul>
+<li>Read <a href="../../guide/getting-started.html">Getting Started</a> for more details on using the LEAP platform.</li>
+<li>See <a href="../../guide/commands.html">Command Line Reference</a> for a list of possible commands.</li>
+</ul>
+
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/tutorials/single-node-email.html b/docs/en/tutorials/single-node-email.html
new file mode 100644
index 00000000..6678fec3
--- /dev/null
+++ b/docs/en/tutorials/single-node-email.html
@@ -0,0 +1,200 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Quick email - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level1'>
+<a class='' href='quick-start.html'>Quick Start Tutorial</a>
+</li>
+<li class=' level1'>
+<a class='' href='single-node-vpn.html'>Quick VPN</a>
+</li>
+<li class='active level1'>
+<a class='' href='single-node-email.html'>Quick email</a>
+</li>
+<li class=' level1'>
+<a class='' href='vagrant.html'>Vagrant</a>
+</li>
+<li class=' level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Single node email tutorial</h1>
+
+<div id='summary'>Tutorial for setting up a simple email provider.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="single-node-email/index.html#our-goal">Our goal</a>
+ </li>
+ <li>
+ <a href="single-node-email/index.html#add-email-services-to-the-node">Add email services to the node</a>
+ </li>
+ <li>
+ <a href="single-node-email/index.html#deploy-to-the-node">Deploy to the node</a>
+ </li>
+ <li>
+ <a href="single-node-email/index.html#setup-dns">Setup DNS</a>
+ </li>
+ <li>
+ <a href="single-node-email/index.html#test-it-out">Test it out</a>
+ </li>
+</ol></div>
+
+<p>This tutorial walks you through the initial process of creating and deploying a minimal email service provider. Please first complete the <a href="quick-start.html">Quick Start Tutorial</a>. This tutorial will pick up where that one left off.</p>
+
+<h2><a name="our-goal"></a>Our goal</h2>
+
+<p>We are going to create a minimal LEAP provider offering email service.</p>
+
+<p>Our goal is something like this:</p>
+
+<pre><code>$ leap list
+ NODES SERVICES TAGS
+ wildebeest couchdb, mx, soledad, webapp
+</code></pre>
+
+<p>Where &lsquo;wildebeest&rsquo; is whatever name you chose for your node in the <a href="quick-start.html">Quick Start Tutorial</a>.</p>
+
+<h2><a name="add-email-services-to-the-node"></a>Add email services to the node</h2>
+
+<p>In order to add <a href="../services.html">services</a> to a node, edit the node&rsquo;s JSON configuration file.</p>
+
+<p>In our example, we would edit <code>nodes/wildebeest.json</code>:</p>
+
+<pre><code>{
+ "ip_address": "1.1.1.1",
+ "services": ["couchdb", "webapp", "mx", "soledad"]
+}
+</code></pre>
+
+<p>Here, we added <code>mx</code> and <code>soledad</code> to the node&rsquo;s <code>services</code> list. Briefly:</p>
+
+<ul>
+<li><strong>mx</strong>: nodes with the <strong>mx</strong> service will run postfix mail transfer agent, and are able to receive and relay email on behalf of your domain. You can have as many as you want, spread out over as many nodes as you want.</li>
+<li><strong>soledad</strong>: nodes with <strong>soledad</strong> service run the server-side daemon that allows the client to synchronize a user&rsquo;s personal data store among their devices. Currently, <strong>soledad</strong> only runs on nodes that are also <strong>couchdb</strong> nodes.</li>
+</ul>
+
+
+<p>For more details, see the <a href="../services.html">Services</a> overview, or the individual pages for the <a href="../services/mx.html">mx</a> and <a href="../services/soledad.html">soledad</a> services.</p>
+
+<h2><a name="deploy-to-the-node"></a>Deploy to the node</h2>
+
+<p>Now you should deploy to your node.</p>
+
+<pre><code>workstation$ leap deploy
+</code></pre>
+
+<h2><a name="setup-dns"></a>Setup DNS</h2>
+
+<p>There are several important DNS entries that all email providers should have:</p>
+
+<ul>
+<li>SPF (Sender Policy Framework): With SPF, an email provider advertises in their DNS which servers should be allowed to relay email on behalf of your domain.</li>
+<li>DKIM (DomainKey Identified Mail): With DKIM, an email provider is able to vouch for the validity of certain headers in outgoing mail, allowing the receiving provider to have more confidence in these values when processing the message for spam or abuse.</li>
+</ul>
+
+
+<p>In order to take advantage of SPF and DKIM, run this command:</p>
+
+<pre><code>workstation$ leap compile zone
+</code></pre>
+
+<p>Then take the output of that command and merge it with the DNS zone file for your domain.</p>
+
+<p>CAUTION: the output of <code>leap compile zone</code> is not a complete zone file since it is missing a serial number. You will need to manually merge it with your existing zone file.</p>
+
+<h2><a name="test-it-out"></a>Test it out</h2>
+
+<p>First, run:</p>
+
+<pre><code>workstation# leap test
+</code></pre>
+
+<p>Then fire up the bitmask client, register a new user with your provider, and try sending and receiving email.</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/tutorials/single-node-email/index.html b/docs/en/tutorials/single-node-email/index.html
new file mode 100644
index 00000000..45a1264f
--- /dev/null
+++ b/docs/en/tutorials/single-node-email/index.html
@@ -0,0 +1,200 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Quick email - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../guide.html'>Guide</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level1'>
+<a class='' href='../quick-start.html'>Quick Start Tutorial</a>
+</li>
+<li class=' level1'>
+<a class='' href='../single-node-vpn.html'>Quick VPN</a>
+</li>
+<li class='active level1'>
+<a class='' href='../single-node-email.html'>Quick email</a>
+</li>
+<li class=' level1'>
+<a class='' href='../vagrant.html'>Vagrant</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Single node email tutorial</h1>
+
+<div id='summary'>Tutorial for setting up a simple email provider.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="index.html#our-goal">Our goal</a>
+ </li>
+ <li>
+ <a href="index.html#add-email-services-to-the-node">Add email services to the node</a>
+ </li>
+ <li>
+ <a href="index.html#deploy-to-the-node">Deploy to the node</a>
+ </li>
+ <li>
+ <a href="index.html#setup-dns">Setup DNS</a>
+ </li>
+ <li>
+ <a href="index.html#test-it-out">Test it out</a>
+ </li>
+</ol></div>
+
+<p>This tutorial walks you through the initial process of creating and deploying a minimal email service provider. Please first complete the <a href="../quick-start.html">Quick Start Tutorial</a>. This tutorial will pick up where that one left off.</p>
+
+<h2><a name="our-goal"></a>Our goal</h2>
+
+<p>We are going to create a minimal LEAP provider offering email service.</p>
+
+<p>Our goal is something like this:</p>
+
+<pre><code>$ leap list
+ NODES SERVICES TAGS
+ wildebeest couchdb, mx, soledad, webapp
+</code></pre>
+
+<p>Where &lsquo;wildebeest&rsquo; is whatever name you chose for your node in the <a href="../quick-start.html">Quick Start Tutorial</a>.</p>
+
+<h2><a name="add-email-services-to-the-node"></a>Add email services to the node</h2>
+
+<p>In order to add <a href="../../services.html">services</a> to a node, edit the node&rsquo;s JSON configuration file.</p>
+
+<p>In our example, we would edit <code>nodes/wildebeest.json</code>:</p>
+
+<pre><code>{
+ "ip_address": "1.1.1.1",
+ "services": ["couchdb", "webapp", "mx", "soledad"]
+}
+</code></pre>
+
+<p>Here, we added <code>mx</code> and <code>soledad</code> to the node&rsquo;s <code>services</code> list. Briefly:</p>
+
+<ul>
+<li><strong>mx</strong>: nodes with the <strong>mx</strong> service will run postfix mail transfer agent, and are able to receive and relay email on behalf of your domain. You can have as many as you want, spread out over as many nodes as you want.</li>
+<li><strong>soledad</strong>: nodes with <strong>soledad</strong> service run the server-side daemon that allows the client to synchronize a user&rsquo;s personal data store among their devices. Currently, <strong>soledad</strong> only runs on nodes that are also <strong>couchdb</strong> nodes.</li>
+</ul>
+
+
+<p>For more details, see the <a href="../../services.html">Services</a> overview, or the individual pages for the <a href="../../services/mx.html">mx</a> and <a href="../../services/soledad.html">soledad</a> services.</p>
+
+<h2><a name="deploy-to-the-node"></a>Deploy to the node</h2>
+
+<p>Now you should deploy to your node.</p>
+
+<pre><code>workstation$ leap deploy
+</code></pre>
+
+<h2><a name="setup-dns"></a>Setup DNS</h2>
+
+<p>There are several important DNS entries that all email providers should have:</p>
+
+<ul>
+<li>SPF (Sender Policy Framework): With SPF, an email provider advertises in their DNS which servers should be allowed to relay email on behalf of your domain.</li>
+<li>DKIM (DomainKey Identified Mail): With DKIM, an email provider is able to vouch for the validity of certain headers in outgoing mail, allowing the receiving provider to have more confidence in these values when processing the message for spam or abuse.</li>
+</ul>
+
+
+<p>In order to take advantage of SPF and DKIM, run this command:</p>
+
+<pre><code>workstation$ leap compile zone
+</code></pre>
+
+<p>Then take the output of that command and merge it with the DNS zone file for your domain.</p>
+
+<p>CAUTION: the output of <code>leap compile zone</code> is not a complete zone file since it is missing a serial number. You will need to manually merge it with your existing zone file.</p>
+
+<h2><a name="test-it-out"></a>Test it out</h2>
+
+<p>First, run:</p>
+
+<pre><code>workstation# leap test
+</code></pre>
+
+<p>Then fire up the bitmask client, register a new user with your provider, and try sending and receiving email.</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/tutorials/single-node-vpn.html b/docs/en/tutorials/single-node-vpn.html
new file mode 100644
index 00000000..1bfeb937
--- /dev/null
+++ b/docs/en/tutorials/single-node-vpn.html
@@ -0,0 +1,250 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Quick VPN - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level1'>
+<a class='' href='quick-start.html'>Quick Start Tutorial</a>
+</li>
+<li class='active level1'>
+<a class='' href='single-node-vpn.html'>Quick VPN</a>
+</li>
+<li class=' level1'>
+<a class='' href='single-node-email.html'>Quick email</a>
+</li>
+<li class=' level1'>
+<a class='' href='vagrant.html'>Vagrant</a>
+</li>
+<li class=' level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Single node VPN tutorial</h1>
+
+<div id='summary'>Tutorial for setting up a simple VPN provider.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="single-node-vpn/index.html#our-goal">Our goal</a>
+ </li>
+ <li>
+ <a href="single-node-vpn/index.html#add-vpn-service-to-the-node">Add VPN service to the node</a>
+ </li>
+ <li>
+ <a href="single-node-vpn/index.html#add-gateway_address-to-the-node">Add gateway_address to the node</a>
+ </li>
+ <li>
+ <a href="single-node-vpn/index.html#generate-a-diffie-hellman-file">Generate a Diffie-Hellman file</a>
+ </li>
+ <li>
+ <a href="single-node-vpn/index.html#deploy-to-the-node">Deploy to the node</a>
+ </li>
+ <li>
+ <a href="single-node-vpn/index.html#test-it-out">Test it out</a>
+ </li>
+ <li>
+ <a href="single-node-vpn/index.html#what-do-do-next">What do do next</a>
+ </li>
+</ol></div>
+
+<p>This tutorial walks you through the initial process of creating and deploying a minimal VPN service provider. Please first complete the <a href="quick-start.html">Quick Start Tutorial</a>. This tutorial will pick up where that one left off.</p>
+
+<p>NOTE: For the VPN to work, you must use a real or paravirtualized node, not a local Vagrant node.</p>
+
+<h2><a name="our-goal"></a>Our goal</h2>
+
+<p>We are going to create a minimal LEAP provider offering VPN service.</p>
+
+<p>Our goal is something like this:</p>
+
+<pre><code>$ leap list
+ NODES SERVICES TAGS
+ wildebeest couchdb, webapp, openvpn, tor
+</code></pre>
+
+<p>Where &lsquo;wildebeest&rsquo; is whatever name you chose for your node in the <a href="quick-start.html">Quick Start Tutorial</a>.</p>
+
+<h2><a name="add-vpn-service-to-the-node"></a>Add VPN service to the node</h2>
+
+<p>In order to add <a href="../services.html">services</a> to a node, edit the node&rsquo;s JSON configuration file.</p>
+
+<p>In our example, we would edit <code>nodes/wildebeest.json</code>:</p>
+
+<pre><code>{
+ "ip_address": "1.1.1.1",
+ "services": ["couchdb", "webapp", "openvpn", "tor"]
+}
+</code></pre>
+
+<p>Here, we added <code>openvpn</code> and <code>tor</code> to the node&rsquo;s <code>services</code> list. Briefly:</p>
+
+<ul>
+<li><strong>openvpn</strong>: nodes with the <strong>openvpn</strong> service will become OpenVPN gateways that clients connect to in order to proxy their internet connection. You can have as many as you want, spread out over as many nodes as you want.</li>
+<li><strong>tor</strong>: nodes with <strong>tor</strong> service become Tor exit nodes. This is entirely optional, and will add additional bandwidth to your node. If you don&rsquo;t have many VPN users, the added traffic will help create cover traffic for your users. On the down side, this VPN gateway will get flagged as an anonymous proxy and some sites may block traffic from it.</li>
+</ul>
+
+
+<p>For more details, see the <a href="../services.html">Services</a> overview, or the individual pages for the <a href="../services/openvpn.html">openvpn</a> and <a href="../services/tor.html">tor</a> services.</p>
+
+<h2><a name="add-gateway_address-to-the-node"></a>Add gateway_address to the node</h2>
+
+<p>VPN gateways require two different IP addresses:</p>
+
+<ul>
+<li><code>ip_address</code>: This property is used for VPN traffic <strong>egress</strong>. In other words, all VPN traffic appears to come from this IP address. This is also the main IP of the server.</li>
+<li><code>openvpn.gateway_address</code>: This property is used for VPN traffic <strong>ingress</strong>. In other words, clients will connect to this IP address.</li>
+</ul>
+
+
+<p>The node configuration file should now look like this:</p>
+
+<pre><code>{
+ "ip_address": "1.1.1.1",
+ "services": ["couchdb", "webapp", "openvpn", "tor"],
+ "openvpn": {
+ "gateway_address": "2.2.2.2"
+ }
+}
+</code></pre>
+
+<p>Why two different addresses? Without this, the traffic from one VPN user to another would not be encrypted. This is because the routing table of VPN clients must ensure that packets with a destination of the VPN gateway are sent unmodified and don&rsquo;t get passed through the VPN&rsquo;s encryption.</p>
+
+<h2><a name="generate-a-diffie-hellman-file"></a>Generate a Diffie-Hellman file</h2>
+
+<p>Next we need to create a Diffie-Hellman parameter file, used for forward secret OpenVPN ciphers. You only need to do this once.</p>
+
+<pre><code>workstation$ leap cert dh
+</code></pre>
+
+<p>Feel free to erase the resulting DH file and regenerate it as you please.</p>
+
+<h2><a name="deploy-to-the-node"></a>Deploy to the node</h2>
+
+<p>Now you should deploy to your node. This may take a while.</p>
+
+<pre><code>workstation$ leap deploy
+</code></pre>
+
+<p>If the deploy was not successful, try to run it again.</p>
+
+<h2><a name="test-it-out"></a>Test it out</h2>
+
+<p>First, run:</p>
+
+<pre><code>workstation$ leap test
+</code></pre>
+
+<p>Then fire up the Bitmask client, register a new user with your provider, and turn on the VPN connection.</p>
+
+<p>Alternately, you can also manually connect to your VPN gateway using OpenVPN on the command line:</p>
+
+<pre><code>workstation$ sudo apt install openvpn
+workstation$ leap test init
+workstation$ sudo openvpn --config test/openvpn/default_unlimited.ovpn
+</code></pre>
+
+<p>Make sure that Bitmask is not connected to the VPN when you run that command.</p>
+
+<p>The name of the test configuration might differ depending on your setup. The test configuration created by <code>leap test init</code> includes a client certificate that will expire, so you may need to re-run <code>leap test init</code> if it has been a while since you last generated the test configuration.</p>
+
+<h2><a name="what-do-do-next"></a>What do do next</h2>
+
+<p>A VPN provider with a single gateway is kind of limited. You can add as many nodes with service <a href="../services/openvpn.html">openvpn</a> as you like. There is no communication among the VPN gateways or with the <a href="../services/webapp.html">webapp</a> or <a href="../services/couchdb.html">couchdb</a> nodes, so there is no issue with scaling out the number of gateways.</p>
+
+<p>For example, add some more nodes:</p>
+
+<pre><code>workstation$ leap node add giraffe ip_address:1.1.1.2 services:openvpn openvpn.gateway_address:2.2.2.3
+workstation$ leap node add rhino ip_address:1.1.1.3 services:openvpn openvpn.gateway_address:2.2.2.4
+workstation$ leap node init giraffe rhino
+workstation$ leap deploy
+</code></pre>
+
+<p>Now you have three VPN gateways.</p>
+
+<p>One consideration is that you should tag each VPN gateway with a <a href="../guide/nodes.html#locations">location</a>. This helps the client determine which VPN gateway it should connect to by default and will allow the user to choose among gateways based on location.</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/tutorials/single-node-vpn/index.html b/docs/en/tutorials/single-node-vpn/index.html
new file mode 100644
index 00000000..adceb66f
--- /dev/null
+++ b/docs/en/tutorials/single-node-vpn/index.html
@@ -0,0 +1,250 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Quick VPN - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../guide.html'>Guide</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level1'>
+<a class='' href='../quick-start.html'>Quick Start Tutorial</a>
+</li>
+<li class='active level1'>
+<a class='' href='../single-node-vpn.html'>Quick VPN</a>
+</li>
+<li class=' level1'>
+<a class='' href='../single-node-email.html'>Quick email</a>
+</li>
+<li class=' level1'>
+<a class='' href='../vagrant.html'>Vagrant</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Single node VPN tutorial</h1>
+
+<div id='summary'>Tutorial for setting up a simple VPN provider.</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="index.html#our-goal">Our goal</a>
+ </li>
+ <li>
+ <a href="index.html#add-vpn-service-to-the-node">Add VPN service to the node</a>
+ </li>
+ <li>
+ <a href="index.html#add-gateway_address-to-the-node">Add gateway_address to the node</a>
+ </li>
+ <li>
+ <a href="index.html#generate-a-diffie-hellman-file">Generate a Diffie-Hellman file</a>
+ </li>
+ <li>
+ <a href="index.html#deploy-to-the-node">Deploy to the node</a>
+ </li>
+ <li>
+ <a href="index.html#test-it-out">Test it out</a>
+ </li>
+ <li>
+ <a href="index.html#what-do-do-next">What do do next</a>
+ </li>
+</ol></div>
+
+<p>This tutorial walks you through the initial process of creating and deploying a minimal VPN service provider. Please first complete the <a href="../quick-start.html">Quick Start Tutorial</a>. This tutorial will pick up where that one left off.</p>
+
+<p>NOTE: For the VPN to work, you must use a real or paravirtualized node, not a local Vagrant node.</p>
+
+<h2><a name="our-goal"></a>Our goal</h2>
+
+<p>We are going to create a minimal LEAP provider offering VPN service.</p>
+
+<p>Our goal is something like this:</p>
+
+<pre><code>$ leap list
+ NODES SERVICES TAGS
+ wildebeest couchdb, webapp, openvpn, tor
+</code></pre>
+
+<p>Where &lsquo;wildebeest&rsquo; is whatever name you chose for your node in the <a href="../quick-start.html">Quick Start Tutorial</a>.</p>
+
+<h2><a name="add-vpn-service-to-the-node"></a>Add VPN service to the node</h2>
+
+<p>In order to add <a href="../../services.html">services</a> to a node, edit the node&rsquo;s JSON configuration file.</p>
+
+<p>In our example, we would edit <code>nodes/wildebeest.json</code>:</p>
+
+<pre><code>{
+ "ip_address": "1.1.1.1",
+ "services": ["couchdb", "webapp", "openvpn", "tor"]
+}
+</code></pre>
+
+<p>Here, we added <code>openvpn</code> and <code>tor</code> to the node&rsquo;s <code>services</code> list. Briefly:</p>
+
+<ul>
+<li><strong>openvpn</strong>: nodes with the <strong>openvpn</strong> service will become OpenVPN gateways that clients connect to in order to proxy their internet connection. You can have as many as you want, spread out over as many nodes as you want.</li>
+<li><strong>tor</strong>: nodes with <strong>tor</strong> service become Tor exit nodes. This is entirely optional, and will add additional bandwidth to your node. If you don&rsquo;t have many VPN users, the added traffic will help create cover traffic for your users. On the down side, this VPN gateway will get flagged as an anonymous proxy and some sites may block traffic from it.</li>
+</ul>
+
+
+<p>For more details, see the <a href="../../services.html">Services</a> overview, or the individual pages for the <a href="../../services/openvpn.html">openvpn</a> and <a href="../../services/tor.html">tor</a> services.</p>
+
+<h2><a name="add-gateway_address-to-the-node"></a>Add gateway_address to the node</h2>
+
+<p>VPN gateways require two different IP addresses:</p>
+
+<ul>
+<li><code>ip_address</code>: This property is used for VPN traffic <strong>egress</strong>. In other words, all VPN traffic appears to come from this IP address. This is also the main IP of the server.</li>
+<li><code>openvpn.gateway_address</code>: This property is used for VPN traffic <strong>ingress</strong>. In other words, clients will connect to this IP address.</li>
+</ul>
+
+
+<p>The node configuration file should now look like this:</p>
+
+<pre><code>{
+ "ip_address": "1.1.1.1",
+ "services": ["couchdb", "webapp", "openvpn", "tor"],
+ "openvpn": {
+ "gateway_address": "2.2.2.2"
+ }
+}
+</code></pre>
+
+<p>Why two different addresses? Without this, the traffic from one VPN user to another would not be encrypted. This is because the routing table of VPN clients must ensure that packets with a destination of the VPN gateway are sent unmodified and don&rsquo;t get passed through the VPN&rsquo;s encryption.</p>
+
+<h2><a name="generate-a-diffie-hellman-file"></a>Generate a Diffie-Hellman file</h2>
+
+<p>Next we need to create a Diffie-Hellman parameter file, used for forward secret OpenVPN ciphers. You only need to do this once.</p>
+
+<pre><code>workstation$ leap cert dh
+</code></pre>
+
+<p>Feel free to erase the resulting DH file and regenerate it as you please.</p>
+
+<h2><a name="deploy-to-the-node"></a>Deploy to the node</h2>
+
+<p>Now you should deploy to your node. This may take a while.</p>
+
+<pre><code>workstation$ leap deploy
+</code></pre>
+
+<p>If the deploy was not successful, try to run it again.</p>
+
+<h2><a name="test-it-out"></a>Test it out</h2>
+
+<p>First, run:</p>
+
+<pre><code>workstation$ leap test
+</code></pre>
+
+<p>Then fire up the Bitmask client, register a new user with your provider, and turn on the VPN connection.</p>
+
+<p>Alternately, you can also manually connect to your VPN gateway using OpenVPN on the command line:</p>
+
+<pre><code>workstation$ sudo apt install openvpn
+workstation$ leap test init
+workstation$ sudo openvpn --config test/openvpn/default_unlimited.ovpn
+</code></pre>
+
+<p>Make sure that Bitmask is not connected to the VPN when you run that command.</p>
+
+<p>The name of the test configuration might differ depending on your setup. The test configuration created by <code>leap test init</code> includes a client certificate that will expire, so you may need to re-run <code>leap test init</code> if it has been a while since you last generated the test configuration.</p>
+
+<h2><a name="what-do-do-next"></a>What do do next</h2>
+
+<p>A VPN provider with a single gateway is kind of limited. You can add as many nodes with service <a href="../../services/openvpn.html">openvpn</a> as you like. There is no communication among the VPN gateways or with the <a href="../../services/webapp.html">webapp</a> or <a href="../../services/couchdb.html">couchdb</a> nodes, so there is no issue with scaling out the number of gateways.</p>
+
+<p>For example, add some more nodes:</p>
+
+<pre><code>workstation$ leap node add giraffe ip_address:1.1.1.2 services:openvpn openvpn.gateway_address:2.2.2.3
+workstation$ leap node add rhino ip_address:1.1.1.3 services:openvpn openvpn.gateway_address:2.2.2.4
+workstation$ leap node init giraffe rhino
+workstation$ leap deploy
+</code></pre>
+
+<p>Now you have three VPN gateways.</p>
+
+<p>One consideration is that you should tag each VPN gateway with a <a href="../../guide/nodes.html#locations">location</a>. This helps the client determine which VPN gateway it should connect to by default and will allow the user to choose among gateways based on location.</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/tutorials/vagrant.html b/docs/en/tutorials/vagrant.html
new file mode 100644
index 00000000..3d4f0520
--- /dev/null
+++ b/docs/en/tutorials/vagrant.html
@@ -0,0 +1,724 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Vagrant - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level1'>
+<a class='' href='quick-start.html'>Quick Start Tutorial</a>
+</li>
+<li class=' level1'>
+<a class='' href='single-node-vpn.html'>Quick VPN</a>
+</li>
+<li class=' level1'>
+<a class='' href='single-node-email.html'>Quick email</a>
+</li>
+<li class='active level1'>
+<a class='' href='vagrant.html'>Vagrant</a>
+</li>
+<li class=' level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Vagrant and the LEAP Platform</h1>
+
+<div id='summary'>Running a local provider with Vagrant</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="vagrant/index.html#what-is-vagrant">What is Vagrant?</a>
+ </li>
+ <li>
+ <a href="vagrant/index.html#install-vagrant">Install Vagrant</a>
+ </li>
+ <li>
+ <a href="vagrant/index.html#vagrant-with-leap-command">Vagrant with leap command</a>
+ <ol>
+ <li>
+ <a href="vagrant/index.html#creating-local-nodes">Creating local nodes</a>
+ </li>
+ <li>
+ <a href="vagrant/index.html#starting-local-nodes">Starting local nodes</a>
+ </li>
+ <li>
+ <a href="vagrant/index.html#useful-local-commands">Useful local commands</a>
+ <ol>
+ <li>
+ <a href="vagrant/index.html#listing-what-machines-are-running">Listing what machines are running</a>
+ </li>
+ <li>
+ <a href="vagrant/index.html#stopping-machines">Stopping machines</a>
+ </li>
+ <li>
+ <a href="vagrant/index.html#connecting-to-machines">Connecting to machines</a>
+ </li>
+ <li>
+ <a href="vagrant/index.html#snapshotting-machines">Snapshotting machines</a>
+ </li>
+ <li>
+ <a href="vagrant/index.html#more-information">More information</a>
+ </li>
+ </ol>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="vagrant/index.html#2-vagrant-with-static-vagrantfile">2. Vagrant with static Vagrantfile</a>
+ <ol>
+ <li>
+ <a href="vagrant/index.html#use-the-bitmask-client-to-do-an-initial-soledad-sync">Use the bitmask client to do an initial soledad sync</a>
+ </li>
+ <li>
+ <a href="vagrant/index.html#testing-email">Testing email</a>
+ </li>
+ <li>
+ <a href="vagrant/index.html#re-run-bitmask-client-to-sync-your-mail">Re-run bitmask client to sync your mail</a>
+ </li>
+ <li>
+ <a href="vagrant/index.html#using-the-webapp">Using the Webapp</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="vagrant/index.html#support-for-libvirt">Support for libvirt</a>
+ <ol>
+ <li>
+ <a href="vagrant/index.html#install-libvirt-plugin">Install libvirt plugin</a>
+ </li>
+ <li>
+ <a href="vagrant/index.html#create-libvirt-pool">Create libvirt pool</a>
+ </li>
+ <li>
+ <a href="vagrant/index.html#force-use-of-libvirt">Force use of libvirt</a>
+ </li>
+ <li>
+ <a href="vagrant/index.html#debugging">Debugging</a>
+ </li>
+ <li>
+ <a href="vagrant/index.html#known-issues">Known issues</a>
+ </li>
+ <li>
+ <a href="vagrant/index.html#useful-commands">Useful commands</a>
+ </li>
+ <li>
+ <a href="vagrant/index.html#shared-folder-support">Shared folder support</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="vagrant/index.html#verify-vagrantboxes">Verify vagrantboxes</a>
+ </li>
+ <li>
+ <a href="vagrant/index.html#troubleshooting">Troubleshooting</a>
+ </li>
+ <li>
+ <a href="vagrant/index.html#additional-notes">Additional notes</a>
+ <ol>
+ <li>
+ <a href="vagrant/index.html#some-useful-plugins">Some useful plugins</a>
+ </li>
+ <li>
+ <a href="vagrant/index.html#limitations">Limitations</a>
+ </li>
+ <li>
+ <a href="vagrant/index.html#known-working-combinations">Known working combinations</a>
+ </li>
+ <li>
+ <a href="vagrant/index.html#issue-reporting">Issue reporting</a>
+ </li>
+ </ol>
+ </li>
+</ol></div>
+
+<h1><a name="what-is-vagrant"></a>What is Vagrant?</h1>
+
+<p><a href="https://www.vagrantup.com">Vagrant</a> is a tool to make it easier to manage virtual machines running on your desktop computer (typically for testing or development purposes). You can use Vagrant to create virtual machines and deploy the LEAP platform locally.</p>
+
+<p>Vagrant can be a pain to get working initially, but this page should help you get through the process. Please make sure you have at least Vagrant v1.5 installed.</p>
+
+<p>There are two ways you can setup LEAP platform using Vagrant.</p>
+
+<ol>
+<li>use the <code>leap</code> command: this will allow you to create multiple virtual machines.</li>
+<li>use static Vagrantfile: there is a static Vagrantfile that is distributed with the <code>leap_platform.git</code>. This only supports a single, pre-configured virtual machine, but can get you started more quickly.</li>
+</ol>
+
+
+<h1><a name="install-vagrant"></a>Install Vagrant</h1>
+
+<p>Requirements:</p>
+
+<ul>
+<li>A real machine with virtualization support in the CPU (VT-x or AMD-V). In other words, not a virtual machine.</li>
+<li>Have at least 4gb of RAM.</li>
+<li>Have a fast internet connection (because you will be downloading a lot of big files, like virtual machine images).</li>
+<li>You should do everything described below as an unprivileged user, and only run those commands as root that are noted with <em>sudo</em> 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.</li>
+</ul>
+
+
+<p><em>Debian &amp; Ubuntu</em></p>
+
+<p>Install core prerequisites:</p>
+
+<pre><code>sudo apt-get install git ruby ruby-dev rsync openssh-client openssl rake make
+</code></pre>
+
+<p>Install Vagrant:</p>
+
+<pre><code>sudo apt-get install vagrant virtualbox
+</code></pre>
+
+<p>If you want to use libvirt instead of virtualbox, you don&rsquo;t need to install virtualbox. See <a href="vagrant/index.html#support-for-libvirt">support for libvirt</a>.</p>
+
+<p><em>Mac OS X 10.9 (Mavericks)</em></p>
+
+<p>Install Homebrew package manager from <a href="http://brew.sh/">http://brew.sh/</a> and enable the <a href="https://github.com/Homebrew/homebrew/wiki/Interesting-Taps-&amp;-Branches">System Duplicates Repository</a> (needed to update old software versions delivered by Apple) with</p>
+
+<pre><code>brew tap homebrew/dupes
+</code></pre>
+
+<p>Update OpenSSH to support ECDSA keys. Follow <a href="http://www.dctrwatson.com/2013/07/how-to-update-openssh-on-mac-os-x/">this guide</a> to let your system use the Homebrew binary.</p>
+
+<pre><code>brew install openssh --with-brewed-openssl --with-keychain-support
+</code></pre>
+
+<p>The certtool provided by Apple it&rsquo;s really old, install the one provided by GnuTLS and shadow the system&rsquo;s default.</p>
+
+<pre><code>sudo brew install gnutls
+ln -sf /usr/local/bin/gnutls-certtool /usr/local/bin/certool
+</code></pre>
+
+<p>Install the Vagrant and VirtualBox packages for OS X from their respective Download pages.</p>
+
+<ul>
+<li><a href="http://www.vagrantup.com/downloads.html">http://www.vagrantup.com/downloads.html</a></li>
+<li><a href="https://www.virtualbox.org/wiki/Downloads">https://www.virtualbox.org/wiki/Downloads</a></li>
+</ul>
+
+
+<h1><a name="vagrant-with-leap-command"></a>Vagrant with leap command</h1>
+
+<p>If you have not done so, install <code>leap</code> command line tool:</p>
+
+<pre><code>gem install leap_cli
+</code></pre>
+
+<h2><a name="creating-local-nodes"></a>Creating local nodes</h2>
+
+<p>When you create a service provider, your servers are called &ldquo;nodes&rdquo;. When a node is virtual and exists only locally using vagrant, this type of node is called a &ldquo;local node&rdquo;.</p>
+
+<p>If you do not have a provider already, you will need to create one and configure it before continuing (see the <a href="vagrant/quick-start.html">Quick Start</a> guide).</p>
+
+<p>These commands, for example, will create an initial provider directory &ldquo;myprovider&rdquo;:</p>
+
+<pre><code>$ leap new --domain example.org --name Example myprovider
+$ cd myprovider
+$ leap add-user --self
+$ leap cert ca
+$ leap cert csr
+</code></pre>
+
+<p>To create local nodes, add the flag <code>--local</code> to the <code>leap node add</code> command. For example:</p>
+
+<pre><code>$ 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
+</code></pre>
+
+<p>This command creates a node configuration file in <code>nodes/web1.json</code> with the webapp service.</p>
+
+<h2><a name="starting-local-nodes"></a>Starting local nodes</h2>
+
+<p>In order to test the node &ldquo;web1&rdquo; 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&rsquo;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).</p>
+
+<p>NOTE: Many people have difficulties getting Vagrant working. If the following commands do not work, please see the troubleshooting section below.</p>
+
+<pre><code>$ leap local start web1
+ = created test/
+ = created test/Vagrantfile
+ = installing vagrant plugin 'sahara'
+Bringing machine 'web1' up with 'virtualbox' provider...
+[web1] Box 'leap-jessie' 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-jessie'...
+0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
+</code></pre>
+
+<p>Now the virtual machine &lsquo;web1&rsquo; is running. You can add another local node using the same process. For example, the webapp node needs a databasse to run, so let&rsquo;s add a &ldquo;couchdb&rdquo; node:</p>
+
+<pre><code>$ 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-jessie'...
+[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 =&gt; 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 =&gt; 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
+</code></pre>
+
+<p>You now can follow the normal LEAP process and initialize it and then deploy your recipes to it:</p>
+
+<pre><code>$ leap node init web1
+$ leap deploy web1
+$ leap node init db1
+$ leap deploy db1
+</code></pre>
+
+<h2><a name="useful-local-commands"></a>Useful local commands</h2>
+
+<p>There are many useful things you can do with a virtualized development environment.</p>
+
+<h3><a name="listing-what-machines-are-running"></a>Listing what machines are running</h3>
+
+<p>Now you have the two virtual machines &ldquo;web1&rdquo; and &ldquo;db1&rdquo; running, you can see the running machines as follows:</p>
+
+<pre><code>$ 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`.
+</code></pre>
+
+<h3><a name="stopping-machines"></a>Stopping machines</h3>
+
+<p>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:</p>
+
+<pre><code>$ leap local stop web1 db1
+</code></pre>
+
+<h3><a name="connecting-to-machines"></a>Connecting to machines</h3>
+
+<p>You can connect to your local nodes just like you do with normal LEAP nodes, by running &lsquo;leap ssh node&rsquo;.</p>
+
+<p>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.</p>
+
+<p>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:</p>
+
+<pre><code>@custom_vagrant_vm_line = 'config.vm.provider "virtualbox" do |v|
+ v.gui = true
+end'
+</code></pre>
+
+<p>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.</p>
+
+<h3><a name="snapshotting-machines"></a>Snapshotting machines</h3>
+
+<p>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.</p>
+
+<p>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:</p>
+
+<pre><code>$ leap local start web1
+$ leap ssh web1
+web1# apt-get -u dist-upgrade
+web1# exit
+$ leap local save web1
+</code></pre>
+
+<p>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:</p>
+
+<pre><code>$ leap local reset web1
+</code></pre>
+
+<h3><a name="more-information"></a>More information</h3>
+
+<p>See <code>leap help local</code> for a complete list of local-only commands and how they can be used.</p>
+
+<h1><a name="2-vagrant-with-static-vagrantfile"></a>2. Vagrant with static Vagrantfile</h1>
+
+<p>You can use the static Vagrantfile if you want to get up a running with a pre-canned test provider.</p>
+
+<p>It will install a single node mail server in the default configuration with one single command.</p>
+
+<p>Clone the platform with</p>
+
+<pre><code>git clone --recursive -b develop https://github.com/leapcode/leap_platform.git
+</code></pre>
+
+<p>Start the vagrant box with</p>
+
+<pre><code>cd leap_platform
+vagrant up
+</code></pre>
+
+<p>Follow the instructions how to configure your <code>/etc/hosts</code>
+in order to use the provider!</p>
+
+<p>You can login via ssh with the systemuser <code>vagrant</code> and the same password.</p>
+
+<pre><code>vagrant ssh
+</code></pre>
+
+<p>On the host, run the tests to check if everything is working as expected:</p>
+
+<pre><code>cd /home/vagrant/leap/configuration/
+leap test
+</code></pre>
+
+<h2><a name="use-the-bitmask-client-to-do-an-initial-soledad-sync"></a>Use the bitmask client to do an initial soledad sync</h2>
+
+<p>Copy the self-signed CA certificate from the host.
+The easiest way is to use the <a href="https://github.com/invernizzi/vagrant-scp">vagrant-scp plugin</a>:</p>
+
+<pre><code>vagrant scp :/home/vagrant/leap/configuration/files/ca/ca.crt /tmp/example.org.ca.crt
+
+vagrant@node1:~/leap/configuration$ cat files/ca/ca.crt
+</code></pre>
+
+<p>and write it into a file, needed by the bitmask client:</p>
+
+<pre><code>bitmask --ca-cert-file /tmp/example.org.ca.crt
+</code></pre>
+
+<p>On the first run, bitmask is creating a gpg keypair. This is
+needed for delivering and encrypting incoming mails.</p>
+
+<h2><a name="testing-email"></a>Testing email</h2>
+
+<pre><code>sudo apt install swaks
+swaks -f test22@leap.se -t test22@example.org -s example.org
+</code></pre>
+
+<p>check the logs:</p>
+
+<pre><code>sudo less /var/log/mail.log
+sudo less /var/log/leap/mx.log
+</code></pre>
+
+<p>if an error occurs, see if the mail is still laying in the mailspool dir:</p>
+
+<pre><code>sudo ls /var/mail/leap-mx/Maildir/new
+</code></pre>
+
+<h2><a name="re-run-bitmask-client-to-sync-your-mail"></a>Re-run bitmask client to sync your mail</h2>
+
+<pre><code>bitmask --ca-cert-file /tmp/example.org.ca.crt
+</code></pre>
+
+<p>Now, connect your favorite mail client to the imap and smtp proxy
+started by the bitmask client:</p>
+
+<pre><code>https://bitmask.net/en/help/email
+</code></pre>
+
+<p>Happy testing !</p>
+
+<h2><a name="using-the-webapp"></a>Using the Webapp</h2>
+
+<p>There are 2 users preconfigured:</p>
+
+<p>. <code>testuser</code> with pw <code>hallo123</code>
+. <code>testadmin</code> with pw <code>hallo123</code></p>
+
+<p>login as <code>testadmin</code> to access the webapp with admin priviledges.</p>
+
+<h1><a name="support-for-libvirt"></a>Support for libvirt</h1>
+
+<h2><a name="install-libvirt-plugin"></a>Install libvirt plugin</h2>
+
+<p>By default, Vagrant will use VirtualBox to create the virtual machines, but this is how you can use libvirt. Using libvirt is more efficient, but VirtualBox is more stable and easier to set up.</p>
+
+<p><em>For debian/ubuntu:</em></p>
+
+<pre><code>sudo apt-get install libvirt-bin libvirt-dev
+
+# to build the vagrant-libvirt plugin you need the following packages:
+sudo apt-get install ruby-dev libxslt-dev libxml2-dev libvirt-dev
+
+# install the required plugins
+vagrant plugin install vagrant-libvirt fog fog-libvirt sahara
+</code></pre>
+
+<p>Log out and then log back in.</p>
+
+<p>Note: if running ubuntu 15.10 as the host OS, you will probably need to run the following commands before &ldquo;vagrant plugin install vagrant-libvirt&rdquo; will work:</p>
+
+<pre><code>ln -sf /usr/lib/liblzma.so.5 /opt/vagrant/embedded/lib
+ln -sf /usr/lib/liblzma.so.5.0.0 /opt/vagrant/embedded/lib
+</code></pre>
+
+<h2><a name="create-libvirt-pool"></a>Create libvirt pool</h2>
+
+<p>Next, you must create the libvirt image pool. The &ldquo;default&rdquo; pool uses <code>/var/lib/libvirt/images</code>, but Vagrant will not download base boxes there. Instead, create a libvirt pool called &ldquo;vagrant&rdquo;, like so:</p>
+
+<pre><code>virsh pool-define-as vagrant dir - - - - /home/$USER/.vagrant.d/boxes
+virsh pool-start vagrant
+virsh pool-autostart vagrant
+</code></pre>
+
+<p>If you want to use a name different than &ldquo;vagrant&rdquo; for the pool, you can change the name in <code>Leapfile</code> by setting the <code>@vagrant_libvirt_pool</code> variable:</p>
+
+<pre><code>@vagrant_libvirt_pool = "vagrant"
+</code></pre>
+
+<h2><a name="force-use-of-libvirt"></a>Force use of libvirt</h2>
+
+<p>Finally, you need to tell Vagrant to use libvirt instead of VirtualBox. If using vagrant with leap_cli, modify your <code>Leapfile</code> or <code>.leaprc</code> file and add this line:</p>
+
+<pre><code>@vagrant_provider = "libvirt"
+</code></pre>
+
+<p>Alternately, if using the static Vagrantfile, you must run this in your shell instead:</p>
+
+<pre><code>export VAGRANT_DEFAULT_PROVIDER=libvirt
+</code></pre>
+
+<h2><a name="debugging"></a>Debugging</h2>
+
+<p>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:</p>
+
+<pre><code>VAGRANT_LOG=info vagrant box add LEAP/jessie
+</code></pre>
+
+<p>You can also run vagrant with &ndash;debug for full logging.</p>
+
+<h2><a name="known-issues"></a>Known issues</h2>
+
+<ul>
+<li>You may need to undefine the default libvirt pool:
+ sudo virsh pool-undefine default</li>
+<li><code>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)</code> - you don&rsquo;t have the libvirtd daemon running or installed, be sure you installed the &lsquo;libvirt-bin&rsquo; package and it is running</li>
+<li><code>Call to virConnectOpen failed: Failed to connect socket to '/var/run/libvirt/libvirt-sock': Permission denied</code> - you need to be in the libvirt group to access the socket, do &lsquo;sudo adduser <user> libvirtd&rsquo; and then re-login to your session.</li>
+<li>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 <a href="https://github.com/pradels/vagrant-libvirt/issues/75">https://github.com/pradels/vagrant-libvirt/issues/75</a> having two virtualization engines installed simultaneously can lead to such weird issues.</li>
+<li>see the <a href="https://github.com/pradels/vagrant-libvirt/issues">vagrant-libvirt issue list on github</a></li>
+<li>be sure to use vagrant-libvirt >= 0.0.11 and sahara >= 0.0.16 (which are the latest stable gems you would get with <code>vagrant plugin install [vagrant-libvirt|sahara]</code>) for proper libvirt support,</li>
+</ul>
+
+
+<h2><a name="useful-commands"></a>Useful commands</h2>
+
+<p>Force re-download of image, in case something goes wrong:</p>
+
+<pre><code>vagrant box add leap/jessie --force --provider libvirt
+</code></pre>
+
+<h2><a name="shared-folder-support"></a>Shared folder support</h2>
+
+<p>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 <a href="https://github.com/pradels/vagrant-libvirt#synced-folders">vagrant-libvirt#synced-folders</a></p>
+
+<pre><code>sudo apt-get install nfs-kernel-serve
+</code></pre>
+
+<p>or you can disable shared folder support (if you do not need it), by setting the following in your Vagrantfile:</p>
+
+<pre><code>config.vm.synced_folder "src/", "/srv/website", disabled: trueconfig.vm.synced_folder "src/", "/srv/website", disabled: true
+</code></pre>
+
+<p>if you are wanting this disabled for all the leap vagrant integration, you can add this to ~/.leaprc:</p>
+
+<pre><code>@custom_vagrant_vm_line = 'config.vm.synced_folder "src/", "/srv/website", disabled: true'
+</code></pre>
+
+<h1><a name="verify-vagrantboxes"></a>Verify vagrantboxes</h1>
+
+<p>When you run vagrant, it goes out to the internet and downloads an initial image for the virtual machine. If you want to verify that authenticity of these images, follow these steps.</p>
+
+<p>Import LEAP archive signing key:</p>
+
+<pre><code>gpg --search-keys 0x1E34A1828E207901
+</code></pre>
+
+<p>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:</p>
+
+<pre><code>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 &lt;sysdev@leap.se&gt;
+</code></pre>
+
+<p>if the fingerprint matches, you could locally sign it so you remember the you already
+verified it:</p>
+
+<pre><code>gpg --lsign-key 1E34A1828E207901
+</code></pre>
+
+<p>Then download the SHA215SUMS file and it&rsquo;s signature file</p>
+
+<pre><code>wget https://downloads.leap.se/platform/SHA256SUMS.sign
+wget https://downloads.leap.se/platform/SHA256SUMS
+</code></pre>
+
+<p>and verify the signature against your local imported LEAP archive signing pubkey</p>
+
+<pre><code>gpg --verify SHA256SUMS.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 &lt;sysdev@leap.se&gt;"
+</code></pre>
+
+<p>Make sure that the last line says &ldquo;Good signature from&hellip;&rdquo;, which tells you that your
+downloaded SHA256SUMS file has the right contents!</p>
+
+<p>Now you can compare the sha215sum of your downloaded vagrantbox with the one in the SHA215SUMS file. You could have downloaded it manually from <a href="https://atlas.hashicorp.com/api/v1/box/LEAP/jessie/$version/$provider.box">https://atlas.hashicorp.com/api/v1/box/LEAP/jessie/$version/$provider.box</a> otherwise it&rsquo;s probably located within ~/.vagrant.d/.</p>
+
+<pre><code>wget https://atlas.hashicorp.com/LEAP/boxes/jessie/versions/1.1.0/providers/libvirt.box
+sha215sum libvirt.box
+cat SHA215SUMS
+</code></pre>
+
+<h1><a name="troubleshooting"></a>Troubleshooting</h1>
+
+<p>To troubleshoot vagrant issues, try going through these steps:</p>
+
+<ul>
+<li>Try plain vagrant using the <a href="http://docs.vagrantup.com/v2/getting-started/index.html">Getting started guide</a>.</li>
+<li>If that fails, make sure that you can run virtual machines (VMs) in plain virtualbox (Virtualbox GUI or VBoxHeadless).
+We don&rsquo;t suggest a special howto for that, <a href="http://www.thegeekstuff.com/2012/02/virtualbox-install-create-vm/">this one</a> seems pretty decent, or you follow the <a href="http://www.virtualbox.org/manual/UserManual.html">Oracale Virtualbox User Manual</a>. There&rsquo;s also specific documentation for <a href="https://wiki.debian.org/VirtualBox">Debian</a> and for <a href="https://help.ubuntu.com/community/VirtualBox">Ubuntu</a>. If you succeeded, try again if you now can start vagrant nodes using plain vagrant (see first step).</li>
+<li>If plain vagrant works for you, you&rsquo;re very close to using vagrant with leap! If you encounter any problems now, please <a href="https://leap.se/en/about-us/contact">contact us</a> or use our <a href="https://leap.se/code">issue tracker</a></li>
+</ul>
+
+
+<h1><a name="additional-notes"></a>Additional notes</h1>
+
+<h2><a name="some-useful-plugins"></a>Some useful plugins</h2>
+
+<ul>
+<li>The vagrant-cachier (plugin <a href="http://fgrehm.viewdocs.io/vagrant-cachier/">http://fgrehm.viewdocs.io/vagrant-cachier/</a>) 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.</li>
+</ul>
+
+
+<h2><a name="limitations"></a>Limitations</h2>
+
+<p>Please consult the known issues for vagrant, see the <a href="vagrant/known-issues.html">Known Issues</a>, section <em>Special Environments</em></p>
+
+<h2><a name="known-working-combinations"></a>Known working combinations</h2>
+
+<p>Please consider that using other combinations might work for you as well, these are just the combinations we tried and worked for us:</p>
+
+<p>Debian Wheezy</p>
+
+<ul>
+<li><code>virtualbox-4.2 4.2.16-86992~Debian~wheezy</code> from Oracle and <code>vagrant 1.2.2</code> from vagrantup.com</li>
+</ul>
+
+
+<p>Ubuntu Wily 15.10</p>
+
+<ul>
+<li>libvirt with vagrant 1.7.2, from standard Ubuntu packages.</li>
+</ul>
+
+
+<p>Mac OS X 10.9</p>
+
+<ul>
+<li><code>VirtualBox 4.3.10</code> from virtualbox.org and <code>vagrant 1.5.4</code> from vagrantup.com</li>
+</ul>
+
+
+<h2><a name="issue-reporting"></a>Issue reporting</h2>
+
+<p>When you encounter any bugs, please <a href="https://leap.se/code/search">check first</a> on our bugtracker if it&rsquo;s something already known. Reporting bugs is the first <a href="https://leap.se/code/projects/report-issues">step in fixing them</a>. Please include all the relevant details: platform branch, version of leap_cli, past upgrades.</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/tutorials/vagrant/index.html b/docs/en/tutorials/vagrant/index.html
new file mode 100644
index 00000000..95bd6b71
--- /dev/null
+++ b/docs/en/tutorials/vagrant/index.html
@@ -0,0 +1,724 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Vagrant - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../guide.html'>Guide</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level1'>
+<a class='' href='../quick-start.html'>Quick Start Tutorial</a>
+</li>
+<li class=' level1'>
+<a class='' href='../single-node-vpn.html'>Quick VPN</a>
+</li>
+<li class=' level1'>
+<a class='' href='../single-node-email.html'>Quick email</a>
+</li>
+<li class='active level1'>
+<a class='' href='../vagrant.html'>Vagrant</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Vagrant and the LEAP Platform</h1>
+
+<div id='summary'>Running a local provider with Vagrant</div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+ <li>
+ <a href="index.html#what-is-vagrant">What is Vagrant?</a>
+ </li>
+ <li>
+ <a href="index.html#install-vagrant">Install Vagrant</a>
+ </li>
+ <li>
+ <a href="index.html#vagrant-with-leap-command">Vagrant with leap command</a>
+ <ol>
+ <li>
+ <a href="index.html#creating-local-nodes">Creating local nodes</a>
+ </li>
+ <li>
+ <a href="index.html#starting-local-nodes">Starting local nodes</a>
+ </li>
+ <li>
+ <a href="index.html#useful-local-commands">Useful local commands</a>
+ <ol>
+ <li>
+ <a href="index.html#listing-what-machines-are-running">Listing what machines are running</a>
+ </li>
+ <li>
+ <a href="index.html#stopping-machines">Stopping machines</a>
+ </li>
+ <li>
+ <a href="index.html#connecting-to-machines">Connecting to machines</a>
+ </li>
+ <li>
+ <a href="index.html#snapshotting-machines">Snapshotting machines</a>
+ </li>
+ <li>
+ <a href="index.html#more-information">More information</a>
+ </li>
+ </ol>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#2-vagrant-with-static-vagrantfile">2. Vagrant with static Vagrantfile</a>
+ <ol>
+ <li>
+ <a href="index.html#use-the-bitmask-client-to-do-an-initial-soledad-sync">Use the bitmask client to do an initial soledad sync</a>
+ </li>
+ <li>
+ <a href="index.html#testing-email">Testing email</a>
+ </li>
+ <li>
+ <a href="index.html#re-run-bitmask-client-to-sync-your-mail">Re-run bitmask client to sync your mail</a>
+ </li>
+ <li>
+ <a href="index.html#using-the-webapp">Using the Webapp</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#support-for-libvirt">Support for libvirt</a>
+ <ol>
+ <li>
+ <a href="index.html#install-libvirt-plugin">Install libvirt plugin</a>
+ </li>
+ <li>
+ <a href="index.html#create-libvirt-pool">Create libvirt pool</a>
+ </li>
+ <li>
+ <a href="index.html#force-use-of-libvirt">Force use of libvirt</a>
+ </li>
+ <li>
+ <a href="index.html#debugging">Debugging</a>
+ </li>
+ <li>
+ <a href="index.html#known-issues">Known issues</a>
+ </li>
+ <li>
+ <a href="index.html#useful-commands">Useful commands</a>
+ </li>
+ <li>
+ <a href="index.html#shared-folder-support">Shared folder support</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="index.html#verify-vagrantboxes">Verify vagrantboxes</a>
+ </li>
+ <li>
+ <a href="index.html#troubleshooting">Troubleshooting</a>
+ </li>
+ <li>
+ <a href="index.html#additional-notes">Additional notes</a>
+ <ol>
+ <li>
+ <a href="index.html#some-useful-plugins">Some useful plugins</a>
+ </li>
+ <li>
+ <a href="index.html#limitations">Limitations</a>
+ </li>
+ <li>
+ <a href="index.html#known-working-combinations">Known working combinations</a>
+ </li>
+ <li>
+ <a href="index.html#issue-reporting">Issue reporting</a>
+ </li>
+ </ol>
+ </li>
+</ol></div>
+
+<h1><a name="what-is-vagrant"></a>What is Vagrant?</h1>
+
+<p><a href="https://www.vagrantup.com">Vagrant</a> is a tool to make it easier to manage virtual machines running on your desktop computer (typically for testing or development purposes). You can use Vagrant to create virtual machines and deploy the LEAP platform locally.</p>
+
+<p>Vagrant can be a pain to get working initially, but this page should help you get through the process. Please make sure you have at least Vagrant v1.5 installed.</p>
+
+<p>There are two ways you can setup LEAP platform using Vagrant.</p>
+
+<ol>
+<li>use the <code>leap</code> command: this will allow you to create multiple virtual machines.</li>
+<li>use static Vagrantfile: there is a static Vagrantfile that is distributed with the <code>leap_platform.git</code>. This only supports a single, pre-configured virtual machine, but can get you started more quickly.</li>
+</ol>
+
+
+<h1><a name="install-vagrant"></a>Install Vagrant</h1>
+
+<p>Requirements:</p>
+
+<ul>
+<li>A real machine with virtualization support in the CPU (VT-x or AMD-V). In other words, not a virtual machine.</li>
+<li>Have at least 4gb of RAM.</li>
+<li>Have a fast internet connection (because you will be downloading a lot of big files, like virtual machine images).</li>
+<li>You should do everything described below as an unprivileged user, and only run those commands as root that are noted with <em>sudo</em> 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.</li>
+</ul>
+
+
+<p><em>Debian &amp; Ubuntu</em></p>
+
+<p>Install core prerequisites:</p>
+
+<pre><code>sudo apt-get install git ruby ruby-dev rsync openssh-client openssl rake make
+</code></pre>
+
+<p>Install Vagrant:</p>
+
+<pre><code>sudo apt-get install vagrant virtualbox
+</code></pre>
+
+<p>If you want to use libvirt instead of virtualbox, you don&rsquo;t need to install virtualbox. See <a href="index.html#support-for-libvirt">support for libvirt</a>.</p>
+
+<p><em>Mac OS X 10.9 (Mavericks)</em></p>
+
+<p>Install Homebrew package manager from <a href="http://brew.sh/">http://brew.sh/</a> and enable the <a href="https://github.com/Homebrew/homebrew/wiki/Interesting-Taps-&amp;-Branches">System Duplicates Repository</a> (needed to update old software versions delivered by Apple) with</p>
+
+<pre><code>brew tap homebrew/dupes
+</code></pre>
+
+<p>Update OpenSSH to support ECDSA keys. Follow <a href="http://www.dctrwatson.com/2013/07/how-to-update-openssh-on-mac-os-x/">this guide</a> to let your system use the Homebrew binary.</p>
+
+<pre><code>brew install openssh --with-brewed-openssl --with-keychain-support
+</code></pre>
+
+<p>The certtool provided by Apple it&rsquo;s really old, install the one provided by GnuTLS and shadow the system&rsquo;s default.</p>
+
+<pre><code>sudo brew install gnutls
+ln -sf /usr/local/bin/gnutls-certtool /usr/local/bin/certool
+</code></pre>
+
+<p>Install the Vagrant and VirtualBox packages for OS X from their respective Download pages.</p>
+
+<ul>
+<li><a href="http://www.vagrantup.com/downloads.html">http://www.vagrantup.com/downloads.html</a></li>
+<li><a href="https://www.virtualbox.org/wiki/Downloads">https://www.virtualbox.org/wiki/Downloads</a></li>
+</ul>
+
+
+<h1><a name="vagrant-with-leap-command"></a>Vagrant with leap command</h1>
+
+<p>If you have not done so, install <code>leap</code> command line tool:</p>
+
+<pre><code>gem install leap_cli
+</code></pre>
+
+<h2><a name="creating-local-nodes"></a>Creating local nodes</h2>
+
+<p>When you create a service provider, your servers are called &ldquo;nodes&rdquo;. When a node is virtual and exists only locally using vagrant, this type of node is called a &ldquo;local node&rdquo;.</p>
+
+<p>If you do not have a provider already, you will need to create one and configure it before continuing (see the <a href="quick-start.html">Quick Start</a> guide).</p>
+
+<p>These commands, for example, will create an initial provider directory &ldquo;myprovider&rdquo;:</p>
+
+<pre><code>$ leap new --domain example.org --name Example myprovider
+$ cd myprovider
+$ leap add-user --self
+$ leap cert ca
+$ leap cert csr
+</code></pre>
+
+<p>To create local nodes, add the flag <code>--local</code> to the <code>leap node add</code> command. For example:</p>
+
+<pre><code>$ 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
+</code></pre>
+
+<p>This command creates a node configuration file in <code>nodes/web1.json</code> with the webapp service.</p>
+
+<h2><a name="starting-local-nodes"></a>Starting local nodes</h2>
+
+<p>In order to test the node &ldquo;web1&rdquo; 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&rsquo;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).</p>
+
+<p>NOTE: Many people have difficulties getting Vagrant working. If the following commands do not work, please see the troubleshooting section below.</p>
+
+<pre><code>$ leap local start web1
+ = created test/
+ = created test/Vagrantfile
+ = installing vagrant plugin 'sahara'
+Bringing machine 'web1' up with 'virtualbox' provider...
+[web1] Box 'leap-jessie' 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-jessie'...
+0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
+</code></pre>
+
+<p>Now the virtual machine &lsquo;web1&rsquo; is running. You can add another local node using the same process. For example, the webapp node needs a databasse to run, so let&rsquo;s add a &ldquo;couchdb&rdquo; node:</p>
+
+<pre><code>$ 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-jessie'...
+[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 =&gt; 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 =&gt; 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
+</code></pre>
+
+<p>You now can follow the normal LEAP process and initialize it and then deploy your recipes to it:</p>
+
+<pre><code>$ leap node init web1
+$ leap deploy web1
+$ leap node init db1
+$ leap deploy db1
+</code></pre>
+
+<h2><a name="useful-local-commands"></a>Useful local commands</h2>
+
+<p>There are many useful things you can do with a virtualized development environment.</p>
+
+<h3><a name="listing-what-machines-are-running"></a>Listing what machines are running</h3>
+
+<p>Now you have the two virtual machines &ldquo;web1&rdquo; and &ldquo;db1&rdquo; running, you can see the running machines as follows:</p>
+
+<pre><code>$ 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`.
+</code></pre>
+
+<h3><a name="stopping-machines"></a>Stopping machines</h3>
+
+<p>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:</p>
+
+<pre><code>$ leap local stop web1 db1
+</code></pre>
+
+<h3><a name="connecting-to-machines"></a>Connecting to machines</h3>
+
+<p>You can connect to your local nodes just like you do with normal LEAP nodes, by running &lsquo;leap ssh node&rsquo;.</p>
+
+<p>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.</p>
+
+<p>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:</p>
+
+<pre><code>@custom_vagrant_vm_line = 'config.vm.provider "virtualbox" do |v|
+ v.gui = true
+end'
+</code></pre>
+
+<p>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.</p>
+
+<h3><a name="snapshotting-machines"></a>Snapshotting machines</h3>
+
+<p>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.</p>
+
+<p>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:</p>
+
+<pre><code>$ leap local start web1
+$ leap ssh web1
+web1# apt-get -u dist-upgrade
+web1# exit
+$ leap local save web1
+</code></pre>
+
+<p>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:</p>
+
+<pre><code>$ leap local reset web1
+</code></pre>
+
+<h3><a name="more-information"></a>More information</h3>
+
+<p>See <code>leap help local</code> for a complete list of local-only commands and how they can be used.</p>
+
+<h1><a name="2-vagrant-with-static-vagrantfile"></a>2. Vagrant with static Vagrantfile</h1>
+
+<p>You can use the static Vagrantfile if you want to get up a running with a pre-canned test provider.</p>
+
+<p>It will install a single node mail server in the default configuration with one single command.</p>
+
+<p>Clone the platform with</p>
+
+<pre><code>git clone --recursive -b develop https://github.com/leapcode/leap_platform.git
+</code></pre>
+
+<p>Start the vagrant box with</p>
+
+<pre><code>cd leap_platform
+vagrant up
+</code></pre>
+
+<p>Follow the instructions how to configure your <code>/etc/hosts</code>
+in order to use the provider!</p>
+
+<p>You can login via ssh with the systemuser <code>vagrant</code> and the same password.</p>
+
+<pre><code>vagrant ssh
+</code></pre>
+
+<p>On the host, run the tests to check if everything is working as expected:</p>
+
+<pre><code>cd /home/vagrant/leap/configuration/
+leap test
+</code></pre>
+
+<h2><a name="use-the-bitmask-client-to-do-an-initial-soledad-sync"></a>Use the bitmask client to do an initial soledad sync</h2>
+
+<p>Copy the self-signed CA certificate from the host.
+The easiest way is to use the <a href="https://github.com/invernizzi/vagrant-scp">vagrant-scp plugin</a>:</p>
+
+<pre><code>vagrant scp :/home/vagrant/leap/configuration/files/ca/ca.crt /tmp/example.org.ca.crt
+
+vagrant@node1:~/leap/configuration$ cat files/ca/ca.crt
+</code></pre>
+
+<p>and write it into a file, needed by the bitmask client:</p>
+
+<pre><code>bitmask --ca-cert-file /tmp/example.org.ca.crt
+</code></pre>
+
+<p>On the first run, bitmask is creating a gpg keypair. This is
+needed for delivering and encrypting incoming mails.</p>
+
+<h2><a name="testing-email"></a>Testing email</h2>
+
+<pre><code>sudo apt install swaks
+swaks -f test22@leap.se -t test22@example.org -s example.org
+</code></pre>
+
+<p>check the logs:</p>
+
+<pre><code>sudo less /var/log/mail.log
+sudo less /var/log/leap/mx.log
+</code></pre>
+
+<p>if an error occurs, see if the mail is still laying in the mailspool dir:</p>
+
+<pre><code>sudo ls /var/mail/leap-mx/Maildir/new
+</code></pre>
+
+<h2><a name="re-run-bitmask-client-to-sync-your-mail"></a>Re-run bitmask client to sync your mail</h2>
+
+<pre><code>bitmask --ca-cert-file /tmp/example.org.ca.crt
+</code></pre>
+
+<p>Now, connect your favorite mail client to the imap and smtp proxy
+started by the bitmask client:</p>
+
+<pre><code>https://bitmask.net/en/help/email
+</code></pre>
+
+<p>Happy testing !</p>
+
+<h2><a name="using-the-webapp"></a>Using the Webapp</h2>
+
+<p>There are 2 users preconfigured:</p>
+
+<p>. <code>testuser</code> with pw <code>hallo123</code>
+. <code>testadmin</code> with pw <code>hallo123</code></p>
+
+<p>login as <code>testadmin</code> to access the webapp with admin priviledges.</p>
+
+<h1><a name="support-for-libvirt"></a>Support for libvirt</h1>
+
+<h2><a name="install-libvirt-plugin"></a>Install libvirt plugin</h2>
+
+<p>By default, Vagrant will use VirtualBox to create the virtual machines, but this is how you can use libvirt. Using libvirt is more efficient, but VirtualBox is more stable and easier to set up.</p>
+
+<p><em>For debian/ubuntu:</em></p>
+
+<pre><code>sudo apt-get install libvirt-bin libvirt-dev
+
+# to build the vagrant-libvirt plugin you need the following packages:
+sudo apt-get install ruby-dev libxslt-dev libxml2-dev libvirt-dev
+
+# install the required plugins
+vagrant plugin install vagrant-libvirt fog fog-libvirt sahara
+</code></pre>
+
+<p>Log out and then log back in.</p>
+
+<p>Note: if running ubuntu 15.10 as the host OS, you will probably need to run the following commands before &ldquo;vagrant plugin install vagrant-libvirt&rdquo; will work:</p>
+
+<pre><code>ln -sf /usr/lib/liblzma.so.5 /opt/vagrant/embedded/lib
+ln -sf /usr/lib/liblzma.so.5.0.0 /opt/vagrant/embedded/lib
+</code></pre>
+
+<h2><a name="create-libvirt-pool"></a>Create libvirt pool</h2>
+
+<p>Next, you must create the libvirt image pool. The &ldquo;default&rdquo; pool uses <code>/var/lib/libvirt/images</code>, but Vagrant will not download base boxes there. Instead, create a libvirt pool called &ldquo;vagrant&rdquo;, like so:</p>
+
+<pre><code>virsh pool-define-as vagrant dir - - - - /home/$USER/.vagrant.d/boxes
+virsh pool-start vagrant
+virsh pool-autostart vagrant
+</code></pre>
+
+<p>If you want to use a name different than &ldquo;vagrant&rdquo; for the pool, you can change the name in <code>Leapfile</code> by setting the <code>@vagrant_libvirt_pool</code> variable:</p>
+
+<pre><code>@vagrant_libvirt_pool = "vagrant"
+</code></pre>
+
+<h2><a name="force-use-of-libvirt"></a>Force use of libvirt</h2>
+
+<p>Finally, you need to tell Vagrant to use libvirt instead of VirtualBox. If using vagrant with leap_cli, modify your <code>Leapfile</code> or <code>.leaprc</code> file and add this line:</p>
+
+<pre><code>@vagrant_provider = "libvirt"
+</code></pre>
+
+<p>Alternately, if using the static Vagrantfile, you must run this in your shell instead:</p>
+
+<pre><code>export VAGRANT_DEFAULT_PROVIDER=libvirt
+</code></pre>
+
+<h2><a name="debugging"></a>Debugging</h2>
+
+<p>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:</p>
+
+<pre><code>VAGRANT_LOG=info vagrant box add LEAP/jessie
+</code></pre>
+
+<p>You can also run vagrant with &ndash;debug for full logging.</p>
+
+<h2><a name="known-issues"></a>Known issues</h2>
+
+<ul>
+<li>You may need to undefine the default libvirt pool:
+ sudo virsh pool-undefine default</li>
+<li><code>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)</code> - you don&rsquo;t have the libvirtd daemon running or installed, be sure you installed the &lsquo;libvirt-bin&rsquo; package and it is running</li>
+<li><code>Call to virConnectOpen failed: Failed to connect socket to '/var/run/libvirt/libvirt-sock': Permission denied</code> - you need to be in the libvirt group to access the socket, do &lsquo;sudo adduser <user> libvirtd&rsquo; and then re-login to your session.</li>
+<li>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 <a href="https://github.com/pradels/vagrant-libvirt/issues/75">https://github.com/pradels/vagrant-libvirt/issues/75</a> having two virtualization engines installed simultaneously can lead to such weird issues.</li>
+<li>see the <a href="https://github.com/pradels/vagrant-libvirt/issues">vagrant-libvirt issue list on github</a></li>
+<li>be sure to use vagrant-libvirt >= 0.0.11 and sahara >= 0.0.16 (which are the latest stable gems you would get with <code>vagrant plugin install [vagrant-libvirt|sahara]</code>) for proper libvirt support,</li>
+</ul>
+
+
+<h2><a name="useful-commands"></a>Useful commands</h2>
+
+<p>Force re-download of image, in case something goes wrong:</p>
+
+<pre><code>vagrant box add leap/jessie --force --provider libvirt
+</code></pre>
+
+<h2><a name="shared-folder-support"></a>Shared folder support</h2>
+
+<p>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 <a href="https://github.com/pradels/vagrant-libvirt#synced-folders">vagrant-libvirt#synced-folders</a></p>
+
+<pre><code>sudo apt-get install nfs-kernel-serve
+</code></pre>
+
+<p>or you can disable shared folder support (if you do not need it), by setting the following in your Vagrantfile:</p>
+
+<pre><code>config.vm.synced_folder "src/", "/srv/website", disabled: trueconfig.vm.synced_folder "src/", "/srv/website", disabled: true
+</code></pre>
+
+<p>if you are wanting this disabled for all the leap vagrant integration, you can add this to ~/.leaprc:</p>
+
+<pre><code>@custom_vagrant_vm_line = 'config.vm.synced_folder "src/", "/srv/website", disabled: true'
+</code></pre>
+
+<h1><a name="verify-vagrantboxes"></a>Verify vagrantboxes</h1>
+
+<p>When you run vagrant, it goes out to the internet and downloads an initial image for the virtual machine. If you want to verify that authenticity of these images, follow these steps.</p>
+
+<p>Import LEAP archive signing key:</p>
+
+<pre><code>gpg --search-keys 0x1E34A1828E207901
+</code></pre>
+
+<p>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:</p>
+
+<pre><code>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 &lt;sysdev@leap.se&gt;
+</code></pre>
+
+<p>if the fingerprint matches, you could locally sign it so you remember the you already
+verified it:</p>
+
+<pre><code>gpg --lsign-key 1E34A1828E207901
+</code></pre>
+
+<p>Then download the SHA215SUMS file and it&rsquo;s signature file</p>
+
+<pre><code>wget https://downloads.leap.se/platform/SHA256SUMS.sign
+wget https://downloads.leap.se/platform/SHA256SUMS
+</code></pre>
+
+<p>and verify the signature against your local imported LEAP archive signing pubkey</p>
+
+<pre><code>gpg --verify SHA256SUMS.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 &lt;sysdev@leap.se&gt;"
+</code></pre>
+
+<p>Make sure that the last line says &ldquo;Good signature from&hellip;&rdquo;, which tells you that your
+downloaded SHA256SUMS file has the right contents!</p>
+
+<p>Now you can compare the sha215sum of your downloaded vagrantbox with the one in the SHA215SUMS file. You could have downloaded it manually from <a href="https://atlas.hashicorp.com/api/v1/box/LEAP/jessie/$version/$provider.box">https://atlas.hashicorp.com/api/v1/box/LEAP/jessie/$version/$provider.box</a> otherwise it&rsquo;s probably located within ~/.vagrant.d/.</p>
+
+<pre><code>wget https://atlas.hashicorp.com/LEAP/boxes/jessie/versions/1.1.0/providers/libvirt.box
+sha215sum libvirt.box
+cat SHA215SUMS
+</code></pre>
+
+<h1><a name="troubleshooting"></a>Troubleshooting</h1>
+
+<p>To troubleshoot vagrant issues, try going through these steps:</p>
+
+<ul>
+<li>Try plain vagrant using the <a href="http://docs.vagrantup.com/v2/getting-started/index.html">Getting started guide</a>.</li>
+<li>If that fails, make sure that you can run virtual machines (VMs) in plain virtualbox (Virtualbox GUI or VBoxHeadless).
+We don&rsquo;t suggest a special howto for that, <a href="http://www.thegeekstuff.com/2012/02/virtualbox-install-create-vm/">this one</a> seems pretty decent, or you follow the <a href="http://www.virtualbox.org/manual/UserManual.html">Oracale Virtualbox User Manual</a>. There&rsquo;s also specific documentation for <a href="https://wiki.debian.org/VirtualBox">Debian</a> and for <a href="https://help.ubuntu.com/community/VirtualBox">Ubuntu</a>. If you succeeded, try again if you now can start vagrant nodes using plain vagrant (see first step).</li>
+<li>If plain vagrant works for you, you&rsquo;re very close to using vagrant with leap! If you encounter any problems now, please <a href="https://leap.se/en/about-us/contact">contact us</a> or use our <a href="https://leap.se/code">issue tracker</a></li>
+</ul>
+
+
+<h1><a name="additional-notes"></a>Additional notes</h1>
+
+<h2><a name="some-useful-plugins"></a>Some useful plugins</h2>
+
+<ul>
+<li>The vagrant-cachier (plugin <a href="http://fgrehm.viewdocs.io/vagrant-cachier/">http://fgrehm.viewdocs.io/vagrant-cachier/</a>) 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.</li>
+</ul>
+
+
+<h2><a name="limitations"></a>Limitations</h2>
+
+<p>Please consult the known issues for vagrant, see the <a href="known-issues.html">Known Issues</a>, section <em>Special Environments</em></p>
+
+<h2><a name="known-working-combinations"></a>Known working combinations</h2>
+
+<p>Please consider that using other combinations might work for you as well, these are just the combinations we tried and worked for us:</p>
+
+<p>Debian Wheezy</p>
+
+<ul>
+<li><code>virtualbox-4.2 4.2.16-86992~Debian~wheezy</code> from Oracle and <code>vagrant 1.2.2</code> from vagrantup.com</li>
+</ul>
+
+
+<p>Ubuntu Wily 15.10</p>
+
+<ul>
+<li>libvirt with vagrant 1.7.2, from standard Ubuntu packages.</li>
+</ul>
+
+
+<p>Mac OS X 10.9</p>
+
+<ul>
+<li><code>VirtualBox 4.3.10</code> from virtualbox.org and <code>vagrant 1.5.4</code> from vagrantup.com</li>
+</ul>
+
+
+<h2><a name="issue-reporting"></a>Issue reporting</h2>
+
+<p>When you encounter any bugs, please <a href="https://leap.se/code/search">check first</a> on our bugtracker if it&rsquo;s something already known. Reporting bugs is the first <a href="https://leap.se/code/projects/report-issues">step in fixing them</a>. Please include all the relevant details: platform branch, version of leap_cli, past upgrades.</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/tutorials/vagrant/known-issues.html b/docs/en/tutorials/vagrant/known-issues.html
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/docs/en/tutorials/vagrant/known-issues.html
diff --git a/docs/en/tutorials/vagrant/quick-start.html b/docs/en/tutorials/vagrant/quick-start.html
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/docs/en/tutorials/vagrant/quick-start.html
diff --git a/docs/en/upgrading.html b/docs/en/upgrading.html
new file mode 100644
index 00000000..0e5d6607
--- /dev/null
+++ b/docs/en/upgrading.html
@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Upgrading - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='services.html'>Services</a>
+</li>
+<li class='active level0'>
+<a class='' href='upgrading.html'>Upgrading</a>
+</li>
+<li class=' level1'>
+<a class='' href='upgrading/upgrade-0-9.html'>Upgrade to 0.9</a>
+</li>
+<li class=' level1'>
+<a class='' href='upgrading/upgrade-0-8.html'>Upgrade to 0.8</a>
+</li>
+<li class=' level0'>
+<a class='' href='troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Upgrading from prior LEAP platform releases</h1>
+
+<div id='summary'></div>
+</div>
+<div id='content-box'>
+<div id="TOC"><ol>
+</ol></div>
+
+<div class=' page-summary'>
+ <h2>
+ <a href='upgrading/upgrade-0-9.html'>Upgrade to 0.9</a>
+ </h2>
+ <div class='summary'></div>
+</div>
+<div class=' page-summary'>
+ <h2>
+ <a href='upgrading/upgrade-0-8.html'>Upgrade to 0.8</a>
+ </h2>
+ <div class='summary'></div>
+</div>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/upgrading/upgrade-0-8.html b/docs/en/upgrading/upgrade-0-8.html
new file mode 100644
index 00000000..275abd11
--- /dev/null
+++ b/docs/en/upgrading/upgrade-0-8.html
@@ -0,0 +1,337 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Upgrade to 0.8 - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class=' level1'>
+<a class='' href='upgrade-0-9.html'>Upgrade to 0.9</a>
+</li>
+<li class='active level1'>
+<a class='' href='upgrade-0-8.html'>Upgrade to 0.8</a>
+</li>
+<li class=' level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Upgrade to 0.8</h1>
+
+<div id='summary'></div>
+</div>
+<div id='content-box'>
+<p>LEAP Platform release 0.8 introduces several major changes that need do get taken into account while upgrading:</p>
+
+<ul>
+<li>Dropping Debian Wheezy support. You need to upgrade your nodes to jessie before deploying a platform upgrade.</li>
+<li>Dropping BigCouch support. LEAP Platform now requires CouchDB and therefore you need to migrate all your data from BigCouch to CouchDB.</li>
+</ul>
+
+
+<h2><a name="upgrading-to-platform-08"></a>Upgrading to Platform 0.8</h2>
+
+<h3><a name="step-1-get-new-leap_platform-and-leap_cli"></a>Step 1: Get new leap_platform and leap_cli</h3>
+
+<pre><code>workstation$ gem install leap_cli --version 1.8
+workstation$ cd leap_platform
+workstation$ git pull
+workstation$ git checkout 0.8.0
+</code></pre>
+
+<h3><a name="step-2-prepare-to-migrate-from-bigcouch-to-couchdb"></a>Step 2: Prepare to migrate from BigCouch to CouchDB</h3>
+
+<p><p>At the end of this process, you will have just <em>one</em> node with <code>services</code> property equal to <code>couchdb</code>. If you had a BigCouch cluster before, you will be removing all but one of those machines to consolidate them into one CouchDB machine.</p>
+
+<ol>
+<li><p>if you have multiple nodes with the <code>couchdb</code> service on them, pick one of them to be your CouchDB server, and remove the service from the others. If these machines were only doing BigCouch before, you can remove the nodes completely with <code>leap node rm &lt;nodename&gt;</code> and then you can decommission the servers</p></li>
+<li><p>put the webapp into <a href="../services/webapp.html#maintenance-mode">maintenance mode</a></p></li>
+<li><p>turn off daemons that access the database. For example:</p>
+
+<pre><code class="`"> workstation$ leap ssh &lt;each soledad-node&gt;
+ server# /etc/init.d/soledad-server stop
+
+ workstation$ leap ssh &lt;mx-node&gt;
+ server# /etc/init.d/postfix stop
+ server# /etc/init.d/leap-mx stop
+
+ workstation$ leap ssh &lt;webapp-node&gt;
+ server# /etc/init.d/nickserver stop
+</code></pre>
+
+<p> Alternately, you can create a temporary firewall rule to block access (run on couchdb server):</p>
+
+<pre><code class="`"> server# iptables -A INPUT -p tcp --dport 5984 --jump REJECT
+</code></pre></li>
+<li><p>remove orphaned databases and do a backup of all remaining, active databases. This can take some time and will place several hundred megabytes of data into /var/backups/couchdb. The size and time depends on how many users there are on your system. For example, 15k users took approximately 25 minutes and 308M of space:</p>
+
+<pre><code class="`"> workstation$ leap ssh &lt;couchdb-node&gt;
+ server# cd /srv/leap/couchdb/scripts
+ server# ./cleanup-user-dbs
+ server# time ./couchdb_dumpall.sh
+</code></pre></li>
+<li><p>stop bigcouch:</p>
+
+<pre><code class="`"> server# /etc/init.d/bigcouch stop
+ server# pkill epmd
+</code></pre></li>
+<li><p>remove bigcouch:</p>
+
+<pre><code class="`"> server# apt-get remove bigcouch
+</code></pre></li>
+<li><p>configure your couch node to use plain couchdb instead of bigcouch, you can do this by editing nodes/<couch-node>.json, look for this section:</p>
+
+<pre><code class="`"> "couch": {
+ "mode": "plain"
+ }
+</code></pre>
+
+<p>change it, so it looks like this instead:</p>
+
+<pre><code class="``"> "couch": {
+ "mode": "plain",
+ "pwhash_alg": "pbkdf2"
+ }
+</code></pre></li>
+</ol>
+
+</p>
+
+<h3><a name="step-3-upgrade-from-debian-wheezy-to-jessie"></a>Step 3: Upgrade from Debian Wheezy to Jessie</h3>
+
+<p>There are the <a href="https://www.debian.org/releases/stable/amd64/release-notes/ch-upgrading.html">Debian release notes on how to upgrade from wheezy to jessie</a>. Here are the steps that worked for us, but please keep in mind that there is no bullet-proof method that will work in every situation.</p>
+
+<p><strong>USE AT YOUR OWN RISK.</strong></p>
+
+<p>For each one of your nodes, login to it and do the following process:</p>
+
+<pre><code># keep a log of the progress:
+screen
+script -t 2&gt;~/leap_upgrade-jessiestep.time -a ~/upgrade-jessiestep.script
+
+# ensure you have a good wheezy install:
+export DEBIAN_FRONTEND=noninteractive
+apt-get autoremove --yes
+apt-get update
+apt-get -y -o DPkg::Options::=--force-confold dist-upgrade
+
+# if either of these return anything, you will need to resolve them before continuing:
+dpkg --audit
+dpkg --get-selections | grep 'hold$'
+
+# switch sources to jessie
+sed -i 's/wheezy/jessie/g' /etc/apt/sources.list
+rm /etc/apt/sources.list.d/*
+echo "deb http://deb.leap.se/0.8 jessie main" &gt; /etc/apt/sources.list.d/leap.list
+
+# remove pinnings to wheezy
+rm /etc/apt/preferences
+rm /etc/apt/preferences.d/*
+
+# get jessie package lists
+apt-get update
+
+# clean out old package files
+apt-get clean
+
+# test to see if you have enough space to upgrade, the following will alert
+# you if you do not have enough space, it will not do the actual upgrade
+apt-get -o APT::Get::Trivial-Only=true dist-upgrade
+
+# do first stage upgrade
+apt-get -y -o DPkg::Options::=--force-confold upgrade
+
+# repeat the following until it makes no more changes:
+apt-get -y -o DPkg::Options::=--force-confold dist-upgrade
+
+# resolve any apt issues if there are some
+apt-get -y -o DPkg::Options::=--force-confold -f install
+
+# clean up extra packages
+apt-get autoremove --yes
+
+reboot
+</code></pre>
+
+<h2><a name="potential-jessie-upgrade-issues"></a>Potential Jessie Upgrade Issues</h2>
+
+<p><strong>W: Ignoring Provides line with DepCompareOp for package python-cffi-backend-api-max</strong></p>
+
+<p>You can ignore these warnings, they will be resolved on upgrade.</p>
+
+<p><strong>E: Unable to fetch some archives, maybe run apt-get update or try with &ndash;fix-missing?</strong></p>
+
+<p>If you get this error, run <code>apt-get update</code> and then re-run the command.</p>
+
+<p><strong>Unmet dependencies. Try using -f.</strong></p>
+
+<p>Sometimes you might get an error similar to this (although the package names may be different):</p>
+
+<pre><code>You might want to run 'apt-get -f install' to correct these.
+The following packages have unmet dependencies:
+lsof : Depends: libperl4-corelibs-perl but it is not installed or
+ perl (&lt; 5.12.3-7) but 5.20.2-3+deb8u4 is installed
+</code></pre>
+
+<p>If this happens, run <code>apt-get -f install</code> to resolve it, and then re-do the previous upgrade command
+you did when this happened.</p>
+
+<p><strong>Failure restarting some services for OpenSSL upgrade</strong></p>
+
+<p>If you get this warning:</p>
+
+<pre><code>The following services could not be restarted for the OpenSSL library upgrade:
+ postfix
+You will need to start these manually by running '/etc/init.d/&lt;service&gt; start'.
+</code></pre>
+
+<p>Just ignore it, it should be fixed on reboot/deploy.</p>
+
+<h3><a name="step-4-deploy-leap-platform-08-to-the-couch-node"></a>Step 4: Deploy LEAP Platform 0.8 to the Couch node</h3>
+
+<p>You will need to deploy the 0.8 version of LEAP Platform to the couch node before continuing.</p>
+
+<ol>
+<li><p>deploy to the couch node:</p>
+
+<pre><code class="`"> workstation$ leap deploy &lt;couchdb-node&gt;
+</code></pre>
+
+<p> If you used the iptables method of blocking access to couchdb, you need to run it again because the deploy just overwrote all the iptables rules:</p>
+
+<pre><code class="`"> server# iptables -A INPUT -p tcp --dport 5984 --jump REJECT
+</code></pre></li>
+</ol>
+
+
+<h3><a name="step-5-import-data-into-couchdb"></a>Step 5: Import Data into CouchDB</h3>
+
+<p><ol>
+<li><p>restore the backup, this will take approximately the same amount of time as the backup took above:</p>
+
+<pre><code class="`"> server# cd /srv/leap/couchdb/scripts
+ server# time ./couchdb_restoreall.sh
+</code></pre></li>
+<li><p>start services again that were stopped in the beginning:</p>
+
+<pre><code class="`"> workstation$ leap ssh soledad-nodes
+ server# /etc/init.d/soledad-server start
+
+ workstation$ leap ssh mx-node
+ server# /etc/init.d/postfix start
+ server# /etc/init.d/leap-mx start
+
+ workstation$ leap ssh webapp
+ server# /etc/init.d/nickserver start
+</code></pre>
+
+<p> Or, alternately, if you set up the firewall rule instead, now remove it:</p>
+
+<pre><code class="`"> server# iptables -D INPUT -p tcp --dport 5984 --jump REJECT
+</code></pre></li>
+</ol>
+
+</p>
+
+<h3><a name="step-6-deploy-everything"></a>Step 6: Deploy everything</h3>
+
+<p>Now that you&rsquo;ve upgraded all nodes to Jessie, and migrated to CouchDB, you are ready to deploy LEAP Platform 0.8 to the rest of the nodes:</p>
+
+<pre><code>workstation$ cd &lt;provider directory&gt;
+workstation$ leap deploy
+</code></pre>
+
+<h3><a name="step-7-test-and-cleanup"></a>Step 7: Test and cleanup</h3>
+
+<p><ol>
+<li><p>check if everything is working, including running the test on your deployment machine:</p>
+
+<pre><code class="`"> workstation$ leap test
+</code></pre></li>
+<li><p>Remove old bigcouch data dir <code>/opt</code> after you double checked everything is in place</p></li>
+<li><p>Relax, enjoy a refreshing beverage.</p></li>
+</ol>
+
+</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/en/upgrading/upgrade-0-9.html b/docs/en/upgrading/upgrade-0-9.html
new file mode 100644
index 00000000..cb78569d
--- /dev/null
+++ b/docs/en/upgrading/upgrade-0-9.html
@@ -0,0 +1,149 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Upgrade to 0.9 - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class=''>
+<a href='../../index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='../guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='../tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='../services.html'>Services</a>
+</li>
+<li class='semi-active level0'>
+<a class='' href='../upgrading.html'>Upgrading</a>
+</li>
+<li class='active level1'>
+<a class='' href='upgrade-0-9.html'>Upgrade to 0.9</a>
+</li>
+<li class=' level1'>
+<a class='' href='upgrade-0-8.html'>Upgrade to 0.8</a>
+</li>
+<li class=' level0'>
+<a class='' href='../troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='../details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>Upgrade to 0.9</h1>
+
+<div id='summary'></div>
+</div>
+<div id='content-box'>
+<h2><a name="upgrading-to-platform-09"></a>Upgrading to Platform 0.9</h2>
+
+<p>You will need the new version of leap_cli:</p>
+
+<pre><code>workstation$ sudo gem install leap_cli --version=1.9
+</code></pre>
+
+<p>If you don&rsquo;t want to install using &lsquo;sudo&rsquo;:</p>
+
+<pre><code>workstation$ gem install --user-install leap_cli --version=1.9
+workstation$ PATH="$PATH:$(ruby -e 'puts Gem.user_dir')/bin"
+</code></pre>
+
+<p>Because 0.9 does not use submodules anymore, you must remove them before pulling
+the latest leap_platform from git:</p>
+
+<pre><code>workstation$ cd leap_platform
+workstation$ for dir in $(git submodule | awk '{print $2}'); do
+workstation$ git submodule deinit $dir
+workstation$ done
+workstation$ git pull
+workstation$ git checkout 0.9.0
+</code></pre>
+
+<p>Alternately, just clone a fresh leap_platform:</p>
+
+<pre><code>workstation$ git clone https://leap.se/git/leap_platform
+workstation$ cd leap_platform
+workstation$ git checkout 0.9.0
+</code></pre>
+
+<p>Then, just deploy</p>
+
+<pre><code>workstation$ cd PROVIDER_DIR
+workstation$ leap deploy
+</code></pre>
+
+<h2><a name="known-issues"></a>Known issues</h2>
+
+<p>When upgrading, sometimes systemd does not report the correct state of a daemon.
+The daemon will be not running, but systemd thinks it is. The symptom of this is
+that a deploy will succeed but <code>leap test</code> will fail. To fix, you can run
+<code>systemctl stop DAEMON</code> and then <code>systemctl start DAEMON</code> on the affected host
+(systemctl restart seems to work less reliably).</p>
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 00000000..49465a9d
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,190 @@
+<!DOCTYPE html>
+<html lang='en'>
+<head>
+<title>
+Provider Platform - LEAP Platform Documentation
+</title>
+<meta content='width=device-width, initial-scale=1.0' name='viewport'>
+<meta charset='UTF-8'>
+<base href="" />
+<style>
+ body {
+ background: #444;
+ display: flex;
+ flex-direction: row;
+ padding: 10px;
+ margin: 0px;
+ }
+ #sidebar {
+ flex: 0 0 250px;
+ background: white;
+ margin-right: 10px;
+ padding: 20px;
+ }
+ #sidebar ul {
+ list-style-type: none;
+ padding-left: 0px;
+ margin: 0;
+ }
+ #sidebar li { padding: 4px }
+ #sidebar li a { text-decoration: none }
+ #sidebar li.active { background: #444 }
+ #sidebar li.active a { color: white }
+ #sidebar li.level1 { padding-left: 20px }
+ #sidebar li.level2 { padding-left: 40px }
+ #main {
+ flex: 1 1 auto;
+ background: white;
+ padding: 20px;
+ }
+ #title-box {
+ padding-bottom: 20px;
+ border-bottom: 5px solid #eee;
+ }
+ #title-box h1 {
+ margin-top: 0px;
+ }
+ pre {
+ padding: 10px;
+ background: #eef;
+ }
+ code {
+ background: #eef;
+ }
+ table {border-collapse: collapse}
+ table td {
+ border: 1px solid #ccc;
+ padding: 4px;
+ vertical-align: top;
+ }
+</style>
+</head>
+<body>
+<div id='sidebar'>
+<ul>
+<li class='active'>
+<a href='index.html'>Home</a>
+</li>
+<li class=' level0'>
+<a class='' href='en/guide.html'>Guide</a>
+</li>
+<li class=' level0'>
+<a class='' href='en/tutorials.html'>Tutorials</a>
+</li>
+<li class=' level0'>
+<a class='' href='en/services.html'>Services</a>
+</li>
+<li class=' level0'>
+<a class='' href='en/upgrading.html'>Upgrading</a>
+</li>
+<li class=' level0'>
+<a class='' href='en/troubleshooting.html'>Troubleshooting</a>
+</li>
+<li class=' level0'>
+<a class='' href='en/details.html'>Details</a>
+</li>
+</ul>
+</div>
+<div id='main'>
+<div id='title-box'>
+<h1>LEAP Platform for Service Providers</h1>
+
+<div id='summary'>The LEAP Platform is set of complementary packages and server recipes to automate the maintenance of LEAP services in a hardened Debian environment.</div>
+</div>
+<div id='content-box'>
+<p>Its goal is to make it as painless as possible for sysadmins to deploy and maintain a service provider&rsquo;s infrastructure for secure communication.</p>
+
+<p><strong>REQUIREMENTS</strong> &ndash; Before you begin, make sure you meet these requirements:</p>
+
+<ul>
+<li><em>Debian Servers</em>: Servers that you deploy to must be running <strong>Debian Jessie</strong>, and no other distribution or version.</li>
+<li><em>Real or Paravirtualized Servers</em>: Servers must be real machines or paravirtualized VMs (e.g. KVM, Xen, OpenStack, AWS, Google Compute). OS level virtualization is not supported (e.g. OpenVZ, Linux-VServer, etc), nor are system emulators (VirtualBox, QEMU, etc).</li>
+<li><em>Your Workstation</em>: You must have a Linux or Mac computer to deploy from (this can be a headless machine with no GUI). Windows is not supported (Cygwin would probably work, but is untested).</li>
+<li><em>Your Own Domain</em>: You must own a domain name. Before your provider can be put into production, you will need to make modifications to the DNS for the provider&rsquo;s domain.</li>
+</ul>
+
+
+<p>The LEAP Platform consists of three parts, detailed below:</p>
+
+<ol>
+<li><a href="index.html#the-platform-recipes">The platform recipes.</a></li>
+<li><a href="index.html#the-provider-instance">The provider instance.</a></li>
+<li><a href="index.html#the-leap-command-line-tool">The <code>leap</code> command line tool.</a></li>
+</ol>
+
+
+<h2><a name="the-platform-recipes"></a>The platform recipes</h2>
+
+<p>The LEAP platform recipes define an abstract service provider. It is a set of <a href="https://puppetlabs.com/puppet/puppet-open-source/">Puppet</a> modules designed to work together to provide to sysadmins everything they need to manage a service provider infrastructure that provides secure communication services.</p>
+
+<p>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.</p>
+
+<p>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).</p>
+
+<p>LEAP&rsquo;s platform recipes are distributed as a git repository: <code>https://leap.se/git/leap_platform</code></p>
+
+<h2><a name="the-provider-instance"></a>The provider instance</h2>
+
+<p>A provider instance is a directory tree (typically tracked in git) containing all the configurations for a service provider&rsquo;s infrastructure. A provider instance <strong>lives on your workstation</strong>, not on the server.</p>
+
+<p>A provider instance primarily consists of:</p>
+
+<ul>
+<li>A pointer to the platform recipes.</li>
+<li>A global configuration file for the provider.</li>
+<li>A configuration file for each server (node) in the provider&rsquo;s infrastructure.</li>
+<li>Additional files, such as certificates and keys.</li>
+</ul>
+
+
+<p>A minimal provider instance directory looks like this:</p>
+
+<pre><code>└── 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.
+</code></pre>
+
+<p>A provider instance directory contains everything needed to manage all the servers that compose a provider&rsquo;s infrastructure. Because of this, any versioning tool and development work-flow can be used to manage your provider instance.</p>
+
+<h2><a name="the-leap-command-line-tool"></a>The <code>leap</code> command line tool</h2>
+
+<p>The <code>leap</code> <a href="en/commands.html">command line tool</a> is used by sysadmins to manage everything about a service provider&rsquo;s infrastructure.</p>
+
+<p>Keep these rules in mind:</p>
+
+<ul>
+<li><code>leap</code> is run on your workstation: The <code>leap</code> command is always run locally on your workstation, never on a server you are deploying to.</li>
+<li><code>leap</code> is run from within a provider instance: The <code>leap</code> command requires that the current working directory is a valid provider instance, except when running <code>leap new</code> to create a new provider instance.</li>
+</ul>
+
+
+<p>The <code>leap</code> command line has many capabilities, including:</p>
+
+<ul>
+<li>Create, initialize, and deploy nodes.</li>
+<li>Manage keys and certificates.</li>
+<li>Query information about the node configurations.</li>
+</ul>
+
+
+<p>Everything about your provider is managed by editing JSON configuration files and running <code>leap</code> commands.</p>
+
+<h2><a name="what-is-next"></a>What is next?</h2>
+
+<p>We recommend reading the platform documentation in the following order:</p>
+
+<ol>
+<li><a href="en/tutorials/quick-start.html">Quick Start Tutorial</a></li>
+<li><a href="en/guide/getting-started.html">Getting Started</a></li>
+<li><a href="en/guide.html">Guide</a></li>
+</ol>
+
+
+</div>
+</div>
+</body>
+</html>
diff --git a/docs/robots.txt.html b/docs/robots.txt.html
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/docs/robots.txt.html
diff --git a/leap-debug-remote.sh b/leap-debug-remote.sh
deleted file mode 100644
index 7f9c6945..00000000
--- a/leap-debug-remote.sh
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/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/platform.rb b/lib/leap/platform.rb
new file mode 100644
index 00000000..9e6cadd5
--- /dev/null
+++ b/lib/leap/platform.rb
@@ -0,0 +1,99 @@
+module Leap
+
+ class Platform
+ class << self
+ #
+ # configuration
+ #
+
+ attr_reader :version
+ attr_reader :compatible_cli
+ attr_accessor :facts
+ attr_accessor :paths
+ attr_accessor :node_files
+ attr_accessor :monitor_username
+ attr_accessor :reserved_usernames
+
+ attr_accessor :hiera_dir
+ attr_accessor :hiera_path
+ attr_accessor :files_dir
+ attr_accessor :leap_dir
+ attr_accessor :init_path
+
+ attr_accessor :default_puppet_tags
+
+ def define(&block)
+ # some defaults:
+ @reserved_usernames = []
+ @hiera_dir = '/etc/leap'
+ @hiera_path = '/etc/leap/hiera.yaml'
+ @leap_dir = '/srv/leap'
+ @files_dir = '/srv/leap/files'
+ @init_path = '/srv/leap/initialized'
+ @default_puppet_tags = []
+
+ self.instance_eval(&block)
+
+ @version ||= Gem::Version.new("0.0")
+ end
+
+ def validate!(cli_version, compatible_platforms, leapfile)
+ if !compatible_with_cli?(cli_version) || !version_in_range?(compatible_platforms)
+ raise StandardError, "This leap command (v#{cli_version}) " +
+ "is not compatible with the platform #{leapfile.platform_directory_path} (v#{version}).\n " +
+ "You need either leap command #{compatible_cli.first} to #{compatible_cli.last} or " +
+ "platform version #{compatible_platforms.first} to #{compatible_platforms.last}"
+ end
+ end
+
+ def version=(version)
+ @version = Gem::Version.new(version)
+ end
+
+ def compatible_cli=(range)
+ @compatible_cli = range
+ @minimum_cli_version = Gem::Version.new(range.first)
+ @maximum_cli_version = Gem::Version.new(range.last)
+ end
+
+ #
+ # return true if the cli_version is compatible with this platform.
+ #
+ def compatible_with_cli?(cli_version)
+ cli_version = Gem::Version.new(cli_version)
+ cli_version >= @minimum_cli_version && cli_version <= @maximum_cli_version
+ end
+
+ #
+ # return true if the platform version is within the specified range.
+ #
+ def version_in_range?(range)
+ if range.is_a? String
+ range = range.split('..')
+ end
+ minimum_platform_version = Gem::Version.new(range.first)
+ maximum_platform_version = Gem::Version.new(range.last)
+ @version >= minimum_platform_version && @version <= maximum_platform_version
+ end
+
+ def major_version
+ if @version.segments.first == 0
+ @version.segments[0..1].join('.')
+ else
+ @version.segments.first
+ end
+ end
+
+ def method_missing(method, *args)
+ puts
+ puts "WARNING:"
+ puts " leap_cli is out of date and does not understand `#{method}`."
+ puts " called from: #{caller.first}"
+ puts " please upgrade to a newer leap_cli"
+ end
+
+ end
+
+ end
+
+end \ No newline at end of file
diff --git a/lib/leap_cli/acme.rb b/lib/leap_cli/acme.rb
new file mode 100644
index 00000000..6c7dbe98
--- /dev/null
+++ b/lib/leap_cli/acme.rb
@@ -0,0 +1,101 @@
+require 'openssl'
+require 'acme-client'
+
+#
+# A little bit of sugar around gem acme-client
+#
+
+module LeapCli
+ class Acme
+
+ if ENV['ACME_STAGING']
+ ENDPOINT = 'https://acme-staging.api.letsencrypt.org/'
+ puts "using endpoint " + ENDPOINT
+ else
+ ENDPOINT = 'https://acme-v01.api.letsencrypt.org/'
+ end
+
+ def initialize(domain: nil, key:)
+ @client = ::Acme::Client.new(
+ private_key: key,
+ endpoint: ENDPOINT,
+ connection_options: {request: {open_timeout: 5, timeout: 5}}
+ )
+ @domain = domain
+ end
+
+ #
+ # static methods
+ #
+
+ def self.new_private_key
+ return OpenSSL::PKey::RSA.new(4096)
+ end
+
+ def self.load_private_key(pem_encoded_key)
+ return OpenSSL::PKey::RSA.new(pem_encoded_key)
+ end
+
+ def self.load_csr(pem_encoded_csr)
+ return OpenSSL::X509::Request.new(pem_encoded_csr)
+ end
+
+ #
+ # instance methods
+ #
+
+ #
+ # register a new account key with CA
+ #
+ def register(contact)
+ registration = @client.register(contact: 'mailto:' + contact)
+ if registration && registration.agree_terms
+ return registration
+ else
+ return false
+ end
+ end
+
+ #
+ # authorize account key for domain
+ #
+ def authorize
+ authorization = @client.authorize(domain: @domain)
+ challenge = nil
+ begin
+ while true
+ if authorization.status == 'pending'
+ challenge = authorization.http01
+ yield challenge
+ challenge.request_verification
+ sleep 1
+ authorization.verify_status
+ if challenge.error
+ return 'error', challenge.error
+ end
+ elsif authorization.status == 'invalid'
+ challenge_msg = (challenge.nil? ? '' : challenge.error)
+ return 'error', 'Something bad happened. %s' % challenge_msg
+ elsif authorization.status == 'valid'
+ return 'valid', nil
+ else
+ challenge_msg = (challenge.nil? ? '' : challenge.error)
+ return 'error', 'status: %s, response message: %s' % [authorization.status, challenge_msg]
+ end
+ end
+ rescue Interrupt
+ return 'error', 'interrupted'
+ end
+ rescue ::Acme::Client::Error::Unauthorized => exc
+ return 'unauthorized', exc.to_s
+ end
+
+ #
+ # get new certificate
+ #
+ def get_certificate(csr)
+ return @client.new_certificate(csr)
+ end
+
+ end
+end \ No newline at end of file
diff --git a/lib/leap_cli/cloud.rb b/lib/leap_cli/cloud.rb
new file mode 100644
index 00000000..268cea38
--- /dev/null
+++ b/lib/leap_cli/cloud.rb
@@ -0,0 +1,4 @@
+
+require 'fog/aws'
+require_relative 'cloud/cloud.rb'
+require_relative 'cloud/image.rb'
diff --git a/lib/leap_cli/cloud/cloud.rb b/lib/leap_cli/cloud/cloud.rb
new file mode 100644
index 00000000..2c06e7ed
--- /dev/null
+++ b/lib/leap_cli/cloud/cloud.rb
@@ -0,0 +1,380 @@
+#
+# An abstractions in front of Fog, which is an abstraction in front of
+# the AWS api. Oh my!
+#
+# A Cloud object binds a particular node with particular Fog
+# authentication credentials.
+#
+# NOTE: Possible AWS options for creating instances:
+#
+# options = {
+# 'BlockDeviceMapping' => block_device_mapping,
+# 'NetworkInterfaces' => network_interfaces,
+# 'ClientToken' => client_token,
+# 'DisableApiTermination' => disable_api_termination,
+# 'EbsOptimized' => ebs_optimized,
+# 'IamInstanceProfile.Arn' => @iam_instance_profile_arn,
+# 'IamInstanceProfile.Name' => @iam_instance_profile_name,
+# 'InstanceInitiatedShutdownBehavior' => instance_initiated_shutdown_behavior,
+# 'InstanceType' => flavor_id,
+# 'KernelId' => kernel_id,
+# 'KeyName' => key_name,
+# 'Monitoring.Enabled' => monitoring,
+# 'Placement.AvailabilityZone' => availability_zone,
+# 'Placement.GroupName' => placement_group,
+# 'Placement.Tenancy' => tenancy,
+# 'PrivateIpAddress' => private_ip_address,
+# 'RamdiskId' => ramdisk_id,
+# 'SecurityGroup' => groups,
+# 'SecurityGroupId' => security_group_ids,
+# 'SubnetId' => subnet_id,
+# 'UserData' => user_data,
+# }
+#
+
+module LeapCli
+ class Cloud
+ DEFAULT_INSTANCE_OPTIONS = {
+ "InstanceType" => "t2.nano"
+ }
+ LEAP_SG_NAME = 'leap_default'
+ LEAP_SG_DESC = 'Default security group for LEAP nodes'
+
+ include LeapCli::LogCommand
+
+ attr_reader :compute # Fog::Compute object
+ attr_reader :node # Config::Node, if any
+ attr_reader :options # options for the VMs, if any
+ attr_reader :image # which vm image to use, if any
+ attr_reader :name # name of which entry in cloud.json to use
+
+ def initialize(name, conf, node=nil)
+ @node = node
+ @name = name
+ @conf = conf
+ @compute = nil
+ @options = DEFAULT_INSTANCE_OPTIONS
+ @image = nil
+
+ raise ArgumentError, 'name missing' unless @name
+ raise ArgumentError, 'config missing' unless @conf
+ raise ArgumentError, 'config auth missing' unless @conf["auth"]
+ raise ArgumentError, 'config auth missing' unless @conf["vendor"]
+
+ credentials = @conf["auth"].symbolize_keys
+ credentials[:provider] = @conf["vendor"]
+ @compute = Fog::Compute.new(credentials)
+
+ if @conf['default_options']
+ @options = @options.merge(@conf['default_options'])
+ end
+ @image = @conf['default_image'] || Cloud.aws_image(credentials[:region])
+ if @node
+ @options = @options.merge(node.vm.options) if node['vm.options']
+ @image = node.vm.image if node['vm.image']
+ end
+
+ unless @options['InstanceType']
+ raise ArgumentError, 'VM instance type is required. See https://leap.se/virtual-machines for more information.'
+ end
+ unless @image
+ raise ArgumentError, 'VM image is required. See https://leap.se/virtual-machines for more information.'
+ end
+ end
+
+ #
+ # fetches or creates a server for this cloud object.
+ #
+ def fetch_or_create_server(options)
+ fetch_server_for_node || create_new_vm_instance(choose_ssh_key: options[:choose_ssh_key])
+ end
+
+ #
+ # fetches the server for a particular node.
+ #
+ # return nil if this cloud object has no node, or there is no corresponding
+ # server.
+ #
+ def fetch_server_for_node(bail_on_failure=false)
+ server = nil
+ return nil unless @node
+
+ # does an instance exist that matches the node's vm.id?
+ if @node.vm_id?
+ instance_id = @node.vm.id
+ server = @compute.servers.get(instance_id)
+ end
+
+ # does an instance exist that is tagged with this node name?
+ if server.nil?
+ response = @compute.describe_instances({"tag:node_name" => @node.name})
+ # puts JSON.pretty_generate(response.body)
+ if !response.body["reservationSet"].empty?
+ instances = response.body["reservationSet"].first["instancesSet"]
+ if instances.size > 1
+ bail! "There are multiple VMs with the same node name tag! Manually remove one before continuing."
+ elsif instances.size == 1
+ instance_id = instances.first["instanceId"]
+ server = @compute.servers.get(instance_id)
+ end
+ end
+ end
+
+ if server.nil? && bail_on_failure
+ bail! :error, "A virtual machine could not be found for node `#{@node.name}`. Things to try:" do
+ log "check the output of `leap vm status`"
+ log "check the value of `vm.id` in #{@node.name}.json"
+ log "run `leap vm add #{@node.name}` to create a corresponding virtual machine"
+ end
+ end
+
+ return server
+ end
+
+ #
+ # associates a node with a vm
+ #
+ def bind_server_to_node(server)
+ unless @node
+ raise ArgumentError, 'no node'
+ end
+ if server.public_ip_address.nil?
+ bail! do
+ log 'The virtual machine `%s` must have an IP address in order to bind it to the configuration `%s`.' % [
+ server.id, Path.relative_path(Path.named_path([:node_config, @node.name]))]
+ log 'To fix, run `leap vm start %s`' % server.id
+ end
+ end
+
+ # assign tag
+ @compute.create_tags(server.id, {'node_name' => @node.name})
+ log :created, "association between node '%s' and vm '%s'" % [@node.name, server.id]
+
+ # update node json
+ @node.update_json({
+ "ip_address" => server.public_ip_address,
+ "vm"=> {"id"=>server.id}
+ })
+ end
+
+ #
+ # disassociates a node from a vm
+ #
+ def unbind_server_from_node(server)
+ raise ArgumentError, 'no node' unless @node
+
+ # assign tag
+ @compute.delete_tags(server.id, {'node_name' => @node.name})
+ log :removed, "association between node '%s' and vm '%s'" % [@node.name, server.id]
+
+ # update node json
+ @node.update_json({
+ "ip_address" => '0.0.0.0',
+ "vm"=> {"id" => ""}
+ })
+ end
+
+ #
+ # return an AWS KeyPair object, potentially uploading it to the server
+ # if necessary.
+ #
+ # this is used when initially creating the vm. After the first `node init`, then
+ # all sysadmins should have access to the server.
+ #
+ # NOTE: ssh and aws use different types of fingerprint
+ #
+ def find_or_create_key_pair(pick_ssh_key_method)
+ require 'leap_cli/ssh'
+ key_pair, local_key = match_ssh_key(:user_only => true)
+ if key_pair
+ log :using, "user SSH key #{local_key.filename}" do
+ log 'AWS MD5 fingerprint: ' + local_key.fingerprint(:digest => :md5, :type => :der, :encoding => :hex)
+ log 'SSH MD5 fingerprint: ' + local_key.fingerprint(:digest => :md5, :type => :ssh, :encoding => :hex)
+ log 'SSH SHA256 fingerprint: ' + local_key.fingerprint(:digest => :sha256, :type => :ssh, :encoding => :base64)
+ end
+ elsif key_pair.nil?
+ username, key = pick_ssh_key_method.call(self)
+ key_pair = upload_ssh_key(username, key)
+ end
+ return key_pair
+ end
+
+ #
+ # checks if there is a match between a local key and a registered key_pair
+ #
+ # options:
+ # :key_pair -- limit comparisons to this key_pair object.
+ # :user_only -- limit comparisons to the user's ~/.ssh directory only
+ #
+ # returns:
+ #
+ # key_pair -- an AWS KeyPair
+ # local_key -- a LeapCLi::SSH::Key
+ #
+ def match_ssh_key(options={})
+ key_pair = options[:key_pair]
+ local_keys_to_check = LeapCli::SSH::Key.my_public_keys
+ unless options[:user_only]
+ local_keys_to_check += LeapCli::SSH::Key.provider_public_keys
+ end
+ fingerprints = Hash[local_keys_to_check.map {|k|
+ [k.fingerprint(:digest => :md5, :type => :der, :encoding => :hex), k]
+ }]
+ key_pair ||= @compute.key_pairs.select {|key_pair|
+ fingerprints.include?(key_pair.fingerprint)
+ }.first
+ if key_pair
+ local_key = fingerprints[key_pair.fingerprint]
+ return key_pair, local_key
+ else
+ return nil, nil
+ end
+ end
+
+ def wait_for_ssh_host_key(server)
+ require 'leap_cli/ssh'
+ return nil if Fog.mock?
+ tries = 0
+ host_key = nil
+ cloud = self
+ server.wait_for {
+ if tries > 0
+ LeapCli.log :waiting, "for SSH host key..."
+ elsif tries > 20
+ return nil
+ end
+ tries += 1
+ ssh_host_keys = cloud.ssh_host_keys(server)
+ if ssh_host_keys.nil?
+ false
+ else
+ host_key = SSH::Key.pick_best_key(ssh_host_keys)
+ true
+ end
+ }
+ return host_key
+ end
+
+ #
+ # checks the console of the server for the ssh host keys
+ #
+ # returns nil if they cannot be found.
+ #
+ def ssh_host_keys(server)
+ require 'leap_cli/ssh'
+ return nil if Fog.mock?
+ response = @compute.get_console_output(server.id)
+ output = response.body["output"]
+ if output.nil?
+ return nil
+ end
+ keys = output.match(
+ /-----BEGIN SSH HOST KEY KEYS-----(.*)-----END SSH HOST KEY KEYS-----/m
+ )
+ if keys.nil?
+ return nil
+ else
+ ssh_key_list = keys[1].strip.split("\r\n").map {|key_str|
+ SSH::Key.load(key_str)
+ }
+ return ssh_key_list.compact
+ end
+ end
+
+ private
+
+ #
+ # Every AWS instance requires a security group, which is just a simple firewall.
+ # In the future, we could create a separate security group for each node,
+ # and set the rules to match what the rules should be for that node.
+ #
+ # However, for now, we just use a security group 'leap_default' that opens
+ # all the ports.
+ #
+ # The default behavior for AWS security groups is:
+ # all ingress traffic is blocked and all egress traffic is allowed.
+ #
+ def find_or_create_security_group
+ group = @compute.security_groups.get(LEAP_SG_NAME)
+ if group.nil?
+ group = @compute.security_groups.create(
+ :name => LEAP_SG_NAME,
+ :description => LEAP_SG_DESC
+ )
+ group.authorize_port_range(0..65535,
+ :ip_protocol => 'tcp',
+ :cidr_ip => '0.0.0.0/0',
+ )
+ group.authorize_port_range(0..65535,
+ :ip_protocol => 'udp',
+ :cidr_ip => '0.0.0.0/0',
+ )
+ end
+ return group
+ end
+
+ #
+ # key - a LeapCli::SSH::Key object
+ # returns -- AWS KeyPair
+ #
+ def upload_ssh_key(username, key)
+ key_name = 'leap_' + username
+ key_pair = @compute.key_pairs.create(
+ :name => key_name,
+ :public_key => key.public_key.to_s
+ )
+ log :registered, "public key" do
+ log 'cloud provider: ' + @name
+ log 'name: ' + key_name
+ log 'AWS MD5 fingerprint: ' + key.fingerprint(:digest => :md5, :type => :der, :encoding => :hex)
+ log 'SSH MD5 fingerprint: ' + key.fingerprint(:digest => :md5, :type => :ssh, :encoding => :hex)
+ log 'SSH SHA256 fingerprint: ' + key.fingerprint(:digest => :sha256, :type => :ssh, :encoding => :base64)
+ end
+ return key_pair
+ end
+
+ def create_new_vm_instance(choose_ssh_key: nil)
+ log :creating, "new vm instance..."
+ assert! @image, "No image found. Specify `default_image` in cloud.json or `vm.image` in node's config."
+ if Fog.mock?
+ options = @options
+ else
+ key_pair = find_or_create_key_pair(choose_ssh_key)
+ security_group = find_or_create_security_group
+ options = @options.merge({
+ 'KeyName' => key_pair.name,
+ 'SecurityGroup' => security_group.name
+ })
+ end
+ response = @compute.run_instances(
+ @image,
+ 1, # min count
+ 1, # max count
+ options
+ )
+ instance_id = response.body["instancesSet"].first["instanceId"]
+ log :created, "vm with instance id #{instance_id}."
+ server = @compute.servers.get(instance_id)
+ if server.nil?
+ bail! :error, "could not query instance '#{instance_id}'."
+ end
+ unless Fog.mock?
+ tries = 0
+ server.wait_for {
+ if tries > 0
+ LeapCli.log :waiting, "for IP address to be assigned..."
+ end
+ tries += 1
+ !public_ip_address.nil?
+ }
+ if options[:wait]
+ log :waiting, "for vm #{instance_id} to start..."
+ server.wait_for { ready? }
+ log :started, "#{instance_id} with #{server.public_ip_address}"
+ end
+ end
+ return server
+ end
+
+ end
+end \ No newline at end of file
diff --git a/lib/leap_cli/cloud/dependencies.rb b/lib/leap_cli/cloud/dependencies.rb
new file mode 100644
index 00000000..fd690e59
--- /dev/null
+++ b/lib/leap_cli/cloud/dependencies.rb
@@ -0,0 +1,40 @@
+#
+# I am not sure this is a good idea, but it might be. Tricky, so disabled for now
+#
+
+=begin
+module LeapCli
+ class Cloud
+
+ def self.check_required_gems
+ begin
+ require "fog"
+ rescue LoadError
+ bail! do
+ log :error, "The 'vm' command requires the gem 'fog-core'. Please run `gem install fog-core` and try again."
+ end
+ end
+
+ fog_gems = @cloud.required_gems
+ if !options[:mock] && fog_gems.empty?
+ bail! do
+ log :warning, "no vm providers are configured in cloud.json."
+ log "You must have credentials for one of: #{@cloud.possible_apis.join(', ')}."
+ end
+ end
+
+ fog_gems.each do |name, gem_name|
+ begin
+ require gem_name.sub('-','/')
+ rescue LoadError
+ bail! do
+ log :error, "The 'vm' command requires the gem '#{gem_name}' (because of what is configured in cloud.json)."
+ log "Please run `sudo gem install #{gem_name}` and try again."
+ end
+ end
+ end
+ end
+
+ end
+end
+=end \ No newline at end of file
diff --git a/lib/leap_cli/cloud/image.rb b/lib/leap_cli/cloud/image.rb
new file mode 100644
index 00000000..1f7f47b9
--- /dev/null
+++ b/lib/leap_cli/cloud/image.rb
@@ -0,0 +1,31 @@
+module LeapCli
+ class Cloud
+
+ #
+ # returns the latest official debian image for
+ # a particular AWS region
+ #
+ # https://wiki.debian.org/Cloud/AmazonEC2Image/Jessie
+ # current list based on Debian 8.4
+ #
+ # might return nil if no image is found.
+ #
+ def self.aws_image(region)
+ image_list = %q[
+ ap-northeast-1 ami-d7d4c5b9
+ ap-northeast-2 ami-9a03caf4
+ ap-southeast-1 ami-73974210
+ ap-southeast-2 ami-09daf96a
+ eu-central-1 ami-ccc021a3
+ eu-west-1 ami-e079f893
+ sa-east-1 ami-d3ae21bf
+ us-east-1 ami-c8bda8a2
+ us-west-1 ami-45374b25
+ us-west-2 ami-98e114f8
+ ]
+ region_to_image = Hash[image_list.strip.split("\n").map{|i| i.split(" ")}]
+ return region_to_image[region]
+ end
+
+ end
+end \ No newline at end of file
diff --git a/lib/leap_cli/commands/ca.rb b/lib/leap_cli/commands/ca.rb
index 1b311eee..3c5fc7d5 100644
--- a/lib/leap_cli/commands/ca.rb
+++ b/lib/leap_cli/commands/ca.rb
@@ -1,8 +1,3 @@
-autoload :OpenSSL, 'openssl'
-autoload :CertificateAuthority, 'certificate_authority'
-autoload :Date, 'date'
-require 'digest/md5'
-
module LeapCli; module Commands
desc "Manage X.509 certificates"
@@ -35,41 +30,15 @@ module LeapCli; module Commands
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
+ generate_dh
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.arg_name "DOMAIN"
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."
@@ -81,70 +50,26 @@ module LeapCli; module Commands
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
+ generate_csr(global_options, options, args)
+ end
+ 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
+ cert.desc "Register an authorization key with the CA letsencrypt.org"
+ cert.long_desc "This only needs to be done once."
+ cert.command :register do |register|
+ register.action do |global, options, args|
+ do_register_key(global, options, args)
+ 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
+ cert.desc "Renews a certificate using the CA letsencrypt.org"
+ cert.arg_name "DOMAIN"
+ cert.command :renew do |renew|
+ renew.action do |global, options, args|
+ do_renew_cert(global, options, args)
end
end
+
end
protected
@@ -153,6 +78,7 @@ module LeapCli; module Commands
# will generate new certificates for the specified nodes, if needed.
#
def update_certificates(nodes, options={})
+ require 'leap_cli/x509'
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'
@@ -160,382 +86,281 @@ module LeapCli; module Commands
assert_config! 'common.x509.use'
nodes.each_node do |node|
- warn_if_commercial_cert_will_soon_expire(node)
+ node.warn_if_commercial_cert_will_soon_expire
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)
+ elsif options[:force] || node.cert_needs_updating?
+ node.generate_cert
end
end
end
+ #
+ # yields client key and cert suitable for testing
+ #
+ def generate_test_client_cert(prefix=nil)
+ require 'leap_cli/x509'
+ 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 = X509.yesterday
+ cert.not_after = X509.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
+
private
def generate_new_certificate_authority(key_file, cert_file, common_name)
+ require 'leap_cli/x509'
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
+ root = X509.new_ca(provider.ca, common_name)
- # 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])
+ write_file!(key_file, root.key_material.private_key.to_pem)
+ write_file!(cert_file, root.to_pem)
+ end
+
+ def generate_dh
+ require 'leap_cli/x509'
+ 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
- # 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)
+ #
+ # 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
+ #
+ def generate_csr(global_options, options, args)
+ require 'leap_cli/x509'
+ 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'
- # sign self
- root.signing_entity = true
- root.parent = root
- root.sign!(ca_root_signing_profile)
+ server_certificates = provider.ca.server_certificates
+ options[:domain] ||= args.first || provider.domain
+ options[:organization] ||= provider.name[provider.default_language]
+ options[:country] ||= server_certificates['country']
+ options[:state] ||= server_certificates['state']
+ options[:locality] ||= server_certificates['locality']
+ options[:bits] ||= server_certificates.bit_size
+ options[:digest] ||= server_certificates.digest
+
+ unless global_options[:force]
+ assert_files_missing! [:commercial_key, options[:domain]], [:commercial_csr, options[:domain]],
+ :msg => 'If you really want to create a new key and CSR, remove these files first or run with --force.'
+ end
- # save
- write_file!(key_file, root.key_material.private_key.to_pem)
- write_file!(cert_file, root.to_pem)
+ X509.create_csr_and_cert(options)
end
#
- # returns true if the certs associated with +node+ need to be regenerated.
+ # letsencrypt.org
#
- 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
+
+ def do_register_key(global, options, args)
+ require 'leap_cli/acme'
+ assert_config! 'provider.contacts.default'
+ contact = manager.provider.contacts.default.first
+
+ if file_exists?(:acme_key) && !global[:force]
+ bail! do
+ log "the authorization key for letsencrypt.org already exists"
+ log "run with --force if you really want to register a new key."
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
+ else
+ private_key = Acme.new_private_key
+ registration = nil
+
+ log(:registering, "letsencrypt.org authorization key using contact `%s`" % contact) do
+ acme = Acme.new(key: private_key)
+ registration = acme.register(contact)
+ if registration
+ log 'success!', :color => :green, :style => :bold
+ else
+ bail! "could not register authorization key."
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(':')
+ log :saving, "authorization key for letsencrypt.org" do
+ write_file!(:acme_key, private_key.to_pem)
+ write_file!(:acme_info, JSON.sorted_generate({
+ id: registration.id,
+ contact: registration.contact,
+ key: registration.key,
+ uri: registration.uri
+ }))
+ log :warning, "keep key file private!"
+ end
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
+ def assert_no_errors!(msg)
+ yield
+ rescue StandardError => exc
+ bail! :error, msg do
+ log exc.to_s
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)
+ def do_renew_cert(global, options, args)
+ require 'leap_cli/acme'
+ require 'leap_cli/ssh'
+ require 'socket'
+ require 'net/http'
- # set expiration
- cert.not_before = yesterday
- cert.not_after = yesterday_advance(provider.ca.server_certificates.life_span)
+ csr = nil
+ account_key = nil
+ cert = nil
+ acme = nil
- # 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
+ #
+ # sanity check the domain
+ #
+ domain = args.first
+ nodes = nodes_for_domain(domain)
+ domain_ready_for_acme!(domain)
- def ca_root
- @ca_root ||= begin
- load_certificate_file(:ca_cert, :ca_key)
+ #
+ # load key material
+ #
+ assert_files_exist!([:commercial_key, domain], [:commercial_csr, domain],
+ :msg => 'Please create the CSR first with `leap cert csr %s`' % domain)
+ assert_no_errors!("Could not load #{path([:commercial_csr, domain])}") do
+ csr = Acme.load_csr(read_file!([:commercial_csr, domain]))
end
- end
-
- def client_ca_root
- @client_ca_root ||= begin
- load_certificate_file(:client_ca_cert, :client_ca_key)
+ assert_files_exist!(:acme_key,
+ :msg => "Please run `leap cert register` first. This only needs to be done once.")
+ assert_no_errors!("Could not load #{path(:acme_key)}") do
+ account_key = Acme.load_private_key(read_file!(:acme_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)
+ #
+ # check authorization for this domain
+ #
+ log :checking, "authorization"
+ acme = Acme.new(domain: domain, key: account_key)
+ status, message = acme.authorize do |challenge|
+ log(:uploading, 'challenge to server %s' % domain) do
+ SSH.remote_command(nodes) do |ssh, host|
+ ssh.scripts.upload_acme_challenge(challenge.token, challenge.file_content)
+ end
+ end
+ log :waiting, "for letsencrypt.org to verify challenge"
+ end
+ if status == 'valid'
+ log 'authorized!', color: :green, style: :bold
+ elsif status == 'error'
+ bail! :error, message
+ elsif status == 'unauthorized'
+ bail!(:unauthorized, message, color: :yellow, style: :bold) do
+ log 'You must first run `leap cert register` to register the account key with letsencrypt.org'
+ end
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)
- }
- }
- }
+ log :fetching, "new certificate from letsencrypt.org"
+ assert_no_errors!("could not renew certificate") do
+ cert = acme.get_certificate(csr)
+ end
+ log 'success', color: :green, style: :bold
+ write_file!([:commercial_cert, domain], cert.fullchain_to_pem)
+ log 'You should now run `leap deploy` to deploy the new certificate.'
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.
+ # Returns a hash of nodes that match this domain. It also checks:
#
- def domain_test_signing_profile
- {
- "digest" => "SHA256",
- "extensions" => {
- "keyUsage" => {
- "usage" => ["digitalSignature", "keyEncipherment"]
- },
- "extendedKeyUsage" => {
- "usage" => ["serverAuth"]
- }
- }
- }
- end
-
+ # * a node configuration has this domain
+ # * the dns for the domain exists
#
- # This is used when signing a dummy client certificate that is only to be
- # used for testing.
+ # This method will bail if any checks fail.
#
- 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
+ def nodes_for_domain(domain)
+ bail! { log 'Argument DOMAIN is required' } if domain.nil? || domain.empty?
+ nodes = manager.nodes['dns.aliases' => domain]
+ if nodes.empty?
+ bail! :error, "There are no nodes configured for domain `%s`" % domain
end
- names.compact!
- names.sort!
- names.uniq!
- return names
+ begin
+ ips = Socket.getaddrinfo(domain, 'http').map {|record| record[2]}.uniq
+ nodes = nodes['ip_address' => ips]
+ if nodes.empty?
+ bail! do
+ log :error, "The domain `%s` resolves to [%s]" % [domain, ips.join(', ')]
+ log :error, "But there no nodes configured for this domain with these adddresses."
+ end
+ end
+ rescue SocketError
+ bail! :error, "Could not resolve the DNS for `#{domain}`. Without a DNS " +
+ "entry for this domain, authorization will not work."
+ end
+ return nodes
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)
+ # runs the following checks on the domain:
#
- def cert_serial_number(domain_name)
- Digest::MD5.hexdigest("#{domain_name} -- #{Time.now}").to_i(16)
- end
-
+ # * we are able to get /.well-known/acme-challenge/ok
#
- # 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
+ # This method will bail if any checks fail.
#
- 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.")
+ def domain_ready_for_acme!(domain)
+ begin
+ uri = URI("https://#{domain}/.well-known/acme-challenge/ok")
+ options = {
+ use_ssl: true,
+ open_timeout: 5,
+ verify_mode: OpenSSL::SSL::VERIFY_NONE
+ }
+ Net::HTTP.start(uri.host, uri.port, options) do |http|
+ http.request(Net::HTTP::Get.new(uri)) do |response|
+ if !response.is_a?(Net::HTTPSuccess)
+ bail!(:error, "Could not GET %s" % uri) do
+ log "%s %s" % [response.code, response.message]
+ log "You may need to run `leap deploy`"
+ end
+ end
+ end
+ end
+ rescue Errno::ETIMEDOUT, Net::OpenTimeout
+ bail! :error, "Connection attempt timed out: %s" % uri
+ rescue Interrupt
+ bail!
+ rescue StandardError => exc
+ bail!(:error, "Could not GET %s" % uri) do
+ log exc.to_s
+ end
end
- yesterday.advance(unit.to_sym => number.to_i)
end
end; end
diff --git a/lib/leap_cli/commands/compile.rb b/lib/leap_cli/commands/compile.rb
index f9079279..92c879d7 100644
--- a/lib/leap_cli/commands/compile.rb
+++ b/lib/leap_cli/commands/compile.rb
@@ -284,7 +284,6 @@ remove this directory if you don't use it.
# 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 = []
#
diff --git a/lib/leap_cli/commands/db.rb b/lib/leap_cli/commands/db.rb
index 5307ac4d..227d429d 100644
--- a/lib/leap_cli/commands/db.rb
+++ b/lib/leap_cli/commands/db.rb
@@ -50,7 +50,7 @@ module LeapCli; module Commands
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"')
+ ssh.run('/etc/init.d/couchdb stop && test ! -z "$(ls /var/lib/couchdb 2> /dev/null)" && rm -r /var/lib/couchdb/* && echo "All DBs destroyed" || echo "DBs already destroyed"')
end
end
diff --git a/lib/leap_cli/commands/deploy.rb b/lib/leap_cli/commands/deploy.rb
index 9dd190ab..91e25a96 100644
--- a/lib/leap_cli/commands/deploy.rb
+++ b/lib/leap_cli/commands/deploy.rb
@@ -29,57 +29,7 @@ module LeapCli
: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
+ run_deploy(global, options, args)
end
end
@@ -94,19 +44,91 @@ module LeapCli
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
+ run_history(global, options, args)
+ end
+ end
+
+ private
+
+ def run_deploy(global, options, args)
+ require 'leap_cli/ssh'
+
+ 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
- nodes = manager.filter!(args)
- ssh_connect(nodes, connect_options(options)) do |ssh|
- ssh.leap.history(lines)
+ 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)
+
+ log :checking, 'nodes' do
+ SSH.remote_command(nodes, options) do |ssh, host|
+ begin
+ ssh.scripts.check_for_no_deploy
+ ssh.scripts.assert_initialized
+ rescue SSH::ExecuteError
+ # skip nodes with errors, but run others
+ nodes.delete(host.hostname)
+ end
+ end
+ end
+
+ if nodes.empty?
+ return
+ end
+
+ log :synching, "configuration files" do
+ sync_hiera_config(nodes, options)
+ sync_support_files(nodes, options)
+ end
+ log :synching, "puppet manifests" do
+ sync_puppet_files(nodes, options)
+ end
+
+ unless options[:sync]
+ log :applying, "puppet" do
+ SSH.remote_command(nodes, options) do |ssh, host|
+ ssh.scripts.puppet_apply(
+ :verbosity => [LeapCli.log_level,5].min,
+ :tags => tags(options),
+ :force => options[:force],
+ :info => deploy_info,
+ :downgrade => options[:downgrade]
+ )
+ end
end
end
end
- private
+ def run_history(global, options, args)
+ require 'leap_cli/ssh'
+
+ if options[:last] == true
+ lines = 1
+ else
+ lines = 10
+ end
+ nodes = manager.filter!(args)
+ SSH.remote_command(nodes, options) do |ssh, host|
+ ssh.scripts.history(lines)
+ end
+ end
def forcible_prompt(forced, msg, prompt)
say(msg)
@@ -211,56 +233,51 @@ module LeapCli
end
end
- def sync_hiera_config(ssh)
- ssh.rsync.update do |server|
- node = manager.node(server.host)
+ def sync_hiera_config(nodes, options)
+ SSH.remote_sync(nodes, options) do |sync, host|
+ node = manager.node(host.hostname)
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"
- }
+ sync.log hiera_file + ' -> ' + node.name + ':' + Leap::Platform.hiera_path
+ sync.source = hiera_file
+ sync.dest = Leap::Platform.hiera_path
+ sync.flags = "-rltp --chmod=u+rX,go-rwx"
+ sync.exec
end
end
#
# sync various support files.
#
- def sync_support_files(ssh)
- dest_dir = Leap::Platform.files_dir
+ def sync_support_files(nodes, options)
+ dest_dir = Leap::Platform.files_dir
custom_files = build_custom_file_list
- ssh.rsync.update do |server|
- node = manager.node(server.host)
+ SSH.remote_sync(nodes, options) do |sync, host|
+ node = manager.node(host.hostname)
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
+ sync.log(files_to_sync.join(', ') + ' -> ' + node.name + ':' + dest_dir)
+ sync.chdir = Path.named_path(:files_dir)
+ sync.source = "."
+ sync.dest = dest_dir
+ sync.excludes = "*"
+ sync.includes = calculate_includes_from_files(files_to_sync, '/files')
+ sync.flags = "-rltp --chmod=u+rX,go-rwx --relative --delete --delete-excluded --copy-links"
+ sync.exec
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"
- }
+ def sync_puppet_files(nodes, options)
+ SSH.remote_sync(nodes, options) do |sync, host|
+ sync.log(Path.platform + '/[bin,tests,puppet] -> ' + host.hostname + ':' + Leap::Platform.leap_dir)
+ sync.dest = Leap::Platform.leap_dir
+ sync.source = '.'
+ sync.chdir = Path.platform
+ sync.excludes = '*'
+ sync.includes = ['/bin', '/bin/**', '/puppet', '/puppet/**', '/tests', '/tests/server-tests', '/tests/server-tests/**']
+ sync.flags = "-rlt --relative --delete --copy-links"
+ sync.exec
end
end
@@ -269,7 +286,7 @@ module LeapCli
# repository.
#
def init_submodules
- return unless is_git_directory?(Path.platform)
+ return unless is_git_directory?(Path.platform) && !is_git_subrepo?(Path.platform)
Dir.chdir Path.platform do
assert_run! "git submodule sync"
statuses = assert_run! "git submodule status"
diff --git a/lib/leap_cli/commands/facts.rb b/lib/leap_cli/commands/facts.rb
index 11329ccc..74ef463d 100644
--- a/lib/leap_cli/commands/facts.rb
+++ b/lib/leap_cli/commands/facts.rb
@@ -79,15 +79,18 @@ module LeapCli; module Commands
private
def update_facts(global_options, options, args)
+ require 'leap_cli/ssh'
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])
+ SSH.remote_command(nodes) do |ssh, host|
+ response = ssh.capture(facter_cmd, :log_output => false)
+ if response
+ node = manager.node(host.hostname)
if node
- new_facts[node.name] = response[:data].strip
+ new_facts[node.name] = response.strip
+ log 'done', :host => host.to_s
else
- log :warning, 'Could not find node for hostname %s' % response[:host]
+ log :warning, 'Could not find node for hostname %s' % host
end
end
end
diff --git a/lib/leap_cli/commands/info.rb b/lib/leap_cli/commands/info.rb
index 52225a94..a49c20c9 100644
--- a/lib/leap_cli/commands/info.rb
+++ b/lib/leap_cli/commands/info.rb
@@ -5,10 +5,17 @@ module LeapCli; module Commands
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
+ run_info(global, options, args)
+ end
+ end
+
+ private
+
+ def run_info(global, options, args)
+ require 'leap_cli/ssh'
+ nodes = manager.filter!(args)
+ SSH.remote_command(nodes, options) do |ssh, host|
+ ssh.scripts.debug
end
end
diff --git a/lib/leap_cli/commands/inspect.rb b/lib/leap_cli/commands/inspect.rb
index 20654fa7..b71da80e 100644
--- a/lib/leap_cli/commands/inspect.rb
+++ b/lib/leap_cli/commands/inspect.rb
@@ -25,27 +25,22 @@ module LeapCli; module Commands
"PEM certificate request" => :inspect_x509_csr
}
+ SUFFIX_MAP = {
+ ".json" => :inspect_unknown_json,
+ ".key" => :inspect_x509_key
+ }
+
def inspection_method(object)
- if File.exists?(object)
+ if File.exist?(object)
ftype = `file #{object}`.split(':').last.strip
+ suffix = File.extname(object)
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
+ elsif SUFFIX_MAP[suffix]
+ SUFFIX_MAP[suffix]
+ else
+ nil
end
elsif manager.nodes[object]
:inspect_node
@@ -72,8 +67,10 @@ module LeapCli; module Commands
end
def inspect_x509_cert(file_path, options)
+ require 'leap_cli/x509'
assert_bin! 'openssl'
puts assert_run! 'openssl x509 -in %s -text -noout' % file_path
+ log 0, :"SHA1 fingerprint", X509.fingerprint("SHA1", file_path)
log 0, :"SHA256 fingerprint", X509.fingerprint("SHA256", file_path)
end
@@ -123,6 +120,23 @@ module LeapCli; module Commands
end
end
+ def inspect_unknown_json(arg, options)
+ full_path = File.expand_path(arg, Dir.pwd)
+ if path_match?(:node_config, full_path)
+ inspect_node(arg, options)
+ elsif path_match?(:service_config, full_path)
+ inspect_service(arg, options)
+ elsif path_match?(:tag_config, full_path)
+ inspect_tag(arg, options)
+ elsif path_match?(:provider_config, full_path) || path_match?(:provider_env_config, full_path)
+ inspect_provider(arg, options)
+ elsif path_match?(:common_config, full_path)
+ inspect_common(arg, options)
+ else
+ inspect_json(arg, options)
+ end
+ end
+
#
# helpers
#
diff --git a/lib/leap_cli/commands/list.rb b/lib/leap_cli/commands/list.rb
index aa425432..1b3efc27 100644
--- a/lib/leap_cli/commands/list.rb
+++ b/lib/leap_cli/commands/list.rb
@@ -1,5 +1,3 @@
-require 'command_line_reporter'
-
module LeapCli; module Commands
desc 'List nodes and their classifications'
@@ -15,33 +13,38 @@ module LeapCli; module Commands
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
+ do_list(global_options, options, args)
end
end
private
- def self.print_node_properties(nodes, properties)
+ def do_list(global, options, args)
+ require 'leap_cli/util/console_table'
+ # don't rely on default manager(), because we want to pass custom options to load()
+ manager = LeapCli::Config::Manager.new
+ if global[:color]
+ colors = [:cyan, nil]
+ 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
+
+ def 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|
@@ -62,8 +65,7 @@ module LeapCli; module Commands
puts
end
- class TagTable
- include CommandLineReporter
+ class TagTable < LeapCli::Util::ConsoleTable
def initialize(heading, tag_list, colors)
@heading = heading
@tag_list = tag_list
@@ -71,29 +73,24 @@ module LeapCli; module Commands
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
+ table do
+ row(color: @colors[0]) do
+ column @heading, align: 'right', min_width: 20
+ column "NODES"
end
tags.each do |tag|
next if @tag_list[tag].node_list.empty?
- row :color => @colors[1] do
+ row(color: @colors[1]) do
column tag
column @tag_list[tag].node_list.keys.sort.join(', ')
end
end
end
- vertical_spacing
+ draw_table
end
end
- #
- # might be handy: HighLine::SystemExtensions.terminal_size.first
- #
- class NodeTable
- include CommandLineReporter
+ class NodeTable < LeapCli::Util::ConsoleTable
def initialize(node_list, colors)
@node_list = node_list
@colors = colors
@@ -103,29 +100,25 @@ module LeapCli; module Commands
[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 " = " + LeapCli.logger.colorize("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
+ table do
+ row(color: @colors[0]) do
+ column "NODES", align: 'right', min_width: 20
+ column "SERVICES"
+ column "TAGS"
end
rows.each do |r|
- row :color => @colors[1] do
+ row(color: @colors[1]) do
column r[0]
column r[1]
column r[2]
end
end
end
- vertical_spacing
+ draw_table
end
end
diff --git a/lib/leap_cli/commands/node.rb b/lib/leap_cli/commands/node.rb
index a23661b3..60540de9 100644
--- a/lib/leap_cli/commands/node.rb
+++ b/lib/leap_cli/commands/node.rb
@@ -3,8 +3,6 @@
# but all other `node x` commands live here.
#
-autoload :IPAddr, 'ipaddr'
-
module LeapCli; module Commands
##
@@ -18,33 +16,16 @@ module LeapCli; module Commands
"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")
+ "Separate 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.switch :local, :desc => 'Make a local testing node (by assigning the next available local IP address). Local nodes are run as virtual machines on your computer.', :negatable => false
+ add.switch :vm, :desc => 'Make a remote virtual machine for this node. Requires a valid cloud.json configuration.', :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)
+ if options[:vm]
+ do_vm_add(global_options, options, args)
+ else
+ do_node_add(global_options, options, args)
end
end
end
@@ -53,15 +34,7 @@ module LeapCli; module Commands
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)
+ do_node_move(global_options, options, args)
end
end
@@ -69,12 +42,7 @@ module LeapCli; module Commands
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)
+ do_node_rm(global_options, options, args)
end
end
end
@@ -93,96 +61,69 @@ module LeapCli; module Commands
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
+ protected
#
- # load "new node template" information into the `node`, modifying `node`.
- # values in the template will not override existing node values.
+ # additionally called by `leap vm add`
#
- 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
+ def do_node_add(global, options, args)
+ name = args.first
+ unless global[:force]
+ assert_files_missing! [:node_config, name]
end
- end
-
- def remove_node_files(node_name)
- (Leap::Platform.node_files + [:node_files_dir]).each do |path|
- remove_file! [path, node_name]
+ node = Config::Node.new(manager.env)
+ node['name'] = name
+ if options[:ip_address]
+ node['ip_address'] = options[:ip_address]
+ elsif options[:local]
+ node['ip_address'] = pick_next_vagrant_ip_address
end
+ node.seed_from_args(args[1..-1])
+ node.seed_from_template
+ node.validate!
+ node.write_configs
+ # reapply inheritance, since tags/services might have changed:
+ node = manager.reload_node!(node)
+ node.generate_cert
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
+ private
- 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
+ def do_node_move(global, options, args)
+ node = get_node_from_args(args, include_disabled: true)
+ new_name = args.last
+ Config::Node.validate_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
- 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
+ remove_directory! [:node_files_dir, node.name]
+ rename_node_facts(node.name, new_name)
+ if node.vm_id?
+ node['name'] = new_name
+ bind_server_to_node(node.vm.id, node, options)
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)"
+ def do_node_rm(global, options, args)
+ node = get_node_from_args(args, include_disabled: true)
+ if node.vm?
+ if !node.vm_id?
+ log :warning, "The node #{node.name} is missing a 'vm.id' property. "+
+ "You may have a virtual machine instance that is left "+
+ "running. Check `leap vm status`"
+ else
+ msg = "The node #{node.name} appears to be associated with a virtual machine. " +
+ "Do you want to also destroy this virtual machine? "
+ if global[:yes] || agree(msg)
+ do_vm_rm(global, options, args)
+ end
+ end
+ elsif node.vagrant?
+ vagrant_command("destroy --force", [node.name])
end
+ node.remove_files
+ remove_node_facts(node.name)
end
end; end
diff --git a/lib/leap_cli/commands/node_init.rb b/lib/leap_cli/commands/node_init.rb
index 33f6288d..59661295 100644
--- a/lib/leap_cli/commands/node_init.rb
+++ b/lib/leap_cli/commands/node_init.rb
@@ -6,58 +6,70 @@
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, " +
+ command :node do |cmd|
+ cmd.desc 'Bootstraps a node or nodes, setting up SSH keys and installing prerequisite packages'
+ cmd.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
+ cmd.arg_name 'FILTER'
+ cmd.command :init do |init|
+ #init.switch 'echo', :desc => 'If set, passwords are visible as you type them (default is hidden)', :negatable => false
+ # ^^ i am not sure how to get this working with sshkit
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
+ run_node_init(global, options, args)
+ end
+ end
+ end
+
+ private
+
+ def run_node_init(global, options, args)
+ require 'leap_cli/ssh'
+ 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
+ # allow password auth for new nodes:
+ options[:auth_methods] = ["publickey", "password"]
+ if node.vm?
+ # new AWS virtual machines will only allow login as 'admin'
+ # before we continue, we must enable root access.
+ SSH.remote_command(node, options.merge(:user => 'admin')) do |ssh, host|
+ ssh.scripts.allow_root_ssh
+ end
+ end
+ SSH.remote_command(node, options) do |ssh, host|
+ if node.vagrant?
+ ssh.scripts.install_insecure_vagrant_key
+ end
+ ssh.scripts.install_authorized_keys
+ ssh.scripts.install_prerequisites
+ unless node.vagrant?
+ ssh.log(:checking, "SSH host keys") do
+ response = ssh.capture(get_ssh_keys_cmd, :log_output => false)
+ if response
+ update_local_ssh_host_keys(node, response)
end
end
- finished << node.name
end
- log :completed, "initialization of nodes #{finished.join(', ')}"
+ ssh.log(:updating, "facts") do
+ response = ssh.capture(facter_cmd)
+ if response
+ update_node_facts(node.name, response)
+ end
+ end
end
+ finished << node.name
end
+ log :completed, "initialization of nodes #{finished.join(', ')}"
end
- private
-
##
## PRIVATE HELPERS
##
@@ -83,7 +95,7 @@ module LeapCli; module Commands
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)
+ if host_keys.include? SSH::Key.load(pub_key_path)
log :trusted, "- Public SSH host key for #{node.name} matches previously saved key", :indent => 1
else
bail! do
@@ -96,7 +108,7 @@ module LeapCli; module Commands
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)
+ public_key = SSH::Key.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
@@ -118,7 +130,7 @@ module LeapCli; module Commands
#
# Get the public host keys for a host using ssh-keyscan.
- # Return an array of SshKey objects, one for each key.
+ # Return an array of SSH::Key objects, one for each key.
#
def get_public_keys_for_ip(address, port=22)
assert_bin!('ssh-keyscan')
@@ -130,7 +142,7 @@ module LeapCli; module Commands
if output =~ /No route to host/
bail! :failed, 'ssh-keyscan: no route to %s' % address
else
- keys = SshKey.parse_keys(output)
+ keys = SSH::Key.parse_keys(output)
if keys.empty?
bail! "ssh-keyscan got zero host keys back (that we understand)! Output was: #{output}"
else
@@ -139,7 +151,7 @@ module LeapCli; module Commands
end
end
- # run on the server to generate a string suitable for passing to SshKey.parse_keys()
+ # run on the server to generate a string suitable for passing to SSH::Key.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
@@ -149,10 +161,10 @@ module LeapCli; module Commands
# 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)
+ remote_keys = SSH::Key.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)
+ current_key = SSH::Key.load(Path.named_path([:node_ssh_pub_key, node.name]))
+ best_key = SSH::Key.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.")
diff --git a/lib/leap_cli/commands/open.rb b/lib/leap_cli/commands/open.rb
new file mode 100644
index 00000000..3de97298
--- /dev/null
+++ b/lib/leap_cli/commands/open.rb
@@ -0,0 +1,103 @@
+module LeapCli
+ module Commands
+
+ desc 'Opens useful URLs in a web browser.'
+ long_desc "NAME can be one or more of: monitor, web, docs, bug"
+ arg_name 'NAME'
+ command :open do |c|
+ c.flag :env, :desc => 'Which environment to use (optional).', :arg_name => 'ENVIRONMENT'
+ c.switch :ip, :desc => 'To get around HSTS or DNS, open the URL using the IP address instead of the domain (optional).'
+ c.action do |global_options,options,args|
+ do_open_cmd(global_options, options, args)
+ end
+ end
+
+ private
+
+ def do_open_cmd(global, options, args)
+ env = options[:env] || LeapCli.leapfile.environment
+ args.each do |name|
+ if name == 'monitor' || name == 'nagios'
+ open_nagios(env, options[:ip])
+ elsif name == 'web' || name == 'webapp'
+ open_webapp(env, options[:ip])
+ elsif name == 'docs' || name == 'help' || name == 'doc'
+ open_url("https://leap.se/docs")
+ elsif name == 'bug' || name == 'feature' || name == 'bugreport'
+ open_url("https://leap.se/code")
+ else
+ bail! "'#{name}' is not a recognized URL."
+ end
+ end
+ end
+
+ def find_node_with_service(service, environment)
+ nodes = manager.nodes[:services => service]
+ node = nil
+ if nodes.size == 0
+ bail! "No nodes with '#{service}' service."
+ elsif nodes.size == 1
+ node = nodes.values.first
+ elsif nodes.size > 1
+ if environment
+ node = nodes[:environment => environment].values.first
+ if node.nil?
+ bail! "No nodes with '#{service}' service."
+ end
+ else
+ node_list = nodes.values
+ list = node_list.map {|i| "#{i.name} (#{i.environment})"}
+ index = numbered_choice_menu("Which #{service}?", list) do |line, i|
+ say("#{i+1}. #{line}")
+ end
+ node = node_list[index]
+ end
+ end
+ return node
+ end
+
+ def pick_domain(node, ip)
+ bail! "monitor missing webapp service" unless node["webapp"]
+ if ip
+ domain = node["ip_address"]
+ else
+ domain = node["webapp"]["domain"]
+ bail! "webapp domain is missing" unless !domain.empty?
+ end
+ return domain
+ end
+
+ def open_webapp(environment, ip)
+ node = find_node_with_service('webapp', environment)
+ domain = pick_domain(node, ip)
+ open_url("https://%s" % domain)
+ end
+
+ def open_nagios(environment, ip)
+ node = find_node_with_service('monitor', environment)
+ domain = pick_domain(node, ip)
+ username = 'nagiosadmin'
+ password = manager.secrets.retrieve("nagios_admin_password", node.environment)
+ bail! "unable to find nagios_admin_password" unless !password.nil? && !password.empty?
+ open_url("https://%s:%s@%s/nagios3" % [username, password, domain])
+ end
+
+ def open_url(url)
+ log :opening, url
+ if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
+ system %(start "#{url}")
+ elsif RbConfig::CONFIG['host_os'] =~ /darwin/
+ system %(open "#{url}")
+ elsif RbConfig::CONFIG['host_os'] =~ /linux|bsd/
+ ['xdg-open', 'sensible-browser', 'gnome-open', 'kde-open'].each do |cmd|
+ if !`which #{cmd}`.strip.empty?
+ system %(#{cmd} "#{url}")
+ return
+ end
+ end
+ log :error, 'no command found to launch browser window.'
+ end
+ end
+
+ end
+end \ No newline at end of file
diff --git a/lib/leap_cli/commands/run.rb b/lib/leap_cli/commands/run.rb
new file mode 100644
index 00000000..cad9b7a0
--- /dev/null
+++ b/lib/leap_cli/commands/run.rb
@@ -0,0 +1,50 @@
+module LeapCli; module Commands
+
+ desc 'Run a shell command remotely'
+ long_desc "Runs the specified command COMMAND on each node in the FILTER set. " +
+ "For example, `leap run 'uname -a' webapp`"
+ arg_name 'COMMAND FILTER'
+ command :run do |c|
+ c.switch 'stream', :default => false, :desc => 'If set, stream the output as it arrives. (default: --stream for a single node, --no-stream for multiple nodes)'
+ c.flag 'port', :arg_name => 'SSH_PORT', :desc => 'Override default SSH port used when trying to connect to the server.'
+ c.action do |global, options, args|
+ run_shell_command(global, options, args)
+ end
+ end
+
+ private
+
+ def run_shell_command(global, options, args)
+ require 'leap_cli/ssh'
+ cmd = args[0]
+ filter = args[1..-1]
+ cmd = global[:force] ? cmd : LeapCli::SSH::Options.sanitize_command(cmd)
+ nodes = manager.filter!(filter)
+ if nodes.size == 1 || options[:stream]
+ stream_command(nodes, cmd, options)
+ else
+ capture_command(nodes, cmd, options)
+ end
+ end
+
+ def capture_command(nodes, cmd, options)
+ SSH.remote_command(nodes, options) do |ssh, host|
+ output = ssh.capture(cmd, :log_output => false)
+ if output
+ logger = LeapCli.new_logger
+ logger.log(:ran, "`" + cmd + "`", host: host.hostname, color: :green) do
+ logger.log(output, wrap: true)
+ end
+ end
+ end
+ end
+
+ def stream_command(nodes, cmd, options)
+ SSH.remote_command(nodes, options) do |ssh, host|
+ ssh.stream(cmd, :log_cmd => true, :log_finish => true, :fail_msg => 'oops')
+ end
+ end
+
+end; end
+
+
diff --git a/lib/leap_cli/commands/ssh.rb b/lib/leap_cli/commands/ssh.rb
index 3887618e..03192071 100644
--- a/lib/leap_cli/commands/ssh.rb
+++ b/lib/leap_cli/commands/ssh.rb
@@ -69,20 +69,6 @@ module LeapCli; module Commands
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?"
@@ -193,7 +179,8 @@ module LeapCli; module Commands
"-o 'UserKnownHostsFile=/dev/null'"
]
if node.vagrant?
- options << "-i #{vagrant_ssh_key_file}" # use the universal vagrant insecure key
+ # use the universal vagrant insecure key:
+ options << "-i #{LeapCli::Util::Vagrant.vagrant_ssh_key_file}"
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)
diff --git a/lib/leap_cli/commands/test.rb b/lib/leap_cli/commands/test.rb
index 73207b31..70eb00fd 100644
--- a/lib/leap_cli/commands/test.rb
+++ b/lib/leap_cli/commands/test.rb
@@ -7,24 +7,7 @@ module LeapCli; module Commands
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
+ do_test_run(global_options, options, args)
end
end
@@ -40,6 +23,28 @@ module LeapCli; module Commands
private
+ def do_test_run(global_options, options, args)
+ require 'leap_cli/ssh'
+ test_order = File.join(Path.platform, 'tests/order.rb')
+ if File.exist?(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::remote_command(node, options) do |ssh, host|
+ ssh.stream(test_cmd(options), :raise_error => true, :log_wrap => true)
+ end
+ rescue LeapCli::SSH::ExecuteError
+ if options[:continue]
+ exit_status(1)
+ else
+ bail!
+ end
+ end
+ end
+ end
+
def test_cmd(options)
if options[:continue]
"#{Leap::Platform.leap_dir}/bin/run_tests --continue"
diff --git a/lib/leap_cli/commands/user.rb b/lib/leap_cli/commands/user.rb
index b842e854..1ca92719 100644
--- a/lib/leap_cli/commands/user.rb
+++ b/lib/leap_cli/commands/user.rb
@@ -13,67 +13,131 @@
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
+ desc 'Manage trusted sysadmins (DEPRECATED)'
+ long_desc "Use `leap user add` instead"
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
+ do_add_user(global_options, options, args)
+ end
+ end
- ssh_pub_key = nil
- pgp_pub_key = nil
+ desc 'Manage trusted sysadmins'
+ long_desc "Manage the trusted sysadmins that are configured in the 'users' directory."
+ command :user do |user|
+
+ user.desc 'Adds a new trusted sysadmin'
+ user.arg_name 'USERNAME'
+ user.command :add 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|
+ do_add_user(global_options, options, args)
+ end
+ end
- if options['ssh-pub-key']
- ssh_pub_key = read_file!(options['ssh-pub-key'])
+ user.desc 'Removes a trusted sysadmin'
+ user.arg_name 'USERNAME'
+ user.command :rm do |c|
+ c.action do |global_options,options,args|
+ do_rm_user(global_options, options, args)
end
- if options['pgp-pub-key']
- pgp_pub_key = read_file!(options['pgp-pub-key'])
+ end
+
+ user.desc 'Lists the configured sysadmins'
+ user.command :ls do |c|
+ c.action do |global_options,options,args|
+ do_list_users(global_options, options, args)
end
+ end
+
+ end
+ private
+
+ def do_add_user(global, options, args)
+ require 'leap_cli/ssh'
+
+ username = args.first
+ if !username.any?
if options[:self]
- ssh_pub_key ||= pick_ssh_key.to_s
- pgp_pub_key ||= pick_pgp_key
+ 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
- assert!(ssh_pub_key, 'Sorry, could not find SSH public key.')
+ ssh_pub_key = nil
+ pgp_pub_key = nil
- 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
+ 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
+
+ def do_rm_user(global, options, args)
+ dir = [:user_dir, args.first]
+ if Util.dir_exists?(dir)
+ Util.remove_file!(dir)
update_authorized_keys
+ else
+ bail! :error, 'There is no directory `%s`' % Path.named_path(dir)
+ end
+ end
+
+ def do_list_users(global, options, args)
+ require 'leap_cli/ssh'
+
+ Dir.glob(path([:user_ssh, '*'])).each do |keyfile|
+ username = File.basename(File.dirname(keyfile))
+ log username, :color => :cyan do
+ log Path.relative_path(keyfile)
+ key = SSH::Key.load(keyfile)
+ log 'SSH MD5 fingerprint: ' + key.fingerprint(:digest => :md5, :type => :ssh, :encoding => :hex)
+ log 'SSH SHA256 fingerprint: ' + key.fingerprint(:digest => :sha256, :type => :ssh, :encoding => :base64)
+ log 'DER MD5 fingerprint: ' + key.fingerprint(:digest => :md5, :type => :der, :encoding => :hex)
+ end
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.
+ # 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)
+ ssh_keys << SSH::Key.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)
+ key = SSH::Key.load(line)
if key
key.comment = 'ssh-agent'
ssh_keys << key unless ssh_keys.include?(key)
diff --git a/lib/leap_cli/commands/util.rb b/lib/leap_cli/commands/util.rb
deleted file mode 100644
index c1da570e..00000000
--- a/lib/leap_cli/commands/util.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-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
index 9fdd48e3..f8a75b61 100644
--- a/lib/leap_cli/commands/vagrant.rb
+++ b/lib/leap_cli/commands/vagrant.rb
@@ -4,7 +4,7 @@ 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'."
+ long_desc "This command provides a convenient 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
@@ -35,7 +35,7 @@ module LeapCli; module Commands
local.desc 'Destroys the virtual machine(s), reclaiming the disk space'
local.arg_name 'FILTER', :optional => true #, :multiple => false
- local.command :destroy do |destroy|
+ local.command [:rm, :destroy] do |destroy|
destroy.action do |global_options,options,args|
if global_options[:yes]
vagrant_command("destroy --force", args)
@@ -47,7 +47,7 @@ module LeapCli; module Commands
local.desc 'Print the status of local virtual machine(s)'
local.arg_name 'FILTER', :optional => true #, :multiple => false
- local.command :status do |status|
+ local.command [:ls, :status] do |status|
status.action do |global_options,options,args|
vagrant_command("status", args)
end
@@ -70,25 +70,6 @@ module LeapCli; module Commands
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={})
diff --git a/lib/leap_cli/commands/vm.rb b/lib/leap_cli/commands/vm.rb
new file mode 100644
index 00000000..790774f1
--- /dev/null
+++ b/lib/leap_cli/commands/vm.rb
@@ -0,0 +1,467 @@
+module LeapCli; module Commands
+
+ desc "Manage remote virtual machines (VMs)."
+ long_desc "This command provides a convenient way to manage virtual machines. " +
+ "FILTER may be a node filter or the ID of a virtual machine."
+
+ command [:vm] do |vm|
+ vm.switch :mock, :desc => "Run as simulation, without actually connecting to a cloud provider. If set, --auth is ignored."
+ vm.switch :wait, :desc => "Wait for servers to start/stop before continuing."
+ vm.flag :auth, :arg_name => 'AUTH',
+ :desc => "Choose which authentication credentials to use from the file cloud.json. "+
+ "If omitted, will default to the node's `vm.auth` property, or the first credentials in cloud.json"
+
+ vm.desc "Allocates a new VM and/or associates it with node NAME."
+ vm.long_desc "If node configuration file does not yet exist, "+
+ "it is created with the optional SEED values. "+
+ "You can run this command when the virtual machine already exists "+
+ "in order to update the node's `vm.id` property."
+ vm.arg_name 'NODE_NAME [SEED]'
+ vm.command :add do |cmd|
+ cmd.action do |global, options, args|
+ do_vm_add(global, options, args)
+ end
+ end
+
+ vm.desc 'Starts one or more VMs'
+ vm.arg_name 'FILTER', :optional => true
+ vm.command :start do |start|
+ start.action do |global, options, args|
+ do_vm_start(global, options, args)
+ end
+ end
+
+ vm.desc 'Shuts down one or more VMs'
+ vm.long_desc 'This keeps the storage allocated. To save resources, run `leap vm rm` instead.'
+ vm.arg_name 'FILTER', :optional => true
+ vm.command :stop do |stop|
+ stop.action do |global, options, args|
+ do_vm_stop(global, options, args)
+ end
+ end
+
+ vm.desc 'Destroys one or more VMs'
+ vm.arg_name 'FILTER', :optional => true
+ vm.command :rm do |rm|
+ rm.action do |global, options, args|
+ do_vm_rm(global, options, args)
+ end
+ end
+
+ vm.desc 'Print the status of all VMs'
+ vm.arg_name 'FILTER', :optional => true
+ vm.command [:status, :ls] do |status|
+ status.action do |global, options, args|
+ do_vm_status(global, options, args)
+ end
+ end
+
+ vm.desc "Binds a running VM instance to a node configuration."
+ vm.long_desc "Afterwards, the VM will be assigned a label matching the node name, "+
+ "and the node config will be updated with the instance ID."
+ vm.arg_name 'NODE_NAME INSTANCE_ID'
+ vm.command 'bind' do |cmd|
+ cmd.action do |global, options, args|
+ do_vm_bind(global, options, args)
+ end
+ end
+
+ vm.desc "Registers a SSH public key for use when creating new VMs."
+ vm.long_desc "Note that only people who are creating new VM instances need to "+
+ "have their key registered."
+ vm.command 'key-register' do |cmd|
+ cmd.action do |global, options, args|
+ do_vm_key_register(global, options, args)
+ end
+ end
+
+ vm.desc "Lists the registered SSH public keys for a particular VM provider."
+ vm.command 'key-list' do |cmd|
+ cmd.action do |global, options, args|
+ do_vm_key_list(global, options, args)
+ end
+ end
+
+ #vm.desc 'Saves the current state of the virtual machine as a new snapshot.'
+ #vm.arg_name 'FILTER', :optional => true
+ #vm.command :save do |save|
+ # save.action do |global, options, args|
+ # do_vm_save(global, options, args)
+ # end
+ #end
+
+ #vm.desc 'Resets virtual machine(s) to the last saved snapshot'
+ #vm.arg_name 'FILTER', :optional => true
+ #vm.command :reset do |reset|
+ # reset.action do |global, options, args|
+ # do_vm_reset(global, options, args)
+ # end
+ #end
+
+ #vm.desc 'Lists the available images.'
+ #vm.command 'image-list' do |cmd|
+ # cmd.action do |global, options, args|
+ # do_vm_image_list(global, options, args)
+ # end
+ #end
+ end
+
+ ##
+ ## SHARED UTILITY METHODS
+ ##
+
+ protected
+
+ #
+ # a callback used if we need to upload a new ssh key
+ #
+ def choose_ssh_key_for_upload(cloud)
+ puts
+ bail! unless agree("The cloud provider `#{cloud.name}` does not have "+
+ "your public key. Do you want to upload one? ")
+ key = pick_ssh_key
+ username = ask("username? ", :default => `whoami`.strip)
+ assert!(username && !username.empty? && username =~ /[0-9a-z_-]+/, "Username must consist of one or more letters or numbers")
+ puts
+ return username, key
+ end
+
+ def bind_server_to_node(vm_id, node, options={})
+ cloud = new_cloud_handle(node, options)
+ server = cloud.compute.servers.get(vm_id)
+ assert! server, "Could not find a VM instance with ID '#{vm_id}'"
+ cloud.bind_server_to_node(server)
+ end
+
+ ##
+ ## COMMANDS
+ ##
+
+ protected
+
+ #
+ # entirely removes the vm, not just stopping it.
+ #
+ # This might be additionally called by the 'leap node rm' command.
+ #
+ def do_vm_rm(global, options, args)
+ servers_from_args(global, options, args) do |cloud, server|
+ cloud.unbind_server_from_node(server) if cloud.node
+ destroy_server(server, options[:wait])
+ end
+ end
+
+ private
+
+ def do_vm_status(global, options, args)
+ cloud = new_cloud_handle(nil, options)
+ servers = cloud.compute.servers
+
+ #
+ # PRETTY TABLE
+ #
+ t = LeapCli::Util::ConsoleTable.new
+ t.table do
+ t.row(color: :cyan) do
+ t.column "ID"
+ t.column "NODE"
+ t.column "STATE"
+ t.column "FLAVOR"
+ t.column "IP"
+ t.column "ZONE"
+ end
+ servers.each do |server|
+ t.row do
+ t.column server.id
+ t.column server.tags["node_name"]
+ t.column server.state, :color => state_color(server.state)
+ t.column server.flavor_id
+ t.column server.public_ip_address
+ t.column server.availability_zone
+ end
+ end
+ end
+ puts
+ t.draw_table
+
+ #
+ # SANITY CHECKS
+ #
+ servers.each do |server|
+ name = server.tags["node_name"]
+ if name
+ node = manager.nodes[name]
+ if node.nil?
+ log :warning, 'A virtual machine has the name `%s`, but there is no corresponding node definition in `%s`.' % [
+ name, relative_path(path([:node_config, name]))]
+ next
+ end
+ if node['vm'].nil?
+ log :warning, 'Node `%s` is not configured as a virtual machine' % name do
+ log 'You should fix this with `leap vm bind %s %s`' % [name, server.id]
+ end
+ next
+ end
+ if node['vm.id'] != server.id
+ message = 'Node `%s` is configured with virtual machine id `%s`' % [name, node['vm.id']]
+ log :warning, message do
+ log 'But the virtual machine with that name really has id `%s`' % server.id
+ log 'You should fix this with `leap vm bind %s %s`' % [name, server.id]
+ end
+ end
+ if server.state == 'running'
+ if node.ip_address != server.public_ip_address
+ message = 'The configuration file for node `%s` has IP address `%s`' % [name, node.ip_address]
+ log(:warning, message) do
+ log 'But the virtual machine actually has IP address `%s`' % server.public_ip_address
+ log 'You should fix this with `leap vm add %s`' % name
+ end
+ end
+ end
+ end
+ end
+ manager.filter(['vm']).each_node do |node|
+ if node['vm.id'].nil?
+ log :warning, 'The node `%s` is missing a server id' % node.name
+ next
+ end
+ if !servers.detect {|s| s.id == node.vm.id }
+ message = "The configuration file for node `%s` has virtual machine id of `%s`" % [node.name, node.vm.id]
+ log :warning, message do
+ log "But that does not match any actual virtual machines!"
+ end
+ end
+ if !servers.detect {|s| s.tags["node_name"] == node.name }
+ log :warning, "The node `%s` has no virtual machines with a matching name." % node.name do
+ server = servers.detect {|s| s.id == node.vm.id }
+ if server
+ log 'Run `leap bind %s %s` to fix this' % [node.name, server.id]
+ end
+ end
+ end
+ end
+ end
+
+ def do_vm_add(global, options, args)
+ name = args.first
+ if manager.nodes[name].nil?
+ do_node_add(global, {:ip_address => '0.0.0.0'}.merge(options), args)
+ end
+ node = manager.nodes[name]
+ cloud = new_cloud_handle(node, options)
+ server = cloud.fetch_or_create_server(:choose_ssh_key => method(:choose_ssh_key_for_upload))
+
+ if server
+ cloud.bind_server_to_node(server)
+ ssh_host_key = cloud.wait_for_ssh_host_key(server)
+ if ssh_host_key.nil?
+ log :warning, "We could not get a SSH host key." do
+ log "Try running `leap vm add #{node.name}` again later."
+ end
+ else
+ log :saving, "SSH host key for #{node.name}"
+ write_file! [:node_ssh_pub_key, node.name], ssh_host_key.to_s
+ end
+ log "done", :color => :green, :style => :bold
+ end
+ end
+
+ def do_vm_start(global, options, args)
+ servers_from_args(global, options, args) do |cloud, server|
+ start_server(server, options[:wait])
+ end
+ end
+
+ def do_vm_stop(global, options, args)
+ servers_from_args(global, options, args) do |cloud, server|
+ stop_server(server, options[:wait])
+ end
+ end
+
+ def do_vm_key_register(global, options, args)
+ cloud = new_cloud_handle(nil, options)
+ cloud.find_or_create_key_pair(method(:choose_ssh_key_for_upload))
+ end
+
+ def do_vm_key_list(global, options, args)
+ require 'leap_cli/ssh'
+ cloud = new_cloud_handle(nil, options)
+ cloud.compute.key_pairs.each do |key_pair|
+ log key_pair.name, :color => :cyan do
+ log "AWS fingerprint: " + key_pair.fingerprint
+ key_pair, local_key = cloud.match_ssh_key(:key_pair => key_pair)
+ if local_key
+ log "matches local key: " + local_key.filename
+ log 'SSH MD5 fingerprint: ' + local_key.fingerprint(:digest => :md5, :type => :ssh, :encoding => :hex)
+ log 'SSH SHA256 fingerprint: ' + local_key.fingerprint(:digest => :sha256, :type => :ssh, :encoding => :base64)
+ end
+ end
+ end
+ end
+
+ #
+ # update association between node and virtual machine.
+ #
+ # This might additionally be called by the 'leap node mv' command.
+ #
+ def do_vm_bind(global, options, args)
+ node_name = args.first
+ vm_id = args.last
+ assert! node_name, "NODE_NAME is missing"
+ assert! vm_id, "INSTANCE_ID is missing"
+ node = manager.nodes[node_name]
+ assert! node, "No node with name '#{node_name}'"
+ bind_server_to_node(vm_id, node, options)
+ end
+
+ #def do_vm_image_list(global, options, args)
+ # compute = fog_setup(nil, options)
+ # p compute.images.all
+ #end
+
+ ##
+ ## PRIVATE UTILITY METHODS
+ ##
+
+ def stop_server(server, wait=false)
+ if server.state == 'stopped'
+ log :skipping, "virtual machine `#{server.id}` (already stopped)."
+ elsif ['shutting-down', 'terminated'].include?(server.state)
+ log :skipping, "virtual machine `#{server.id}` (being destroyed)."
+ else
+ log :stopping, "virtual machine `#{server.id}` (#{server.flavor_id}, #{server.availability_zone})"
+ server.stop
+ if wait
+ log 'please wait...', :indent => 1
+ server.wait_for { state == 'stopped' }
+ log 'done', :color => :green, :indent => 1
+ end
+ end
+ end
+
+ def start_server(server, wait=false)
+ if server.state == 'running'
+ log :skipping, "virtual machine `#{server.id}` (already running)."
+ elsif ['shutting-down', 'terminated'].include?(server.state)
+ log :skipping, "virtual machine `#{server.id}` (being destroyed)."
+ else
+ log :starting, "virtual machine `#{server.id}` (#{server.flavor_id}, #{server.availability_zone})"
+ server.start
+ if wait
+ log 'please wait...', :indent => 1
+ server.wait_for { ready? }
+ log 'done', :color => :green, :indent => 1
+ end
+ end
+ end
+
+ def destroy_server(server, wait=false)
+ if ['shutting-down', 'terminated'].include?(server.state)
+ log :skipping, "virtual machine `#{server.id}` (already being removed)."
+ else
+ log :terminated, "virtual machine `#{server.id}` (#{server.flavor_id}, #{server.availability_zone})"
+ server.destroy
+ if wait
+ log 'please wait...', :indent => 1
+ server.wait_for { state == 'terminated' }
+ log 'done', :color => :green, :indent => 1
+ end
+ end
+ end
+
+ #
+ # for each server it finds, yields cloud, server
+ #
+ def servers_from_args(global, options, args)
+ nodes = filter_vm_nodes(args)
+ if nodes.any?
+ nodes.each_node do |node|
+ cloud = new_cloud_handle(node, options)
+ server = cloud.fetch_server_for_node(true)
+ yield cloud, server
+ end
+ else
+ instance_id = args.first
+ cloud = new_cloud_handle(nil, options)
+ server = cloud.compute.servers.get(instance_id)
+ if server.nil?
+ bail! :error, "There is no virtual machine with ID `#{instance_id}`."
+ end
+ yield cloud, server
+ end
+ end
+
+ #
+ # returns either:
+ #
+ # * the set of nodes specified by the filter, for this environment
+ # even if the result includes nodes that are not previously tagged with 'vm'
+ #
+ # * the list of all vm nodes for this environment, if filter is empty
+ #
+ def filter_vm_nodes(filter)
+ if filter.nil? || filter.empty?
+ return manager.filter(['vm'], :warning => false)
+ elsif filter.is_a? Array
+ return manager.filter(filter, :warning => false)
+ else
+ raise ArgumentError, 'could not understand filter'
+ end
+ end
+
+ def new_cloud_handle(node, options)
+ require 'leap_cli/cloud'
+
+ config = manager.env.cloud
+ name = nil
+ if options[:mock]
+ Fog.mock!
+ name = 'mock_aws'
+ config['mock_aws'] = {
+ "api" => "aws",
+ "vendor" => "aws",
+ "auth" => {
+ "aws_access_key_id" => "dummy",
+ "aws_secret_access_key" => "dummy",
+ "region" => "us-west-2"
+ },
+ "instance_options" => {
+ "image" => "dummy"
+ }
+ }
+ elsif options[:auth]
+ name = options[:auth]
+ assert! config[name], "The value for --auth does not correspond to any value in cloud.json."
+ elsif node && node['vm.auth']
+ name = node.vm.auth
+ assert! config[name], "The node '#{node.name}' has a value for property 'vm.auth' that does not correspond to any value in cloud.json."
+ elsif config.keys.length == 1
+ name = config.keys.first
+ log :using, "cloud vendor credentials `#{name}`."
+ else
+ bail! "You must specify --mock, --auth, or a node filter."
+ end
+
+ entry = config[name] # entry in cloud.json
+ assert! entry, "cloud.json: could not find cloud resource `#{name}`."
+ assert! entry['vendor'], "cloud.json: property `vendor` is missing from `#{name}` entry."
+ assert! entry['api'], "cloud.json: property `api` is missing from `#{name}` entry. It must be one of #{config.possible_apis.join(', ')}."
+ assert! entry['auth'], "cloud.json: property `auth` is missing from `#{name}` entry."
+ assert! entry['auth']['region'], "cloud.json: property `auth.region` is missing from `#{name}` entry."
+ assert! entry['api'] == 'aws', "cloud.json: currently, only 'aws' is supported for `api`."
+ assert! entry['vendor'] == 'aws', "cloud.json: currently, only 'aws' is supported for `vendor`."
+
+ return LeapCli::Cloud.new(name, entry, node)
+ end
+
+ def state_color(state)
+ case state
+ when 'running'; :green
+ when 'terminated'; :red
+ when 'stopped'; :magenta
+ when 'shutting-down'; :yellow
+ else; :white
+ end
+ end
+
+end; end
diff --git a/lib/leap_cli/config/cloud.rb b/lib/leap_cli/config/cloud.rb
new file mode 100644
index 00000000..e3e5c1f1
--- /dev/null
+++ b/lib/leap_cli/config/cloud.rb
@@ -0,0 +1,64 @@
+# encoding: utf-8
+#
+# A class for the cloud.json file
+#
+# Example format:
+#
+# {
+# "my_aws": {
+# "api": "aws",
+# "vendor": "aws",
+# "auth": {
+# "region": "us-west-2",
+# "aws_access_key_id": "xxxxxxxxxxxxxxx",
+# "aws_secret_access_key": "xxxxxxxxxxxxxxxxxxxxxxxxxx"
+# },
+# "default_image": "ami-98e114f8",
+# "default_options": {
+# "InstanceType": "t2.nano"
+# }
+# }
+# }
+#
+
+module LeapCli; module Config
+
+ # http://fog.io/about/supported_services.html
+ VM_APIS = {
+ 'aws' => 'fog-aws',
+ 'google' => 'fog-google',
+ 'libvirt' => 'fog-libvirt',
+ 'openstack' => 'fog-openstack',
+ 'rackspace' => 'fog-rackspace'
+ }
+
+ class Cloud < Hash
+ def initialize(env=nil)
+ end
+
+ #
+ # returns hash, each key is the name of an API that is
+ # needed and the value is the name of the gem.
+ #
+ # only provider APIs that are required because they are present
+ # in cloud.json are included.
+ #
+ def required_gems
+ required = {}
+ self.each do |name, conf|
+ api = conf["api"]
+ required_gems[api] = VM_APIS[api]
+ end
+ return required
+ end
+
+ #
+ # returns an array of all possible providers
+ #
+ def possible_apis
+ VM_APIS.keys
+ end
+
+ end
+
+end; end
diff --git a/lib/leap_cli/config/environment.rb b/lib/leap_cli/config/environment.rb
new file mode 100644
index 00000000..ce570839
--- /dev/null
+++ b/lib/leap_cli/config/environment.rb
@@ -0,0 +1,200 @@
+#
+# All configurations files can be isolated into separate environments.
+#
+# Each config json in each environment inherits from the default environment,
+# which in term inherits from the "_base_" environment:
+#
+# _base_ -- base provider in leap_platform
+# '- default -- environment in provider dir when no env is set
+# '- production -- example environment
+#
+
+module LeapCli; module Config
+
+ class Environment
+ # the String name of the environment
+ attr_accessor :name
+
+ # the shared Manager object
+ attr_accessor :manager
+
+ # hashes of {name => Config::Object}
+ attr_accessor :services, :tags, :partials
+
+ # a Config::Provider
+ attr_accessor :provider
+
+ # a Config::Object
+ attr_accessor :common
+
+ # shared, non-inheritable
+ def nodes; @@nodes; end
+ def secrets; @@secrets; end
+ def cloud; @@cloud; end
+
+ def initialize(manager, name, search_dir, parent, options={})
+ @@nodes ||= nil
+ @@secrets ||= nil
+ @@cloud ||= nil
+
+ @manager = manager
+ @name = name
+
+ load_provider_files(search_dir, options)
+
+ if parent
+ @services.inherit_from! parent.services, self
+ @tags.inherit_from! parent.tags , self
+ @partials.inherit_from! parent.partials, self
+ @common.inherit_from! parent.common
+ @provider.inherit_from! parent.provider
+ end
+
+ if @provider
+ @provider.set_env(name)
+ @provider.validate!
+ end
+ end
+
+ def load_provider_files(search_dir, options)
+ #
+ # load empty environment if search_dir doesn't exist
+ #
+ if search_dir.nil? || !Dir.exist?(search_dir)
+ @services = Config::ObjectList.new
+ @tags = Config::ObjectList.new
+ @partials = Config::ObjectList.new
+ @provider = Config::Provider.new
+ @common = Config::Object.new
+ @cloud = Config::Cloud.new
+ return
+ end
+
+ #
+ # inheritable
+ #
+ if options[:scope]
+ scope = options[:scope]
+ @services = load_all_json(Path.named_path([:service_env_config, '*', scope], search_dir), Config::Tag, options)
+ @tags = load_all_json(Path.named_path([:tag_env_config, '*', scope], search_dir), Config::Tag, options)
+ @partials = load_all_json(Path.named_path([:service_env_config, '_*', scope], search_dir), Config::Tag, options)
+ @provider = load_json( Path.named_path([:provider_env_config, scope], search_dir), Config::Provider, options)
+ @common = load_json( Path.named_path([:common_env_config, scope], search_dir), Config::Object, options)
+ else
+ @services = load_all_json(Path.named_path([:service_config, '*'], search_dir), Config::Tag, options)
+ @tags = load_all_json(Path.named_path([:tag_config, '*'], search_dir), Config::Tag, options)
+ @partials = load_all_json(Path.named_path([:service_config, '_*'], search_dir), Config::Tag, options)
+ @provider = load_json( Path.named_path(:provider_config, search_dir), Config::Provider, options)
+ @common = load_json( Path.named_path(:common_config, search_dir), Config::Object, options)
+ end
+
+ # remove 'name' from partials, since partials get merged with nodes
+ @partials.values.each {|partial| partial.delete('name'); }
+
+ #
+ # shared
+ #
+ # shared configs are also non-inheritable
+ # load the first ones we find, and only those.
+ #
+ if @@nodes.nil? || @@nodes.empty?
+ @@nodes = load_all_json(Path.named_path([:node_config, '*'], search_dir), Config::Node, options)
+ end
+ if @@secrets.nil? || @@secrets.empty?
+ @@secrets = load_json(Path.named_path(:secrets_config, search_dir), Config::Secrets, options)
+ end
+ if @@cloud.nil? || @@cloud.empty?
+ @@cloud = load_json(Path.named_path(:cloud_config, search_dir), Config::Cloud)
+ end
+ end
+
+ #
+ # Loads a json template file as a Hash (used only when creating a new node .json
+ # file for the first time).
+ #
+ def template(template)
+ path = Path.named_path([:template_config, template], Path.provider_base)
+ if File.exist?(path)
+ return load_json(path, Config::Object)
+ else
+ return nil
+ end
+ end
+
+ #
+ # Alters the node's json config file. Unfortunately, doing this will
+ # strip out all the comments.
+ #
+ def update_node_json(node, new_values)
+ node_json_path = Path.named_path([:node_config, node.name])
+ old_data = load_json(node_json_path, Config::Node)
+ new_data = old_data.merge(new_values)
+ new_contents = JSON.sorted_generate(new_data) + "\n"
+ Util::write_file! node_json_path, new_contents
+ end
+
+ private
+
+ def load_all_json(pattern, object_class, options={})
+ results = Config::ObjectList.new
+ Dir.glob(pattern).each do |filename|
+ next if options[:no_dots] && File.basename(filename) !~ /^[^\.]*\.json$/
+ obj = load_json(filename, object_class)
+ if obj
+ name = File.basename(filename).force_encoding('utf-8').sub(/^([^\.]+).*\.json$/,'\1')
+ obj['name'] ||= name
+ if options[:env]
+ obj.environment = options[:env]
+ end
+ results[name] = obj
+ end
+ end
+ results
+ end
+
+ def load_json(filename, object_class, options={})
+ if !File.exist?(filename)
+ return object_class.new(self)
+ end
+
+ Util::log :loading, filename, 3
+
+ #
+ # Read a JSON file, strip out comments.
+ #
+ # UTF8 is the default encoding for JSON, but others are allowed:
+ # https://www.ietf.org/rfc/rfc4627.txt
+ #
+ buffer = StringIO.new
+ File.open(filename, "rb", :encoding => 'UTF-8') do |f|
+ while (line = f.gets)
+ next if line =~ /^\s*\/\//
+ buffer << line
+ end
+ end
+
+ #
+ # force UTF-8
+ #
+ if $ruby_version >= [1,9]
+ string = buffer.string.force_encoding('utf-8')
+ else
+ string = Iconv.conv("UTF-8//IGNORE", "UTF-8", buffer.string)
+ end
+
+ # parse json
+ begin
+ hash = JSON.parse(string, :object_class => Hash, :array_class => Array) || {}
+ rescue SyntaxError, JSON::ParserError => exc
+ Util::log 0, :error, 'in file "%s":' % filename
+ Util::log 0, exc.to_s, :indent => 1
+ return nil
+ end
+ object = object_class.new(self)
+ object.deep_merge!(hash)
+ return object
+ end
+
+ end # end Environment
+
+end; end \ No newline at end of file
diff --git a/lib/leap_cli/config/filter.rb b/lib/leap_cli/config/filter.rb
new file mode 100644
index 00000000..07424894
--- /dev/null
+++ b/lib/leap_cli/config/filter.rb
@@ -0,0 +1,181 @@
+#
+# Many leap_cli commands accept a list of filters to select a subset of nodes for the command to
+# be applied to. This class is a helper for manager to run these filters.
+#
+# Classes other than Manager should not use this class.
+#
+# Filter rules:
+#
+# * A filter consists of a list of tokens
+# * A token may be a service name, tag name, environment name, or node name.
+# * Each token may be optionally prefixed with a plus sign.
+# * Multiple tokens with a plus are treated as an OR condition,
+# but treated as an AND condition with the plus sign.
+#
+# For example
+#
+# * openvpn +development => all nodes with service 'openvpn' AND environment 'development'
+# * openvpn seattle => all nodes with service 'openvpn' OR tag 'seattle'.
+#
+# There can only be one environment specified. Typically, there are also tags
+# for each environment name. These name are treated as environments, not tags.
+#
+module LeapCli
+ module Config
+ class Filter
+
+ #
+ # filter -- array of strings, each one a filter
+ # options -- hash, possible keys include
+ # :nopin -- disregard environment pinning
+ # :local -- if false, disallow local nodes
+ # :warning -- if false, don't print a warning when no nodes are found.
+ #
+ # A nil value in the filters array indicates
+ # the default environment. This is in order to support
+ # calls like `manager.filter(environments)`
+ #
+ def initialize(filters, options, manager)
+ @filters = filters.nil? ? [] : filters.dup
+ @environments = []
+ @options = options
+ @manager = manager
+
+ # split filters by pulling out items that happen
+ # to be environment names.
+ if LeapCli.leapfile.environment.nil? || @options[:nopin]
+ @environments = []
+ else
+ @environments = [LeapCli.leapfile.environment]
+ end
+ @filters.select! do |filter|
+ if filter.nil?
+ @environments << nil unless @environments.include?(nil)
+ false
+ else
+ filter_text = filter.sub(/^\+/,'')
+ if is_environment?(filter_text)
+ if filter_text == LeapCli.leapfile.environment
+ # silently ignore already pinned environments
+ elsif (filter =~ /^\+/ || @filters.first == filter) && !@environments.empty?
+ LeapCli::Util.bail! do
+ LeapCli.log "Environments are exclusive: no node is in two environments." do
+ LeapCli.log "Tried to filter on '#{@environments.join('\' AND \'')}' AND '#{filter_text}'"
+ end
+ end
+ else
+ @environments << filter_text
+ end
+ false
+ else
+ true
+ end
+ end
+ end
+
+ # don't let the first filter have a + prefix
+ if @filters[0] =~ /^\+/
+ @filters[0] = @filters[0][1..-1]
+ end
+ end
+
+ # actually run the filter, returns a filtered list of nodes
+ def nodes()
+ if @filters.empty?
+ return nodes_for_empty_filter
+ else
+ return nodes_for_filter
+ end
+ end
+
+ private
+
+ def nodes_for_empty_filter
+ node_list = @manager.nodes
+ if @environments.any?
+ node_list = node_list[ @environments.collect{|e|[:environment, env_to_filter(e)]} ]
+ end
+ if @options[:local] === false
+ node_list = node_list[:environment => '!local']
+ end
+ if @options[:disabled] === false
+ node_list = node_list[:environment => '!disabled']
+ end
+ node_list
+ end
+
+ def nodes_for_filter
+ node_list = Config::ObjectList.new
+ @filters.each do |filter|
+ if filter =~ /^\+/
+ keep_list = nodes_for_name(filter[1..-1])
+ node_list.delete_if do |name, node|
+ if keep_list[name]
+ false
+ else
+ true
+ end
+ end
+ else
+ node_list.merge!(nodes_for_name(filter))
+ end
+ end
+ node_list
+ end
+
+ private
+
+ #
+ # returns a set of nodes corresponding to a single name,
+ # where name could be a node name, service name, or tag name.
+ #
+ # For services and tags, we only include nodes for the
+ # environments that are active
+ #
+ def nodes_for_name(name)
+ if node = @manager.nodes[name]
+ return Config::ObjectList.new(node)
+ elsif @environments.empty?
+ if @manager.services[name]
+ return @manager.env('_all_').services[name].node_list
+ elsif @manager.tags[name]
+ return @manager.env('_all_').tags[name].node_list
+ elsif @options[:warning] != false
+ LeapCli.log :warning, "filter '#{name}' does not match any node names, tags, services, or environments."
+ return Config::ObjectList.new
+ else
+ return Config::ObjectList.new
+ end
+ else
+ node_list = Config::ObjectList.new
+ if @manager.services[name]
+ @environments.each do |env|
+ node_list.merge!(@manager.env(env).services[name].node_list)
+ end
+ elsif @manager.tags[name]
+ @environments.each do |env|
+ node_list.merge!(@manager.env(env).tags[name].node_list)
+ end
+ elsif @options[:warning] != false
+ LeapCli.log :warning, "filter '#{name}' does not match any node names, tags, services, or environments."
+ end
+ return node_list
+ end
+ end
+
+ #
+ # when pinning, we use the name 'default' to specify nodes
+ # without an environment set, but when filtering, we need to filter
+ # on :environment => nil.
+ #
+ def env_to_filter(environment)
+ environment == 'default' ? nil : environment
+ end
+
+ def is_environment?(text)
+ text == 'default' || @manager.environment_names.include?(text)
+ end
+
+ end
+ end
+end
diff --git a/lib/leap_cli/config/manager.rb b/lib/leap_cli/config/manager.rb
new file mode 100644
index 00000000..d69a5808
--- /dev/null
+++ b/lib/leap_cli/config/manager.rb
@@ -0,0 +1,475 @@
+# encoding: utf-8
+
+require 'json/pure'
+
+if $ruby_version < [1,9]
+ require 'iconv'
+end
+
+module LeapCli
+ module Config
+
+ #
+ # A class to manage all the objects in all the configuration files.
+ #
+ class Manager
+
+ def initialize
+ @environments = {} # hash of `Environment` objects, keyed by name.
+ Config::Object.send(:include, LeapCli::Macro)
+ end
+
+ ##
+ ## ATTRIBUTES
+ ##
+
+ #
+ # returns the Hash of the contents of facts.json
+ #
+ def facts
+ @facts ||= begin
+ content = Util.read_file(:facts)
+ if !content || content.empty?
+ content = "{}"
+ end
+ JSON.parse(content)
+ rescue SyntaxError, JSON::ParserError => exc
+ Util::bail! "Could not parse facts.json -- #{exc}"
+ end
+ end
+
+ #
+ # returns an Array of all the environments defined for this provider.
+ # the returned array includes nil (for the default environment)
+ #
+ def environment_names
+ @environment_names ||= begin
+ [nil] + (env.tags.field('environment') + env.nodes.field('environment')).compact.uniq
+ end
+ end
+
+ #
+ # Returns the appropriate environment variable
+ #
+ def env(env=nil)
+ @environments[env || 'default']
+ end
+
+ #
+ # The default accessors
+ #
+ # For these defaults, use 'default' environment, or whatever
+ # environment is pinned.
+ #
+ # I think it might be an error that these are ever used
+ # and I would like to get rid of them.
+ #
+ def services; env(default_environment).services; end
+ def tags; env(default_environment).tags; end
+ def partials; env(default_environment).partials; end
+ def provider; env(default_environment).provider; end
+ def common; env(default_environment).common; end
+ def secrets; env(default_environment).secrets; end
+ def nodes; env(default_environment).nodes; end
+ def template(*args)
+ self.env.template(*args)
+ end
+
+ def default_environment
+ LeapCli.leapfile.environment
+ end
+
+ ##
+ ## IMPORT EXPORT
+ ##
+
+ def add_environment(args)
+ if args[:inherit]
+ parent = @environments[args.delete(:inherit)]
+ else
+ parent = nil
+ end
+ env = Environment.new(
+ self,
+ args.delete(:name),
+ args.delete(:dir),
+ parent,
+ args
+ )
+ @environments[env.name] = env
+ end
+
+ #
+ # load .json configuration files
+ #
+ def load(options = {})
+ # load base
+ add_environment(name: '_base_', dir: Path.provider_base)
+
+ # load provider
+ Util::assert_files_exist!(Path.named_path(:provider_config, Path.provider))
+ add_environment(name: 'default', dir: Path.provider,
+ inherit: '_base_', no_dots: true)
+
+ # create a special '_all_' environment, used for tracking
+ # the union of all the environments
+ add_environment(name: '_all_', inherit: 'default')
+
+ # load environments
+ environment_names.each do |ename|
+ if ename
+ LeapCli.log 3, :loading, '%s environment...' % ename
+ add_environment(name: ename, dir: Path.provider,
+ inherit: 'default', scope: ename)
+ end
+ end
+
+ # apply inheritance
+ env.nodes.each do |name, node|
+ Util::assert! name =~ /^[0-9a-z-]+$/, "Illegal character(s) used in node name '#{name}'"
+ env.nodes[name] = apply_inheritance(node)
+ end
+
+ # do some node-list post-processing
+ cleanup_node_lists(options)
+
+ # apply service.rb, common.rb, and provider.rb control files
+ apply_control_files
+ end
+
+ #
+ # save compiled hiera .yaml files
+ #
+ # if a node_list is specified, only update those .yaml files.
+ # otherwise, update all files, destroying files that are no longer used.
+ #
+ def export_nodes(node_list=nil)
+ updated_hiera = []
+ updated_files = []
+ existing_hiera = nil
+ existing_files = nil
+
+ unless node_list
+ node_list = env.nodes
+ existing_hiera = Dir.glob(Path.named_path([:hiera, '*'], Path.provider))
+ existing_files = Dir.glob(Path.named_path([:node_files_dir, '*'], Path.provider))
+ end
+
+ node_list.each_node do |node|
+ filepath = Path.named_path([:node_files_dir, node.name], Path.provider)
+ hierapath = Path.named_path([:hiera, node.name], Path.provider)
+ Util::write_file!(hierapath, node.dump_yaml)
+ updated_files << filepath
+ updated_hiera << hierapath
+ end
+
+ if @disabled_nodes
+ # make disabled nodes appear as if they are still active
+ @disabled_nodes.each_node do |node|
+ updated_files << Path.named_path([:node_files_dir, node.name], Path.provider)
+ updated_hiera << Path.named_path([:hiera, node.name], Path.provider)
+ end
+ end
+
+ # remove files that are no longer needed
+ if existing_hiera
+ (existing_hiera - updated_hiera).each do |filepath|
+ Util::remove_file!(filepath)
+ end
+ end
+ if existing_files
+ (existing_files - updated_files).each do |filepath|
+ Util::remove_directory!(filepath)
+ end
+ end
+ end
+
+ def export_secrets(clean_unused_secrets = false)
+ if env.secrets.any?
+ Util.write_file!([:secrets_config, Path.provider], env.secrets.dump_json(clean_unused_secrets) + "\n")
+ end
+ end
+
+ ##
+ ## FILTERING
+ ##
+
+ #
+ # returns a node list consisting only of nodes that satisfy the filter criteria.
+ #
+ # filter: condition [condition] [condition] [+condition]
+ # condition: [node_name | service_name | tag_name | environment_name]
+ #
+ # if conditions is prefixed with +, then it works like an AND. Otherwise, it works like an OR.
+ #
+ # args:
+ # filter -- array of filter terms, one per item
+ #
+ # options:
+ # :local -- if :local is false and the filter is empty, then local nodes are excluded.
+ # :nopin -- if true, ignore environment pinning
+ #
+ def filter(filters=nil, options={})
+ Filter.new(filters, options, self).nodes()
+ end
+
+ #
+ # same as filter(), but exits if there is no matching nodes
+ #
+ def filter!(filters, options={})
+ node_list = filter(filters, options)
+ Util::assert! node_list.any?, "Could not match any nodes from '#{filters.join ' '}'"
+ return node_list
+ end
+
+ #
+ # returns a single Config::Object that corresponds to a Node.
+ #
+ def node(name)
+ if name =~ /\./
+ # probably got a fqdn, since periods are not allowed in node names.
+ # so, take the part before the first period as the node name
+ name = name.split('.').first
+ end
+ env.nodes[name]
+ end
+
+ #
+ # returns a single node that is disabled
+ #
+ def disabled_node(name)
+ @disabled_nodes[name]
+ end
+
+ #
+ # yields each node, in sorted order
+ #
+ def each_node(&block)
+ env.nodes.each_node(&block)
+ end
+
+ def reload_node!(node)
+ env.nodes[node.name] = apply_inheritance!(node)
+ end
+
+ ##
+ ## CONNECTIONS
+ ##
+
+ class ConnectionList < Array
+ def add(data={})
+ self << {
+ "from" => data[:from],
+ "to" => data[:to],
+ "port" => data[:port]
+ }
+ end
+ end
+
+ def connections
+ @connections ||= ConnectionList.new
+ end
+
+ ##
+ ## PRIVATE
+ ##
+
+ private
+
+ #
+ # makes a node inherit options from appropriate the common, service, and tag json files.
+ #
+ def apply_inheritance(node, throw_exceptions=false)
+ new_node = Config::Node.new(nil)
+ node_env = guess_node_env(node)
+ new_node.set_environment(node_env, new_node)
+
+ # inherit from common
+ new_node.deep_merge!(node_env.common)
+
+ # inherit from services
+ if node['services']
+ node['services'].to_a.each do |node_service|
+ service = node_env.services[node_service]
+ if service.nil?
+ msg = 'in node "%s": the service "%s" does not exist.' % [node['name'], node_service]
+ LeapCli.log 0, :error, msg
+ raise LeapCli::ConfigError.new(node, "error " + msg) if throw_exceptions
+ else
+ new_node.deep_merge!(service)
+ end
+ end
+ end
+
+ # inherit from tags
+ node['tags'] = (node['tags'] || []).to_a
+ if node.vagrant?
+ node['tags'] << 'local'
+ elsif node['vm']
+ node['tags'] << 'vm'
+ end
+ node['tags'].each do |node_tag|
+ tag = node_env.tags[node_tag]
+ if tag.nil?
+ msg = 'in node `%s`: the tag "%s" does not exist!' % [node['name'], node_tag]
+ LeapCli.log 0, :error, msg
+ raise LeapCli::ConfigError.new(node, "error " + msg) if throw_exceptions
+ else
+ new_node.deep_merge!(tag)
+ end
+ end
+
+ # inherit from node
+ new_node.deep_merge!(node)
+ return new_node
+ end
+
+ def apply_inheritance!(node)
+ apply_inheritance(node, true)
+ end
+
+ #
+ # Guess the environment of the node from the tag names.
+ #
+ # Technically, this is wrong: a tag that sets the environment might not be
+ # named the same as the environment. This code assumes that it is.
+ #
+ # Unfortunately, it is a chicken and egg problem. We need to know the nodes
+ # likely environment in order to apply the inheritance that will actually
+ # determine the node's properties.
+ #
+ def guess_node_env(node)
+ if node.vagrant?
+ return self.env("local")
+ else
+ environment = self.env(default_environment)
+ if node['tags']
+ node['tags'].to_a.each do |tag|
+ if self.environment_names.include?(tag)
+ environment = self.env(tag)
+ end
+ end
+ end
+ return environment
+ end
+ end
+
+ #
+ # does some final clean at the end of loading nodes.
+ # this includes removing disabled nodes, and populating
+ # the services[x].node_list and tags[x].node_list
+ #
+ def cleanup_node_lists(options)
+ @disabled_nodes = Config::ObjectList.new
+ env.nodes.each do |name, node|
+ if node.enabled || options[:include_disabled]
+ if node['services']
+ node['services'].to_a.each do |node_service|
+ env(node.environment).services[node_service].node_list.add(node.name, node)
+ env('_all_').services[node_service].node_list.add(node.name, node)
+ end
+ end
+ if node['tags']
+ node['tags'].to_a.each do |node_tag|
+ if env(node.environment).tags[node_tag]
+ # if tag exists
+ env(node.environment).tags[node_tag].node_list.add(node.name, node)
+ env('_all_').tags[node_tag].node_list.add(node.name, node)
+ end
+ end
+ end
+ if node.name == 'default' || environment_names.include?(node.name)
+ LeapCli::Util.bail! do
+ LeapCli.log :error, "The node name '#{node.name}' is invalid, because there is an environment with that same name."
+ end
+ end
+ elsif !options[:include_disabled]
+ LeapCli.log 2, :skipping, "disabled node #{name}."
+ env.nodes.delete(name)
+ @disabled_nodes[name] = node
+ end
+ end
+ end
+
+ #
+ # Applies 'control' files for node .json files and provider.json.
+ #
+ # A control file is like a service or a tag JSON file, but it contains
+ # raw ruby code that gets evaluated in the context of the node.
+ #
+ # Yes, this entirely breaks our functional programming model for JSON
+ # generation.
+ #
+ # Control files are evaluated last, after everything else has run.
+ #
+ def apply_control_files
+ @environments.values.each do |e|
+ provider_control_files(e.name).each do |provider_rb|
+ begin
+ e.provider.eval_file provider_rb
+ rescue ConfigError => exc
+ if options[:continue_on_error]
+ exc.log
+ else
+ raise exc
+ end
+ end
+ end
+ end
+ env.nodes.each do |name, node|
+ node_control_files(node).each do |file|
+ begin
+ node.eval_file file
+ rescue ConfigError => exc
+ if options[:continue_on_error]
+ exc.log
+ else
+ raise exc
+ end
+ end
+ end
+ end
+ end
+
+ def node_control_files(node)
+ files = []
+ [Path.provider_base, Path.provider].each do |provider_dir|
+ # add common.rb
+ common = File.join(provider_dir, 'common.rb')
+ files << common if File.exist?(common)
+
+ # add services/*.rb and tags/*.rb, as appropriate for this node
+ [['services', :service_config], ['tags', :tag_config]].each do |attribute, path_sym|
+ node[attribute].each do |attr_value|
+ path = Path.named_path([path_sym, "#{attr_value}.rb"], provider_dir).sub(/\.json$/,'')
+ if File.exist?(path)
+ files << path
+ end
+ end
+ end
+ end
+ return files
+ end
+
+ def provider_control_files(env)
+ # skip envs that start with underscore
+ if env =~ /^_/
+ return []
+ end
+ files = []
+ environments = [nil]
+ environments << env unless env == 'default'
+ environments.each do |environment|
+ [Path.provider_base, Path.provider].each do |provider_dir|
+ provider_rb = File.join(
+ provider_dir, ['provider', environment, 'rb'].compact.join('.')
+ )
+ files << provider_rb if File.exist?(provider_rb)
+ end
+ end
+ return files
+ end
+
+ end
+ end
+end
diff --git a/lib/leap_cli/config/node.rb b/lib/leap_cli/config/node.rb
new file mode 100644
index 00000000..23abdee3
--- /dev/null
+++ b/lib/leap_cli/config/node.rb
@@ -0,0 +1,245 @@
+#
+# Configuration for a 'node' (a server in the provider's infrastructure)
+#
+
+require 'ipaddr'
+
+module LeapCli; module Config
+
+ class Node < Object
+ attr_accessor :file_paths
+
+ def initialize(environment=nil)
+ super(environment)
+ @node = self
+ @file_paths = []
+ end
+
+ #
+ # returns true if this node has an ip address in the range of the vagrant network
+ #
+ def vagrant?
+ ip = self['ip_address']
+ return false unless ip
+ begin
+ vagrant_range = IPAddr.new LeapCli.leapfile.vagrant_network
+ rescue ArgumentError
+ Util::bail! { Util::log :invalid, "vagrant_network in Leapfile or .leaprc" }
+ end
+
+ begin
+ ip_addr = IPAddr.new(ip)
+ rescue ArgumentError
+ Util::log :warning, "invalid ip address '#{ip}' for node '#{@node.name}'"
+ end
+ return vagrant_range.include?(ip_addr)
+ end
+
+ def vm?
+ self['vm']
+ end
+
+ def vm_id?
+ self['vm.id'] && !self['vm.id'].empty?
+ end
+
+ #
+ # Return a hash table representation of ourselves, with the key equal to the @node.name,
+ # and the value equal to the fields specified in *keys.
+ #
+ # Also, the result is flattened to a single hash, so a key of 'a.b' becomes 'a_b'
+ #
+ # compare to Object#pick(*keys). This method is the sames as Config::ObjectList#pick_fields,
+ # but works on a single node.
+ #
+ # Example:
+ #
+ # node.pick('domain.internal') =>
+ #
+ # {
+ # 'node1': {
+ # 'domain_internal': 'node1.example.i'
+ # }
+ # }
+ #
+ def pick_fields(*keys)
+ {@node.name => self.pick(*keys)}
+ end
+
+ #
+ # can be overridden by the platform.
+ # returns a list of node names that should be tested before this node
+ #
+ def test_dependencies
+ []
+ end
+
+ # returns a string list of supported ssh host key algorithms for this node.
+ # or an empty string if it could not be determined
+ def supported_ssh_host_key_algorithms
+ require 'leap_cli/ssh'
+ @host_key_algo ||= LeapCli::SSH::Key.supported_host_key_algorithms(
+ Util.read_file([:node_ssh_pub_key, @node.name])
+ )
+ end
+
+ #
+ # Takes strings such as "openvpn.gateway_address:1.1.1.1"
+ # and converts this to data stored in this node.
+ #
+ def seed_from_args(args)
+ args.each do |seed|
+ key, value = seed.split(':', 2)
+ value = format_seed_value(value)
+ Util.assert! key =~ /^[0-9a-z\._]+$/, "illegal characters used in property '#{key}'"
+ if key =~ /\./
+ key_parts = key.split('.')
+ final_key = key_parts.pop
+ current_object = self
+ 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
+ self[key] = value
+ end
+ end
+ end
+
+ #
+ # Seeds values for this node from a template, based on the services.
+ # Values in the template will not override existing node values.
+ #
+ def seed_from_template
+ inherit_from!(manager.template('common'))
+ [self['services']].flatten.each do |service|
+ if service
+ template = manager.template(service)
+ if template
+ inherit_from!(template)
+ end
+ end
+ end
+ end
+
+ #
+ # bails if the node is not valid.
+ #
+ def validate!
+ #
+ # validate ip_address
+ #
+ if self['ip_address'] == "REQUIRED"
+ Util.bail! do
+ Util.log :error, "ip_address is not set. " +
+ "Specify with `leap node add NAME ip_address:ADDRESS`."
+ end
+ elsif self['ip_address']
+ begin
+ IPAddr.new(self['ip_address'])
+ rescue ArgumentError
+ Util.bail! do
+ Util.log :invalid, "ip_address #{self['ip_address'].inspect}"
+ end
+ end
+ end
+
+ #
+ # validate name
+ #
+ self.class.validate_name!(self.name, self.vagrant?)
+ end
+
+ #
+ # create or update all the configs needed for this node,
+ # including x.509 certs as needed.
+ #
+ # note: this method will write to disk EVERYTHING
+ # in the node, which is not what you want
+ # if the node has inheritance applied.
+ #
+ def write_configs
+ json = self.dump_json(:exclude => ['name'])
+ Util.write_file!([:node_config, name], json + "\n")
+ rescue LeapCli::ConfigError
+ Config::Node.remove_node_files(self.name)
+ end
+
+ #
+ # modifies the config file nodes/NAME.json for this node.
+ #
+ def update_json(new_values)
+ self.env.update_node_json(node, new_values)
+ end
+
+ #
+ # returns an array of all possible dns names for this node
+ #
+ def all_dns_names
+ 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
+
+ def remove_files
+ self.class.remove_node_files(self.name)
+ end
+
+ ##
+ ## Class Methods
+ ##
+
+ def self.remove_node_files(node_name)
+ (Leap::Platform.node_files + [:node_files_dir]).each do |path|
+ Util.remove_file! [path, node_name]
+ end
+ end
+
+ def self.validate_name!(name, local=false)
+ Util.assert! name, 'Node is missing a name.'
+ if local
+ Util.assert! name =~ /^[0-9a-z]+$/,
+ "illegal characters used in node name '#{name}' " +
+ "(note: Vagrant does not allow hyphens or underscores)"
+ else
+ Util.assert! name =~ /^[0-9a-z-]+$/,
+ "illegal characters used in node name '#{name}' " +
+ "(note: Linux does not allow underscores)"
+ end
+ end
+
+ private
+
+ #
+ # 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
+
+ end
+
+end; end
diff --git a/lib/leap_cli/config/node_cert.rb b/lib/leap_cli/config/node_cert.rb
new file mode 100644
index 00000000..da63d621
--- /dev/null
+++ b/lib/leap_cli/config/node_cert.rb
@@ -0,0 +1,124 @@
+#
+# x509 related methods for Config::Node
+#
+module LeapCli; module Config
+
+ class Node < Object
+
+ #
+ # creates a new server certificate file for this node
+ #
+ def generate_cert
+ require 'leap_cli/x509'
+
+ if self['x509.use'] == false ||
+ !Util.file_exists?(:ca_cert, :ca_key) ||
+ !self.cert_needs_updating?
+ return false
+ end
+
+ cert = CertificateAuthority::Certificate.new
+ provider = env.provider
+
+ # set subject
+ cert.subject.common_name = self.domain.full
+ cert.serial_number.number = X509.cert_serial_number(self.domain.full)
+
+ # set expiration
+ cert.not_before = X509.yesterday
+ cert.not_after = X509.yesterday_advance(provider.ca.server_certificates.life_span)
+
+ # generate key
+ cert.key_material.generate_key(provider.ca.server_certificates.bit_size)
+
+ # sign
+ cert.parent = X509.ca_root
+ cert.sign!(X509.server_signing_profile(self))
+
+ # save
+ Util.write_file!([:node_x509_key, self.name], cert.key_material.private_key.to_pem)
+ Util.write_file!([:node_x509_cert, self.name], cert.to_pem)
+ end
+
+ #
+ # returns true if the certs associated with +node+ need to be regenerated.
+ #
+ def cert_needs_updating?(log_comments=true)
+ require 'leap_cli/x509'
+
+ if log_comments
+ def log(*args, &block)
+ Util.log(*args, &block)
+ end
+ else
+ def log(*args); end
+ end
+
+ node = self
+ if !Util.file_exists?([:node_x509_cert, node.name], [:node_x509_key, node.name])
+ return true
+ else
+ cert = X509.load_certificate_file([:node_x509_cert, node.name])
+ if !X509.created_by_authority?(cert)
+ 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 != node.all_dns_names
+ log :updating, "cert for node '#{node.name}' because domain name aliases have changed" do
+ log "from: #{dns_names.inspect}"
+ log "to: #{node.all_dns_names.inspect})"
+ end
+ return true
+ end
+ end
+ end
+ end
+ return false
+ end
+
+ #
+ # check the expiration of commercial certs, if any.
+ #
+ def warn_if_commercial_cert_will_soon_expire
+ require 'leap_cli/x509'
+
+ self.all_dns_names.each do |domain|
+ if Util.file_exists?([:commercial_cert, domain])
+ cert = X509.load_certificate_file([:commercial_cert, domain])
+ path = Path.relative_path([:commercial_cert, domain])
+ if cert.not_after < Time.now.utc
+ Util.log :error, "the commercial certificate '#{path}' has EXPIRED! " +
+ "You should renew it with `leap cert renew #{domain}`."
+ elsif cert.not_after < Time.now.advance(:months => 2)
+ Util.log :warning, "the commercial certificate '#{path}' will expire soon (#{cert.not_after}). "+
+ "You should renew it with `leap cert renew #{domain}`."
+ end
+ end
+ end
+ end
+
+ end
+
+end; end
+
diff --git a/lib/leap_cli/config/object.rb b/lib/leap_cli/config/object.rb
new file mode 100644
index 00000000..16c41999
--- /dev/null
+++ b/lib/leap_cli/config/object.rb
@@ -0,0 +1,454 @@
+# encoding: utf-8
+
+require 'erb'
+require 'json/pure' # pure ruby implementation is required for our sorted trick to work.
+
+if $ruby_version < [1,9]
+ $KCODE = 'UTF8'
+end
+require 'ya2yaml' # pure ruby yaml
+
+module LeapCli
+ module Config
+
+ #
+ # This class represents the configuration for a single node, service, or tag.
+ # Also, all the nested hashes are also of this type.
+ #
+ # It is called 'object' because it corresponds to an Object in JSON.
+ #
+ class Object < Hash
+
+ attr_reader :env
+ attr_reader :node
+
+ def initialize(environment=nil, node=nil)
+ raise ArgumentError unless environment.nil? || environment.is_a?(Config::Environment)
+ @env = environment
+ # an object that is a node as @node equal to self, otherwise all the
+ # child objects point back to the top level node.
+ @node = node || self
+ end
+
+ def manager
+ @env.manager
+ end
+
+ #
+ # TODO: deprecate node.global()
+ #
+ def global
+ @env
+ end
+
+ def environment=(e)
+ self.store('environment', e)
+ end
+
+ def environment
+ self['environment']
+ end
+
+ def duplicate(env)
+ new_object = self.deep_dup
+ new_object.set_environment(env, new_object)
+ end
+
+ #
+ # export YAML
+ #
+ # We use pure ruby yaml exporter ya2yaml instead of SYCK or PSYCH because it
+ # allows us greater compatibility regardless of installed ruby version and
+ # greater control over how the yaml is exported (sorted keys, in particular).
+ #
+ def dump_yaml
+ evaluate(@node)
+ sorted_ya2yaml(:syck_compatible => true)
+ end
+
+ #
+ # export JSON
+ #
+ def dump_json(options={})
+ evaluate(@node)
+ if options[:format] == :compact
+ return self.to_json
+ else
+ excluded = {}
+ if options[:exclude]
+ options[:exclude].each do |key|
+ excluded[key] = self[key]
+ self.delete(key)
+ end
+ end
+ json_str = JSON.sorted_generate(self)
+ if excluded.any?
+ self.merge!(excluded)
+ end
+ return json_str
+ end
+ end
+
+ def evaluate(context=@node)
+ evaluate_everything(context)
+ late_evaluate_everything(context)
+ end
+
+ ##
+ ## FETCHING VALUES
+ ##
+
+ def [](key)
+ get(key)
+ end
+
+ # Overrride some default methods in Hash that are likely to
+ # be used as attributes.
+ alias_method :hkey, :key
+ def key; get('key'); end
+
+ #
+ # make hash addressable like an object (e.g. obj['name'] available as obj.name)
+ #
+ def method_missing(method, *args, &block)
+ get!(method)
+ end
+
+ def get(key)
+ begin
+ get!(key)
+ rescue NoMethodError
+ nil
+ end
+ end
+
+ # override behavior of #default() from Hash
+ def default
+ get!('default')
+ end
+
+ #
+ # Like a normal Hash#[], except:
+ #
+ # (1) lazily eval dynamic values when we encounter them. (i.e. strings that start with "= ")
+ #
+ # (2) support for nested references in a single string (e.g. ['a.b'] is the same as ['a']['b'])
+ # the dot path is always absolute, starting at the top-most object.
+ #
+ def get!(key)
+ key = key.to_s
+ if self.has_key?(key)
+ fetch_value(key)
+ elsif key =~ /\./
+ # for keys with with '.' in them, we start from the root object (@node).
+ keys = key.split('.')
+ value = self.get!(keys.first)
+ if value.is_a? Config::Object
+ value.get!(keys[1..-1].join('.'))
+ else
+ value
+ end
+ else
+ raise NoMethodError.new(key, "No method '#{key}' for #{self.class}")
+ end
+ end
+
+ #
+ # works like Hash#store(key, value), but supports our nested dot notation,
+ # just like get() does.
+ #
+ def set(key, value)
+ key = key.to_s
+ # for keys with with '.' in them, we pop off the first part
+ # and recursively call ourselves.
+ if key =~ /\./
+ keys = key.split('.')
+ parent_value = self.get!(keys.first)
+ if parent_value.is_a?(Config::Object)
+ parent_value.set(keys[1..-1].join('.'), value)
+ else
+ parent_value.store(keys[1..-1].join('.'), value)
+ end
+ else
+ self.store(key, value)
+ end
+ return nil
+ end
+
+ ##
+ ## COPYING
+ ##
+
+ #
+ # A deep (recursive) merge with another Config::Object.
+ #
+ # If prefer_self is set to true, the value from self will be picked when there is a conflict
+ # that cannot be merged.
+ #
+ # Merging rules:
+ #
+ # - If a value is a hash, we recursively merge it.
+ # - If the value is simple, like a string, the new one overwrites the value.
+ # - If the value is an array:
+ # - If both old and new values are arrays, the new one replaces the old.
+ # - If one of the values is simple but the other is an array, the simple is added to the array.
+ #
+ def deep_merge!(object, prefer_self=false)
+ object.each do |key,new_value|
+ if self.has_key?('+'+key)
+ mode = :add
+ old_value = self.fetch '+'+key, nil
+ self.delete('+'+key)
+ elsif self.has_key?('-'+key)
+ mode = :subtract
+ old_value = self.fetch '-'+key, nil
+ self.delete('-'+key)
+ elsif self.has_key?('!'+key)
+ mode = :replace
+ old_value = self.fetch '!'+key, nil
+ self.delete('!'+key)
+ else
+ mode = :normal
+ old_value = self.fetch key, nil
+ end
+
+ # clean up boolean
+ new_value = true if new_value == "true"
+ new_value = false if new_value == "false"
+ old_value = true if old_value == "true"
+ old_value = false if old_value == "false"
+
+ # force replace?
+ if mode == :replace && prefer_self
+ value = old_value
+
+ # merge hashes
+ elsif old_value.is_a?(Hash) || new_value.is_a?(Hash)
+ value = Config::Object.new(@env, @node)
+ old_value.is_a?(Hash) ? value.deep_merge!(old_value) : (value[key] = old_value if !old_value.nil?)
+ new_value.is_a?(Hash) ? value.deep_merge!(new_value, prefer_self) : (value[key] = new_value if !new_value.nil?)
+
+ # merge nil
+ elsif new_value.nil?
+ value = old_value
+ elsif old_value.nil?
+ value = new_value
+
+ # merge arrays when one value is not an array
+ elsif old_value.is_a?(Array) && !new_value.is_a?(Array)
+ (value = (old_value.dup << new_value).compact.uniq).delete('REQUIRED')
+ elsif new_value.is_a?(Array) && !old_value.is_a?(Array)
+ (value = (new_value.dup << old_value).compact.uniq).delete('REQUIRED')
+
+ # merge two arrays
+ elsif old_value.is_a?(Array) && new_value.is_a?(Array)
+ if mode == :add
+ value = (old_value + new_value).sort.uniq
+ elsif mode == :subtract
+ value = new_value - old_value
+ elsif prefer_self
+ value = old_value
+ else
+ value = new_value
+ end
+
+ # catch errors
+ elsif type_mismatch?(old_value, new_value)
+ raise 'Type mismatch. Cannot merge %s (%s) with %s (%s). Key is "%s", name is "%s".' % [
+ old_value.inspect, old_value.class,
+ new_value.inspect, new_value.class,
+ key, self.class
+ ]
+
+ # merge simple strings & numbers
+ else
+ if prefer_self
+ value = old_value
+ else
+ value = new_value
+ end
+ end
+
+ # save value
+ self[key] = value
+ end
+ self
+ end
+
+ def set_environment(env, node)
+ @env = env
+ @node = node
+ self.each do |key, value|
+ if value.is_a?(Config::Object)
+ value.set_environment(env, node)
+ end
+ end
+ end
+
+ #
+ # like a reverse deep merge
+ # (self takes precedence)
+ #
+ def inherit_from!(object)
+ self.deep_merge!(object, true)
+ end
+
+ #
+ # Make a copy of ourselves, except only including the specified keys.
+ #
+ # Also, the result is flattened to a single hash, so a key of 'a.b' becomes 'a_b'
+ #
+ def pick(*keys)
+ keys.map(&:to_s).inject(self.class.new(@manager)) do |hsh, key|
+ value = self.get(key)
+ if !value.nil?
+ hsh[key.gsub('.','_')] = value
+ end
+ hsh
+ end
+ end
+
+ def eval_file(filename)
+ evaluate_ruby(filename, File.read(filename))
+ end
+
+ protected
+
+ #
+ # walks the object tree, eval'ing all the attributes that are dynamic ruby (e.g. value starts with '= ')
+ #
+ def evaluate_everything(context)
+ keys.each do |key|
+ obj = fetch_value(key, context)
+ if is_required_value_not_set?(obj)
+ Util::log 0, :warning, "required property \"#{key}\" is not set in node \"#{node.name}\"."
+ elsif obj.is_a? Config::Object
+ obj.evaluate_everything(context)
+ end
+ end
+ end
+
+ #
+ # some keys need to be evaluated 'late', after all the other keys have been evaluated.
+ #
+ def late_evaluate_everything(context)
+ if @late_eval_list
+ @late_eval_list.each do |key, value|
+ self[key] = context.evaluate_ruby(key, value)
+ if is_required_value_not_set?(self[key])
+ Util::log 0, :warning, "required property \"#{key}\" is not set in node \"#{node.name}\"."
+ end
+ end
+ end
+ values.each do |obj|
+ if obj.is_a? Config::Object
+ obj.late_evaluate_everything(context)
+ end
+ end
+ end
+
+ #
+ # evaluates the string `value` as ruby in the context of self.
+ # (`key` is just passed for debugging purposes)
+ #
+ def evaluate_ruby(key, value)
+ self.instance_eval(value, key, 1)
+ rescue ConfigError => exc
+ raise exc # pass through
+ rescue SystemStackError => exc
+ Util::log 0, :error, "while evaluating node '#{self.name}'"
+ Util::log 0, "offending key: #{key}", :indent => 1
+ Util::log 0, "offending string: #{value}", :indent => 1
+ Util::log 0, "STACK OVERFLOW, BAILING OUT. There must be an eval loop of death (variables with circular dependencies).", :indent => 1
+ raise SystemExit.new(1)
+ rescue FileMissing => exc
+ Util::bail! do
+ if exc.options[:missing]
+ Util::log :missing, exc.options[:missing].gsub('$node', self.name).gsub('$file', exc.path)
+ else
+ Util::log :error, "while evaluating node '#{self.name}'"
+ Util::log "offending key: #{key}", :indent => 1
+ Util::log "offending string: #{value}", :indent => 1
+ Util::log "error message: no file '#{exc}'", :indent => 1
+ end
+ raise exc if DEBUG
+ end
+ rescue AssertionFailed => exc
+ Util.bail! do
+ Util::log :failed, "assertion while evaluating node '#{self.name}'"
+ Util::log 'assertion: %s' % exc.assertion, :indent => 1
+ Util::log "offending key: #{key}", :indent => 1
+ raise exc if DEBUG
+ end
+ rescue SyntaxError, StandardError => exc
+ Util::bail! do
+ Util::log :error, "while evaluating node '#{self.name}'"
+ Util::log "offending key: #{key}", :indent => 1
+ Util::log "offending string: #{value}", :indent => 1
+ Util::log "error message: #{exc.inspect}", :indent => 1
+ raise exc if DEBUG
+ end
+ end
+
+ private
+
+ #
+ # fetches the value for the key, evaluating the value as ruby if it begins with '='
+ #
+ def fetch_value(key, context=@node)
+ value = fetch(key, nil)
+ if value.is_a?(String) && value =~ /^=/
+ # strings prefix with '=' are evaluated as ruby code.
+ if value =~ /^=> (.*)$/
+ value = evaluate_later(key, $1)
+ elsif value =~ /^= (.*)$/
+ value = context.evaluate_ruby(key, $1)
+ end
+ self[key] = value
+ elsif value.is_a?(Proc)
+ # the value might be a proc, set by a 'control' file
+ self[key] = value.call
+ end
+ return value
+ end
+
+ def evaluate_later(key, value)
+ @late_eval_list ||= []
+ @late_eval_list << [key, value]
+ '<evaluate later>'
+ end
+
+ #
+ # when merging, we raise an error if this method returns true for the two values.
+ #
+ def type_mismatch?(old_value, new_value)
+ if old_value.is_a?(Boolean) && new_value.is_a?(Boolean)
+ # note: FalseClass and TrueClass are different classes
+ # so we can't do old_value.class == new_value.class
+ return false
+ elsif old_value.is_a?(String) && old_value =~ /^=/
+ # pass through macros, since we don't know what the type will eventually be.
+ return false
+ elsif new_value.is_a?(String) && new_value =~ /^=/
+ return false
+ elsif old_value.class == new_value.class
+ return false
+ else
+ return true
+ end
+ end
+
+ #
+ # returns true if the value has not been changed and the default is "REQUIRED"
+ #
+ def is_required_value_not_set?(value)
+ if value.is_a? Array
+ value == ["REQUIRED"]
+ else
+ value == "REQUIRED"
+ end
+ end
+
+ end # class
+ end # module
+end # module \ No newline at end of file
diff --git a/lib/leap_cli/config/object_list.rb b/lib/leap_cli/config/object_list.rb
new file mode 100644
index 00000000..80f89d92
--- /dev/null
+++ b/lib/leap_cli/config/object_list.rb
@@ -0,0 +1,215 @@
+require 'tsort'
+
+module LeapCli
+ module Config
+ #
+ # A list of Config::Object instances (internally stored as a hash)
+ #
+ class ObjectList < Hash
+ include TSort
+
+ def initialize(config=nil)
+ if config
+ self.add(config['name'], config)
+ end
+ end
+
+ #
+ # If the key is a string, the Config::Object it references is returned.
+ #
+ # If the key is a hash, we treat it as a condition and filter all the Config::Objects using the condition.
+ # A new ObjectList is returned.
+ #
+ # Examples:
+ #
+ # nodes['vpn1']
+ # node named 'vpn1'
+ #
+ # nodes[:public_dns => true]
+ # all nodes with public dns
+ #
+ # nodes[:services => 'openvpn', 'location.country_code' => 'US']
+ # all nodes with services containing 'openvpn' OR country code of US
+ #
+ # Sometimes, you want to do an OR condition with multiple conditions
+ # for the same field. Since hash keys must be unique, you can use
+ # an array representation instead:
+ #
+ # nodes[[:services, 'openvpn'], [:services, 'tor']]
+ # nodes with openvpn OR tor service
+ #
+ # nodes[:services => 'openvpn'][:tags => 'production']
+ # nodes with openvpn AND are production
+ #
+ def [](key)
+ if key.is_a?(Hash) || key.is_a?(Array)
+ filter(key)
+ else
+ super key.to_s
+ end
+ end
+
+ def exclude(node)
+ list = self.dup
+ list.delete(node.name)
+ return list
+ end
+
+ def each_node(&block)
+ self.keys.sort.each do |node_name|
+ yield self[node_name]
+ end
+ end
+
+ #
+ # filters this object list, producing a new list.
+ # filter is an array or a hash. see []
+ #
+ def filter(filter)
+ results = Config::ObjectList.new
+ filter.each do |field, match_value|
+ field = field.is_a?(Symbol) ? field.to_s : field
+ match_value = match_value.is_a?(Symbol) ? match_value.to_s : match_value
+ if match_value.is_a?(String) && match_value =~ /^!/
+ operator = :not_equal
+ match_value = match_value.sub(/^!/, '')
+ else
+ operator = :equal
+ end
+ each do |name, config|
+ value = config[field]
+ if value.is_a? Array
+ if operator == :equal && value.include?(match_value)
+ results[name] = config
+ elsif operator == :not_equal && !value.include?(match_value)
+ results[name] = config
+ end
+ elsif match_value.is_a? Array
+ if operator == :equal && match_value.include?(value)
+ results[name] = config
+ elsif operator == :not_equal && !match_value.include?(value)
+ results[name] = config
+ end
+ else
+ if operator == :equal && value == match_value
+ results[name] = config
+ elsif operator == :not_equal && value != match_value
+ results[name] = config
+ end
+ end
+ end
+ end
+ results
+ end
+
+ def add(name, object)
+ self[name] = object
+ end
+
+ #
+ # converts the hash of configs into an array of hashes, with ONLY the specified fields
+ #
+ def fields(*fields)
+ result = []
+ keys.sort.each do |name|
+ result << self[name].pick(*fields)
+ end
+ result
+ end
+
+ #
+ # like fields(), but returns an array of values instead of an array of hashes.
+ #
+ def field(field)
+ field = field.to_s
+ result = []
+ keys.sort.each do |name|
+ result << self[name].get(field)
+ end
+ result
+ end
+
+ #
+ # pick_fields(field1, field2, ...)
+ #
+ # generates a Hash from the object list, but with only the fields that are picked.
+ #
+ # If there are more than one field, then the result is a Hash of Hashes.
+ # If there is just one field, it is a simple map to the value.
+ #
+ # For example:
+ #
+ # "neighbors" = "= nodes_like_me[:services => :couchdb].pick_fields('domain.full', 'ip_address')"
+ #
+ # generates this:
+ #
+ # neighbors:
+ # couch1:
+ # domain_full: couch1.bitmask.net
+ # ip_address: "10.5.5.44"
+ # couch2:
+ # domain_full: couch2.bitmask.net
+ # ip_address: "10.5.5.52"
+ #
+ # But this:
+ #
+ # "neighbors": "= nodes_like_me[:services => :couchdb].pick_fields('domain.full')"
+ #
+ # will generate this:
+ #
+ # neighbors:
+ # couch1: couch1.bitmask.net
+ # couch2: couch2.bitmask.net
+ #
+ def pick_fields(*fields)
+ self.values.inject({}) do |hsh, node|
+ value = self[node.name].pick(*fields)
+ if fields.size == 1
+ value = value.values.first
+ end
+ hsh[node.name] = value
+ hsh
+ end
+ end
+
+ #
+ # Applies inherit_from! to all objects.
+ #
+ # 'env' specifies what environment should be for
+ # each object in the list.
+ #
+ def inherit_from!(object_list, env)
+ object_list.each do |name, object|
+ if self[name]
+ self[name].inherit_from!(object)
+ else
+ self[name] = object.duplicate(env)
+ end
+ end
+ end
+
+ #
+ # topographical sort based on test dependency
+ #
+ def tsort_each_node(&block)
+ self.each_key(&block)
+ end
+
+ def tsort_each_child(node_name, &block)
+ if self[node_name]
+ self[node_name].test_dependencies.each do |test_me_first|
+ if self[test_me_first] # TODO: in the future, allow for ability to optionally pull in all dependencies.
+ # not just the ones that pass the node filter.
+ yield(test_me_first)
+ end
+ end
+ end
+ end
+
+ def names_in_test_dependency_order
+ self.tsort
+ end
+
+ end
+ end
+end
diff --git a/lib/leap_cli/config/provider.rb b/lib/leap_cli/config/provider.rb
new file mode 100644
index 00000000..0d8bc1f3
--- /dev/null
+++ b/lib/leap_cli/config/provider.rb
@@ -0,0 +1,22 @@
+#
+# Configuration class for provider.json
+#
+
+module LeapCli; module Config
+ class Provider < Object
+ attr_reader :environment
+ def set_env(e)
+ if e == 'default'
+ @environment = nil
+ else
+ @environment = e
+ end
+ end
+ def provider
+ self
+ end
+ def validate!
+ # nothing here yet :(
+ end
+ end
+end; end
diff --git a/lib/leap_cli/config/secrets.rb b/lib/leap_cli/config/secrets.rb
new file mode 100644
index 00000000..ca851c74
--- /dev/null
+++ b/lib/leap_cli/config/secrets.rb
@@ -0,0 +1,87 @@
+# encoding: utf-8
+#
+# A class for the secrets.json file
+#
+
+module LeapCli; module Config
+
+ class Secrets < Object
+ attr_reader :node_list
+
+ def initialize(manager=nil)
+ super(manager)
+ @discovered_keys = {}
+ end
+
+ # we can't use fetch() or get(), since those already have special meanings
+ def retrieve(key, environment)
+ environment ||= 'default'
+ self.fetch(environment, {})[key.to_s]
+ end
+
+ def set(*args, &block)
+ if block_given?
+ set_with_block(*args, &block)
+ else
+ set_without_block(*args)
+ end
+ end
+
+ # searches over all keys matching the regexp, checking to see if the value
+ # has been already used by any of them.
+ def taken?(regexp, value, environment)
+ self.keys.grep(regexp).each do |key|
+ return true if self.retrieve(key, environment) == value
+ end
+ return false
+ end
+
+ def set_without_block(key, value, environment)
+ set_with_block(key, environment) {value}
+ end
+
+ def set_with_block(key, environment, &block)
+ environment ||= 'default'
+ key = key.to_s
+ @discovered_keys[environment] ||= {}
+ @discovered_keys[environment][key] = true
+ self[environment] ||= {}
+ self[environment][key] ||= yield
+ end
+
+ #
+ # if clean is true, then only secrets that have been discovered
+ # during this run will be exported.
+ #
+ # if environment is also pinned, then we will clean those secrets
+ # just for that environment.
+ #
+ # the clean argument should only be used when all nodes have
+ # been processed, otherwise secrets that are actually in use will
+ # get mistakenly removed.
+ #
+ def dump_json(clean=false)
+ pinned_env = LeapCli.leapfile.environment
+ if clean
+ self.each_key do |environment|
+ if pinned_env.nil? || pinned_env == environment
+ env = self[environment]
+ if env.nil?
+ raise StandardError.new("secrets.json file seems corrupted. No such environment '#{environment}'")
+ end
+ env.each_key do |key|
+ unless @discovered_keys[environment] && @discovered_keys[environment][key]
+ self[environment].delete(key)
+ end
+ end
+ if self[environment].empty?
+ self.delete(environment)
+ end
+ end
+ end
+ end
+ super()
+ end
+ end
+
+end; end
diff --git a/lib/leap_cli/config/sources.rb b/lib/leap_cli/config/sources.rb
new file mode 100644
index 00000000..aee860de
--- /dev/null
+++ b/lib/leap_cli/config/sources.rb
@@ -0,0 +1,11 @@
+# encoding: utf-8
+#
+# A class for the sources.json file
+#
+
+module LeapCli
+ module Config
+ class Sources < Object
+ end
+ end
+end
diff --git a/lib/leap_cli/config/tag.rb b/lib/leap_cli/config/tag.rb
new file mode 100644
index 00000000..6bd8d1e9
--- /dev/null
+++ b/lib/leap_cli/config/tag.rb
@@ -0,0 +1,25 @@
+#
+#
+# A class for node services or node tags.
+#
+#
+
+module LeapCli; module Config
+
+ class Tag < Object
+ attr_reader :node_list
+
+ def initialize(environment=nil)
+ super(environment)
+ @node_list = Config::ObjectList.new
+ end
+
+ # don't copy the node list pointer when this object is dup'ed.
+ def initialize_copy(orig)
+ super
+ @node_list = Config::ObjectList.new
+ end
+
+ end
+
+end; end
diff --git a/lib/leap_cli/leapfile_extensions.rb b/lib/leap_cli/leapfile_extensions.rb
new file mode 100644
index 00000000..cba321f4
--- /dev/null
+++ b/lib/leap_cli/leapfile_extensions.rb
@@ -0,0 +1,24 @@
+module LeapCli
+ class Leapfile
+ attr_reader :custom_vagrant_vm_line
+ attr_reader :leap_version
+ attr_reader :log
+ attr_reader :vagrant_basebox
+
+ def vagrant_network
+ @vagrant_network ||= '10.5.5.0/24'
+ end
+
+ private
+
+ PRIVATE_IP_RANGES = /(^127\.0\.0\.1)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)/
+
+ def validate
+ Util::assert! vagrant_network =~ PRIVATE_IP_RANGES do
+ Util::log 0, :error, "in #{file}: vagrant_network is not a local private network"
+ end
+ return true
+ end
+
+ end
+end
diff --git a/lib/leap_cli/load_libraries.rb b/lib/leap_cli/load_libraries.rb
new file mode 100644
index 00000000..01384f78
--- /dev/null
+++ b/lib/leap_cli/load_libraries.rb
@@ -0,0 +1,23 @@
+#
+# load the commonly needed leap_cli libraries that live in the platform.
+#
+# loaded by leap_cli's bootstrap.rb
+#
+
+require 'leap_cli/log_filter'
+
+require 'leap_cli/config/object'
+require 'leap_cli/config/node'
+require 'leap_cli/config/node_cert'
+require 'leap_cli/config/tag'
+require 'leap_cli/config/provider'
+require 'leap_cli/config/secrets'
+require 'leap_cli/config/cloud'
+require 'leap_cli/config/object_list'
+require 'leap_cli/config/filter'
+require 'leap_cli/config/environment'
+require 'leap_cli/config/manager'
+
+require 'leap_cli/util/secret'
+require 'leap_cli/util/vagrant'
+require 'leap_cli/util/console_table'
diff --git a/lib/leap_cli/log_filter.rb b/lib/leap_cli/log_filter.rb
new file mode 100644
index 00000000..c73f3a91
--- /dev/null
+++ b/lib/leap_cli/log_filter.rb
@@ -0,0 +1,176 @@
+#
+# A module to hide, modify, and colorize log entries.
+#
+
+module LeapCli
+ module LogFilter
+ #
+ # options for formatters:
+ #
+ # :match => regexp for matching a log line
+ # :color => what color the line should be
+ # :style => what style the line should be
+ # :priority => what order the formatters are applied in. higher numbers first.
+ # :match_level => only apply filter at the specified log level
+ # :level => make this line visible at this log level or higher
+ # :replace => replace the matched text
+ # :prepend => insert text at start of message
+ # :append => append text to end of message
+ # :exit => force the exit code to be this (does not interrupt program, just
+ # ensures a specific exit code when the program eventually exits)
+ #
+ FORMATTERS = [
+ # TRACE
+ { :match => /command finished/, :color => :white, :style => :dim, :match_level => 3, :priority => -10 },
+ { :match => /executing locally/, :color => :yellow, :match_level => 3, :priority => -20 },
+
+ # DEBUG
+ #{ :match => /executing .*/, :color => :green, :match_level => 2, :priority => -10, :timestamp => true },
+ #{ :match => /.*/, :color => :yellow, :match_level => 2, :priority => -30 },
+ { :match => /^transaction:/, :level => 3 },
+
+ # INFO
+ { :match => /.*out\] (fatal:|ERROR:).*/, :color => :red, :match_level => 1, :priority => -10 },
+ { :match => /Permission denied/, :color => :red, :match_level => 1, :priority => -20 },
+ { :match => /sh: .+: command not found/, :color => :magenta, :match_level => 1, :priority => -30 },
+
+ # IMPORTANT
+ { :match => /^(E|e)rr ::/, :color => :red, :match_level => 0, :priority => -10, :exit => 1},
+ { :match => /^ERROR:/, :color => :red, :priority => -10, :exit => 1},
+ #{ :match => /.*/, :color => :blue, :match_level => 0, :priority => -20 },
+
+ # CLEANUP
+ #{ :match => /\s+$/, :replace => '', :priority => 0},
+
+ # DEBIAN PACKAGES
+ { :match => /^(Hit|Ign) /, :color => :green, :priority => -20},
+ { :match => /^Err /, :color => :red, :priority => -20},
+ { :match => /^W(ARNING)?: /, :color => :yellow, :priority => -20},
+ { :match => /^E: /, :color => :red, :priority => -20},
+ { :match => /already the newest version/, :color => :green, :priority => -20},
+ { :match => /WARNING: The following packages cannot be authenticated!/, :color => :red, :level => 0, :priority => -10},
+
+ # PUPPET
+ { :match => /^(W|w)arning: Not collecting exported resources without storeconfigs/, :level => 2, :color => :yellow, :priority => -10},
+ { :match => /^(W|w)arning: Found multiple default providers for vcsrepo:/, :level => 2, :color => :yellow, :priority => -10},
+ { :match => /^(W|w)arning: .*is deprecated.*$/, :level => 2, :color => :yellow, :priority => -10},
+ { :match => /^(W|w)arning: Scope.*$/, :level => 2, :color => :yellow, :priority => -10},
+ #{ :match => /^(N|n)otice:/, :level => 1, :color => :cyan, :priority => -20},
+ #{ :match => /^(N|n)otice:.*executed successfully$/, :level => 2, :color => :cyan, :priority => -15},
+ { :match => /^(W|w)arning:/, :level => 0, :color => :yellow, :priority => -20},
+ { :match => /^Duplicate declaration:/, :level => 0, :color => :red, :priority => -20},
+ #{ :match => /Finished catalog run/, :level => 0, :color => :green, :priority => -10},
+ { :match => /^APPLY COMPLETE \(changes made\)/, :level => 0, :color => :green, :style => :bold, :priority => -10},
+ { :match => /^APPLY COMPLETE \(no changes\)/, :level => 0, :color => :green, :style => :bold, :priority => -10},
+
+ # PUPPET FATAL ERRORS
+ { :match => /^(E|e)rr(or|):/, :level => 0, :color => :red, :priority => -1, :exit => 1},
+ { :match => /^Wrapped exception:/, :level => 0, :color => :red, :priority => -1, :exit => 1},
+ { :match => /^Failed to parse template/, :level => 0, :color => :red, :priority => -1, :exit => 1},
+ { :match => /^Execution of.*returned/, :level => 0, :color => :red, :priority => -1, :exit => 1},
+ { :match => /^Parameter matches failed:/, :level => 0, :color => :red, :priority => -1, :exit => 1},
+ { :match => /^Syntax error/, :level => 0, :color => :red, :priority => -1, :exit => 1},
+ { :match => /^Cannot reassign variable/, :level => 0, :color => :red, :priority => -1, :exit => 1},
+ { :match => /^Could not find template/, :level => 0, :color => :red, :priority => -1, :exit => 1},
+ { :match => /^APPLY COMPLETE.*fail/, :level => 0, :color => :red, :style => :bold, :priority => -1, :exit => 1},
+
+ # TESTS
+ { :match => /^PASS: /, :color => :green, :priority => -20},
+ { :match => /^(FAIL|ERROR): /, :color => :red, :priority => -20},
+ { :match => /^(SKIP|WARN): /, :color => :yellow, :priority => -20},
+ { :match => /\d+ tests: \d+ passes, \d+ skips, 0 warnings, 0 failures, 0 errors/,
+ :color => :green, :style => :bold, :priority => -20 },
+ { :match => /\d+ tests: \d+ passes, \d+ skips, [1-9][0-9]* warnings, 0 failures, 0 errors/,
+ :color => :yellow, :style => :bold, :priority => -20 },
+ { :match => /\d+ tests: \d+ passes, \d+ skips, \d+ warnings, \d+ failures, [1-9][0-9]* errors/,
+ :color => :red, :style => :bold, :priority => -20 },
+ { :match => /\d+ tests: \d+ passes, \d+ skips, \d+ warnings, [1-9][0-9]* failures, \d+ errors/,
+ :color => :red, :style => :bold, :priority => -20 },
+
+ # LOG SUPPRESSION
+ { :match => /^(W|w)arning: You cannot collect without storeconfigs being set/, :level => 2, :priority => 10},
+ { :match => /^(W|w)arning: You cannot collect exported resources without storeconfigs being set/, :level => 2, :priority => 10}
+ ]
+
+ SORTED_FORMATTERS = FORMATTERS.sort_by { |i| -(i[:priority] || i[:prio] || 0) }
+
+ #
+ # same as normal formatters, but only applies to the title, not the message.
+ #
+ TITLE_FORMATTERS = [
+ # red
+ { :match => /fatal_error/, :replace => 'fatal error:', :color => :red, :style => :bold },
+ { :match => /error/, :color => :red, :style => :bold },
+ { :match => /removed/, :color => :red, :style => :bold },
+ { :match => /removing/, :color => :red, :style => :bold },
+ { :match => /destroyed/, :color => :red, :style => :bold },
+ { :match => /destroying/, :color => :red, :style => :bold },
+ { :match => /terminated/, :color => :red, :style => :bold },
+ { :match => /failed/, :replace => 'FAILED', :color => :red, :style => :bold },
+ { :match => /bailing/, :replace => 'bailing', :color => :red, :style => :bold },
+ { :match => /invalid/, :color => :red, :style => :bold },
+
+ # yellow
+ { :match => /warning/, :replace => 'warning:', :color => :yellow, :style => :bold },
+ { :match => /missing/, :color => :yellow, :style => :bold },
+ { :match => /skipping/, :color => :yellow, :style => :bold },
+
+ # green
+ { :match => /created/, :color => :green, :style => :bold },
+ { :match => /completed/, :color => :green, :style => :bold },
+ { :match => /ran/, :color => :green, :style => :bold },
+ { :match => /^registered/, :color => :green, :style => :bold },
+
+ # cyan
+ { :match => /note/, :replace => 'NOTE:', :color => :cyan, :style => :bold },
+
+ # magenta
+ { :match => /nochange/, :replace => 'no change', :color => :magenta },
+ { :match => /^loading/, :color => :magenta },
+ ]
+
+ def self.apply_message_filters(message)
+ return self.apply_filters(SORTED_FORMATTERS, message)
+ end
+
+ def self.apply_title_filters(title)
+ return self.apply_filters(TITLE_FORMATTERS, title)
+ end
+
+ private
+
+ def self.apply_filters(formatters, message)
+ level = LeapCli.logger.log_level
+ result = {}
+ formatters.each do |formatter|
+ if (formatter[:match_level] == level || formatter[:match_level].nil?)
+ if message =~ formatter[:match]
+ # puts "applying formatter #{formatter.inspect}"
+ result[:level] = formatter[:level] if formatter[:level]
+ result[:color] = formatter[:color] if formatter[:color]
+ result[:style] = formatter[:style] || formatter[:attribute] # (support original cap colors)
+
+ message.gsub!(formatter[:match], formatter[:replace]) if formatter[:replace]
+ message.replace(formatter[:prepend] + message) unless formatter[:prepend].nil?
+ message.replace(message + formatter[:append]) unless formatter[:append].nil?
+ message.replace(Time.now.strftime('%Y-%m-%d %T') + ' ' + message) if formatter[:timestamp]
+
+ if formatter[:exit]
+ LeapCli::Util.exit_status(formatter[:exit])
+ end
+
+ # stop formatting, unless formatter was just for string replacement
+ break unless formatter[:replace]
+ end
+ end
+ end
+
+ if result[:color] == :hide
+ return [nil, {}]
+ else
+ return [message, result]
+ end
+ end
+
+ end
+end
diff --git a/lib/leap_cli/macros.rb b/lib/leap_cli/macros.rb
deleted file mode 100644
index fdb9a94e..00000000
--- a/lib/leap_cli/macros.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-#
-# 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/files.rb b/lib/leap_cli/macros/files.rb
index 04c94edf..602fdddb 100644
--- a/lib/leap_cli/macros/files.rb
+++ b/lib/leap_cli/macros/files.rb
@@ -79,19 +79,26 @@ module LeapCli
#
# file_path(:dkim_priv_key) {generate_dkim_key}
#
- # notes:
+ # 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,
+ # * Argument 'path' must be relative to Path.provider/files or
+ # Path.provider_base/files. It is OK for the path to be prefixed with
+ # with 'files/', this prefix will be ignored.
+ #
+ # * The path returned by this method is an absolute path on the server.
+ #
+ # * The path stored for use later by rsync is relative to the local
+ # Path.provider. It should always be prefixed with 'files/'
+ #
+ # * 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.
#
+ # NOTE: this is an aweful way to do this. It would be better
+ # to rsync twice.
+ #
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
@@ -110,9 +117,11 @@ module LeapCli
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)
+ return File.join(
+ Leap::Platform.files_dir,
+ relative_path.sub(/^files\//, '')
+ )
end
# deprecated
diff --git a/lib/leap_cli/macros/haproxy.rb b/lib/leap_cli/macros/haproxy.rb
index 602ae726..3fef24c4 100644
--- a/lib/leap_cli/macros/haproxy.rb
+++ b/lib/leap_cli/macros/haproxy.rb
@@ -26,7 +26,7 @@ module LeapCli
# 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]+$/, ''
+ name = stunnel_entry.first.sub(/_[0-9]+$/, '')
hsh[name] = stunnel_entry.last['accept_port']
hsh
end
diff --git a/lib/leap_cli/macros/keys.rb b/lib/leap_cli/macros/keys.rb
index e7a75cfb..9cc01fe7 100644
--- a/lib/leap_cli/macros/keys.rb
+++ b/lib/leap_cli/macros/keys.rb
@@ -29,7 +29,7 @@ module LeapCli
# generating key if it is missing
#
def tor_public_key_path(path_name, key_type)
- file_path(path_name) { generate_tor_key(key_type) }
+ remote_file_path(path_name) { generate_tor_key(key_type) }
end
#
@@ -37,7 +37,7 @@ module LeapCli
# generating key if it is missing
#
def tor_private_key_path(path_name, key_type)
- file_path(path_name) { generate_tor_key(key_type) }
+ remote_file_path(path_name) { generate_tor_key(key_type) }
end
#
@@ -55,15 +55,15 @@ module LeapCli
require 'base32'
require 'base64'
require 'openssl'
- path = Path.find_file([path_name, self.name])
- if path && File.exists?(path)
+ path = Path.named_path([path_name, self.name])
+ if path && File.exist?(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
+ LeapCli.log :warning, 'Tor public key file "%s" does not exist' % path
end
end
diff --git a/lib/leap_cli/ssh.rb b/lib/leap_cli/ssh.rb
new file mode 100644
index 00000000..8b604d1d
--- /dev/null
+++ b/lib/leap_cli/ssh.rb
@@ -0,0 +1,7 @@
+require 'sshkit'
+require_relative 'ssh/options'
+require_relative 'ssh/backend'
+require_relative 'ssh/formatter'
+require_relative 'ssh/scripts'
+require_relative 'ssh/remote_command'
+require_relative 'ssh/key'
diff --git a/lib/leap_cli/ssh/backend.rb b/lib/leap_cli/ssh/backend.rb
new file mode 100644
index 00000000..3894d815
--- /dev/null
+++ b/lib/leap_cli/ssh/backend.rb
@@ -0,0 +1,209 @@
+#
+# A custome SSHKit backend, derived from the default netssh backend.
+# Our custom backend modifies the logging behavior and gracefully captures
+# common exceptions.
+#
+
+require 'stringio'
+require 'timeout'
+require 'sshkit'
+require 'leap_cli/ssh/formatter'
+require 'leap_cli/ssh/scripts'
+
+module SSHKit
+ class Command
+ #
+ # override exit_status in order to be less verbose
+ #
+ def exit_status=(new_exit_status)
+ @finished_at = Time.now
+ @exit_status = new_exit_status
+ if options[:raise_on_non_zero_exit] && exit_status > 0
+ message = ""
+ message += "exit status: " + exit_status.to_s + "\n"
+ message += "stdout: " + (full_stdout.strip.empty? ? "Nothing written" : full_stdout.strip) + "\n"
+ message += "stderr: " + (full_stderr.strip.empty? ? 'Nothing written' : full_stderr.strip) + "\n"
+ raise Failed, message
+ end
+ end
+ end
+end
+
+module LeapCli
+ module SSH
+ class Backend < SSHKit::Backend::Netssh
+
+ # since the @pool is a class instance variable, we need to copy
+ # the code from the superclass that initializes it. boo
+ @pool = SSHKit::Backend::ConnectionPool.new
+
+ # modify to pass itself to the block, instead of relying on instance_exec.
+ def run
+ Thread.current["sshkit_backend"] = self
+ # was: instance_exec(@host, &@block)
+ @block.call(self, @host)
+ ensure
+ Thread.current["sshkit_backend"] = nil
+ end
+
+ # if set, all the commands will begin with:
+ # sudo -u #{@user} -- sh -c '<command>'
+ def set_user(user='root')
+ @user = user
+ end
+
+ #
+ # like default capture, but gracefully logs failures for us
+ # last argument can be an options hash.
+ #
+ # available options:
+ #
+ # :fail_msg - [nil] if set, log this instead of the default
+ # fail message.
+ #
+ # :raise_error - [nil] if true, then reraise failed command exception.
+ #
+ # :log_cmd - [false] if true, log what the command is that gets run.
+ #
+ # :log_output - [true] if true, log each output from the command as
+ # it is received.
+ #
+ # :log_finish - [false] if true, log the exit status and time
+ # to completion
+ #
+ # :log_wrap - [nil] passed to log method as :wrap option.
+ #
+ def capture(*args)
+ extract_options(args)
+ initialize_logger(:log_output => false)
+ rescue_ssh_errors(*args) do
+ return super(*args)
+ end
+ end
+
+ #
+ # like default execute, but log the results as they come in.
+ #
+ # see capture() for available options
+ #
+ def stream(*args)
+ extract_options(args)
+ initialize_logger
+ rescue_ssh_errors(*args) do
+ execute(*args)
+ end
+ end
+
+ def log(*args, &block)
+ @logger ||= LeapCli.new_logger
+ @logger.log(*args, &block)
+ end
+
+ # some prewritten servers-side scripts
+ def scripts
+ @scripts ||= LeapCli::SSH::Scripts.new(self, @host.hostname)
+ end
+
+ #
+ # sshkit just passes upload! and download! to Net::SCP, but Net::SCP
+ # make it impossible to set the file permissions. Here is how the mode
+ # is determined, from upload.rb:
+ #
+ # mode = channel[:stat] ? channel[:stat].mode & 07777 : channel[:options][:mode]
+ #
+ # The stat info from the file always overrides the mode you pass in options.
+ # However, the channel[:options][:mode] will be applied for pure in-memory
+ # uploads. So, if the mode is set, we convert the upload to be a memory
+ # upload instead of a file upload.
+ #
+ # Stupid, but blame Net::SCP.
+ #
+ def upload!(src, dest, options={})
+ if options[:mode]
+ if src.is_a?(StringIO)
+ content = src
+ else
+ content = StringIO.new(File.read(src))
+ end
+ super(content, dest, options)
+ else
+ super(src, dest, options)
+ end
+ end
+
+ private
+
+ #
+ # creates a new logger instance for this specific ssh command.
+ # by doing this, each ssh session has its own logger and its own
+ # indentation.
+ #
+ # potentially modifies 'args' array argument.
+ #
+ def initialize_logger(default_options={})
+ @logger ||= LeapCli.new_logger
+ @output = LeapCli::SSH::Formatter.new(@logger, @host, default_options.merge(@options))
+ end
+
+ def extract_options(args)
+ if args.last.is_a? Hash
+ @options = args.pop
+ else
+ @options = {}
+ end
+ end
+
+ #
+ # capture common exceptions
+ #
+ def rescue_ssh_errors(*args, &block)
+ yield
+ rescue Net::SSH::HostKeyMismatch => exc
+ @logger.log(:fatal_error, "Host key mismatch!") do
+ @logger.log(exc.to_s)
+ @logger.log("The ssh host key for the server does not match what is on "+
+ " file in `%s`." % Path.named_path(:known_hosts))
+ @logger.log("One of these is happening:") do
+ @logger.log("There is an active Man in The Middle attack against you.")
+ @logger.log("Or, someone has generated new host keys for the server " +
+ "and your provider files are out of date.")
+ @logger.log("Or, a new server is using this IP address " +
+ "and your provider files are out of date.")
+ @logger.log("Or, the server configuration has changed to use a different host key.")
+ end
+ @logger.log("You can pin a different host key using `leap node init NODE`, " +
+ "but you must verify the fingerprint of the new host key!")
+ end
+ exit(1)
+ rescue StandardError => exc
+ if exc.is_a?(SSHKit::Command::Failed) || exc.is_a?(SSHKit::Runner::ExecuteError)
+ if @options[:raise_error]
+ raise LeapCli::SSH::ExecuteError, exc.to_s
+ elsif @options[:fail_msg]
+ @logger.log(@options[:fail_msg], host: @host.hostname, :color => :red)
+ else
+ @logger.log(:failed, args.join(' '), host: @host.hostname) do
+ @logger.log(exc.to_s.strip, wrap: true)
+ end
+ end
+ elsif exc.is_a?(Timeout::Error) || exc.is_a?(Net::SSH::ConnectionTimeout)
+ @logger.log(:failed, args.join(' '), host: @host.hostname) do
+ @logger.log("Connection timed out")
+ end
+ if @options[:raise_error]
+ raise LeapCli::SSH::TimeoutError, exc.to_s
+ end
+ else
+ raise
+ end
+ return nil
+ end
+
+ def output
+ @output ||= LeapCli::SSH::Formatter.new(@logger, @host)
+ end
+
+ end
+ end
+end
+
diff --git a/lib/leap_cli/ssh/formatter.rb b/lib/leap_cli/ssh/formatter.rb
new file mode 100644
index 00000000..c2e386dc
--- /dev/null
+++ b/lib/leap_cli/ssh/formatter.rb
@@ -0,0 +1,70 @@
+#
+# A custom SSHKit formatter that uses LeapLogger.
+#
+
+require 'sshkit'
+
+module LeapCli
+ module SSH
+
+ class Formatter < SSHKit::Formatter::Abstract
+
+ DEFAULT_OPTIONS = {
+ :log_cmd => false, # log what the command is that gets run.
+ :log_output => true, # log each output from the command as it is received.
+ :log_finish => false # log the exit status and time to completion.
+ }
+
+ def initialize(logger, host, options={})
+ @logger = logger || LeapCli.new_logger
+ @host = host
+ @options = DEFAULT_OPTIONS.merge(options)
+ end
+
+ def write(obj)
+ @logger.log(obj.to_s, :host => @host.hostname)
+ end
+
+ def log_command_start(command)
+ if @options[:log_cmd]
+ @logger.log(:running, "`" + command.to_s + "`", :host => @host.hostname)
+ end
+ end
+
+ def log_command_data(command, stream_type, stream_data)
+ if @options[:log_output]
+ color = stream_type == :stderr ? :red : nil
+ @logger.log(stream_data.to_s.chomp,
+ :color => color, :host => @host.hostname, :wrap => options[:log_wrap])
+ end
+ end
+
+ def log_command_exit(command)
+ if @options[:log_finish]
+ runtime = sprintf('%5.3fs', command.runtime)
+ if command.failure?
+ message = "in #{runtime} with status #{command.exit_status}."
+ @logger.log(:failed, message, :host => @host.hostname)
+ else
+ message = "in #{runtime}."
+ @logger.log(:completed, message, :host => @host.hostname)
+ end
+ end
+ end
+ end
+
+ end
+end
+
+ #
+ # A custom InteractionHandler that will output the results as they come in.
+ #
+ #class LoggingInteractionHandler
+ # def initialize(hostname, logger=nil)
+ # @hostname = hostname
+ # @logger = logger || LeapCli.new_logger
+ # end
+ # def on_data(command, stream_name, data, channel)
+ # @logger.log(data, host: @hostname, wrap: true)
+ # end
+ #end
diff --git a/lib/leap_cli/ssh/key.rb b/lib/leap_cli/ssh/key.rb
new file mode 100644
index 00000000..76223b7e
--- /dev/null
+++ b/lib/leap_cli/ssh/key.rb
@@ -0,0 +1,310 @@
+#
+# A wrapper around OpenSSL::PKey::RSA instances to provide a better api for
+# dealing with SSH keys.
+#
+# NOTES:
+#
+# cipher 'ssh-ed25519' not supported yet because we are waiting
+# for support in Net::SSH
+#
+# there are many ways to represent an SSH key, since SSH keys can be of
+# a variety of types.
+#
+# To confuse matters more, there are multiple binary representations.
+# So, for example, an RSA key has a native SSH representation
+# (two bignums, e followed by n), and a DER representation.
+#
+# AWS uses fingerprints of the DER representation, but SSH typically reports
+# fingerprints of the SSH representation.
+#
+# Also, SSH public key files are base64 encoded, but with whitespace removed
+# so it all goes on one line.
+#
+# Some useful links:
+#
+# https://stackoverflow.com/questions/3162155/convert-rsa-public-key-to-rsa-der
+# https://net-ssh.github.io/ssh/v2/api/classes/Net/SSH/Buffer.html
+# https://serverfault.com/questions/603982/why-does-my-openssh-key-fingerprint-not-match-the-aws-ec2-console-keypair-finger
+#
+
+require 'net/ssh'
+require 'forwardable'
+require 'base64'
+
+module LeapCli
+ module SSH
+ class Key
+ extend Forwardable
+
+ attr_accessor :filename
+ attr_accessor :comment
+
+ # supported ssh key types, in order of preference
+ SUPPORTED_TYPES = ['ssh-rsa', 'ecdsa-sha2-nistp256']
+ SUPPORTED_TYPES_RE = /(#{SUPPORTED_TYPES.join('|')})/
+
+ ##
+ ## CLASS METHODS
+ ##
+
+ def self.load(arg1, arg2=nil)
+ key = nil
+ if arg1.is_a? OpenSSL::PKey::RSA
+ key = Key.new arg1
+ elsif arg1.is_a? String
+ if arg1 =~ /^ssh-/
+ type, data = arg1.split(' ')
+ key = Key.new load_from_data(data, type)
+ elsif File.exist? arg1
+ key = Key.new load_from_file(arg1)
+ key.filename = arg1
+ else
+ key = Key.new load_from_data(arg1, arg2)
+ end
+ end
+ return key
+ rescue StandardError
+ end
+
+ def self.load_from_file(filename)
+ public_key = nil
+ private_key = nil
+ begin
+ public_key = Net::SSH::KeyFactory.load_public_key(filename)
+ rescue NotImplementedError, Net::SSH::Exception, OpenSSL::PKey::PKeyError
+ begin
+ private_key = Net::SSH::KeyFactory.load_private_key(filename)
+ rescue NotImplementedError, Net::SSH::Exception, OpenSSL::PKey::PKeyError
+ end
+ end
+ public_key || private_key
+ end
+
+ def self.load_from_data(data, type='ssh-rsa')
+ public_key = nil
+ private_key = nil
+ begin
+ public_key = Net::SSH::KeyFactory.load_data_public_key("#{type} #{data}")
+ rescue NotImplementedError, Net::SSH::Exception, OpenSSL::PKey::PKeyError
+ begin
+ private_key = Net::SSH::KeyFactory.load_data_private_key("#{type} #{data}")
+ rescue NotImplementedError, Net::SSH::Exception, OpenSSL::PKey::PKeyError
+ end
+ end
+ public_key || private_key
+ end
+
+ def self.my_public_keys
+ load_keys_from_paths File.join(ENV['HOME'], '.ssh', '*.pub')
+ end
+
+ def self.provider_public_keys
+ load_keys_from_paths Path.named_path([:user_ssh, '*'])
+ end
+
+ #
+ # Picks one key out of an array of keys that we think is the "best",
+ # based on the order of preference in SUPPORTED_TYPES
+ #
+ # Currently, this does not take bitsize into account.
+ #
+ def self.pick_best_key(keys)
+ keys.select {|k|
+ SUPPORTED_TYPES.include?(k.type)
+ }.sort {|a,b|
+ SUPPORTED_TYPES.index(a.type) <=> SUPPORTED_TYPES.index(b.type)
+ }.first
+ end
+
+ #
+ # takes a string with one or more ssh keys, one key per line,
+ # and returns an array of Key objects.
+ #
+ # the lines should be in one of these formats:
+ #
+ # 1. <hostname> <key-type> <key>
+ # 2. <key-type> <key>
+ #
+ def self.parse_keys(string)
+ keys = []
+ lines = string.split("\n").grep(/^[^#]/)
+ lines.each do |line|
+ if line =~ / #{Key::SUPPORTED_TYPES_RE} /
+ # <hostname> <key-type> <key>
+ keys << line.split(' ')[1..2]
+ elsif line =~ /^#{Key::SUPPORTED_TYPES_RE} /
+ # <key-type> <key>
+ keys << line.split(' ')
+ end
+ end
+ return keys.map{|k| Key.load(k[1], k[0])}
+ end
+
+ #
+ # takes a string with one or more ssh keys, one key per line,
+ # and returns a string that specified the ssh key algorithms
+ # that are supported by the keys, in order of preference.
+ #
+ # eg: ecdsa-sha2-nistp256,ssh-rsa,ssh-ed25519
+ #
+ def self.supported_host_key_algorithms(string)
+ if string
+ self.parse_keys(string).map {|key|
+ key.type
+ }.join(',')
+ else
+ ""
+ end
+ end
+
+ private
+
+ def self.load_keys_from_paths(key_glob)
+ keys = []
+ Dir.glob(key_glob).each do |file|
+ key = Key.load(file)
+ if key && key.public?
+ keys << key
+ end
+ end
+ return keys
+ end
+
+ ##
+ ## INSTANCE METHODS
+ ##
+
+ public
+
+ def initialize(p_key)
+ @key = p_key
+ end
+
+ def_delegator :@key, :ssh_type, :type
+ def_delegator :@key, :public_encrypt, :public_encrypt
+ def_delegator :@key, :public_decrypt, :public_decrypt
+ def_delegator :@key, :private_encrypt, :private_encrypt
+ def_delegator :@key, :private_decrypt, :private_decrypt
+ def_delegator :@key, :params, :params
+ def_delegator :@key, :to_text, :to_text
+
+ def public_key
+ Key.new(@key.public_key)
+ end
+
+ def private_key
+ Key.new(@key.private_key)
+ end
+
+ def private?
+ @key.respond_to?(:private?) ? @key.private? : @key.private_key?
+ end
+
+ def public?
+ @key.respond_to?(:public?) ? @key.public? : @key.public_key?
+ end
+
+ #
+ # three arguments:
+ #
+ # - digest: one of md5, sha1, sha256, etc. (default sha256)
+ # - encoding: either :hex (default) or :base64
+ # - type: fingerprint type, either :ssh (default) or :der
+ #
+ # NOTE:
+ #
+ # * I am not sure how to make a fingerprint for OpenSSL::PKey::EC::Point
+ #
+ # * AWS reports fingerprints using MD5 digest for uploaded ssh keys,
+ # but SHA1 for keys it created itself.
+ #
+ # * Also, AWS fingerprints are digests on the DER encoding of the key.
+ # But standard SSH fingerprints are digests of SSH encoding of the key.
+ #
+ # * Other tools will sometimes display fingerprints in hex and sometimes
+ # in base64. Arrrgh.
+ #
+ def fingerprint(type: :ssh, digest: :sha256, encoding: :hex)
+ require 'digest'
+
+ digest = digest.to_s.upcase
+ digester = case digest
+ when "MD5" then Digest::MD5.new
+ when "SHA1" then Digest::SHA1.new
+ when "SHA256" then Digest::SHA256.new
+ when "SHA384" then Digest::SHA384.new
+ when "SHA512" then Digest::SHA512.new
+ else raise ArgumentError, "digest #{digest} is unknown"
+ end
+
+ keymatter = nil
+ if type == :der && @key.respond_to?(:to_der)
+ keymatter = @key.to_der
+ else
+ keymatter = self.raw_key.to_s
+ end
+
+ fp = nil
+ if encoding == :hex
+ fp = digester.hexdigest(keymatter)
+ elsif encoding == :base64
+ fp = Base64.encode64(digester.digest(keymatter)).sub(/=$/, '')
+ else
+ raise ArgumentError, "encoding #{encoding} not understood"
+ end
+
+ if digest == "MD5" && encoding == :hex
+ return fp.scan(/../).join(':')
+ else
+ return fp
+ end
+ end
+
+ #
+ # not sure if this will always work, but is seems to for now.
+ #
+ def bits
+ Net::SSH::Buffer.from(:key, @key).to_s.split("\001\000").last.size * 8
+ end
+
+ def summary
+ if self.filename
+ "%s %s %s (%s)" % [self.type, self.bits, self.fingerprint, File.basename(self.filename)]
+ else
+ "%s %s %s" % [self.type, self.bits, self.fingerprint]
+ end
+ end
+
+ def to_s
+ self.type + " " + self.key
+ end
+
+ #
+ # base64 encoding of the key, with spaces removed.
+ #
+ def key
+ [Net::SSH::Buffer.from(:key, @key).to_s].pack("m*").gsub(/\s/, "")
+ end
+
+ def raw_key
+ Net::SSH::Buffer.from(:key, @key)
+ end
+
+ def ==(other_key)
+ return false if other_key.nil?
+ return false if self.class != other_key.class
+ return self.to_text == other_key.to_text
+ end
+
+ def in_known_hosts?(*identifiers)
+ identifiers.each do |identifier|
+ Net::SSH::KnownHosts.search_for(identifier).each do |key|
+ return true if self == key
+ end
+ end
+ return false
+ end
+
+ end
+ end
+end
diff --git a/lib/leap_cli/ssh/options.rb b/lib/leap_cli/ssh/options.rb
new file mode 100644
index 00000000..7bc06564
--- /dev/null
+++ b/lib/leap_cli/ssh/options.rb
@@ -0,0 +1,100 @@
+#
+# Options for passing to the ruby gem ssh-net
+#
+
+module LeapCli
+ module SSH
+ module Options
+
+ #
+ # options passed to net-ssh. See
+ # https://net-ssh.github.io/net-ssh/Net/SSH.html#method-c-start
+ # for the available options.
+ #
+ def self.global_options
+ {
+ #:keys_only => true,
+ :global_known_hosts_file => Path.named_path(:known_hosts),
+ :user_known_hosts_file => '/dev/null',
+ :paranoid => true,
+ :verbose => net_ssh_log_level,
+ :auth_methods => ["publickey"],
+ :timeout => 5
+ }
+ end
+
+ def self.node_options(node, ssh_options_override=nil)
+ {
+ # :host_key_alias => node.name, << incompatible with ports in known_hosts
+ :host_name => node.ip_address,
+ :port => node.ssh.port
+ }.merge(
+ contingent_ssh_options_for_node(node)
+ ).merge(
+ ssh_options_override||{}
+ )
+ end
+
+ def self.options_from_args(args)
+ ssh_options = {}
+ if args[:port]
+ ssh_options[:port] = args[:port]
+ end
+ if args[:ip]
+ ssh_options[:host_name] = args[:ip]
+ end
+ if args[:auth_methods]
+ ssh_options[:auth_methods] = args[:auth_methods]
+ end
+ if args[:user]
+ ssh_options[:user] = args[:user]
+ end
+ return ssh_options
+ end
+
+ def self.sanitize_command(cmd)
+ if cmd =~ /(^|\/| )rm / || cmd =~ /(^|\/| )unlink /
+ LeapCli.log :warning, "You probably don't want to do that. Run with --force if you are really sure."
+ exit(1)
+ else
+ cmd
+ end
+ end
+
+ private
+
+ def self.contingent_ssh_options_for_node(node)
+ opts = {}
+ if node.vagrant?
+ opts[:keys] = [LeapCli::Util::Vagrant.vagrant_ssh_key_file]
+ opts[:keys_only] = true # only use the keys specified above, and
+ # ignore whatever keys the ssh-agent is aware of.
+ opts[:paranoid] = false # we skip host checking for vagrant nodes,
+ # because fingerprint is different for everyone.
+ if LeapCli.logger.log_level <= 1
+ opts[:verbose] = :error # suppress all the warnings about adding
+ # host keys to known_hosts, since it is
+ # not actually doing that.
+ end
+ end
+ if !node.supported_ssh_host_key_algorithms.empty?
+ opts[:host_key] = node.supported_ssh_host_key_algorithms
+ end
+ return opts
+ end
+
+ def self.net_ssh_log_level
+ if DEBUG
+ case LeapCli.logger.log_level
+ when 1 then :error
+ when 2 then :info
+ else :debug
+ end
+ else
+ :fatal
+ end
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/leap_cli/ssh/remote_command.rb b/lib/leap_cli/ssh/remote_command.rb
new file mode 100644
index 00000000..0e9f2d55
--- /dev/null
+++ b/lib/leap_cli/ssh/remote_command.rb
@@ -0,0 +1,124 @@
+#
+# Provides SSH.remote_command for running commands in parallel or in sequence
+# on remote servers.
+#
+# The gem sshkit is used for this.
+#
+
+require 'sshkit'
+require 'leap_cli/ssh/options'
+require 'leap_cli/ssh/backend'
+
+SSHKit.config.backend = LeapCli::SSH::Backend
+LeapCli::SSH::Backend.config.ssh_options = LeapCli::SSH::Options.global_options
+
+#
+# define remote_command
+#
+module LeapCli
+ module SSH
+
+ class ExecuteError < StandardError
+ end
+
+ class TimeoutError < ExecuteError
+ end
+
+ # override default runner mode
+ class CustomCoordinator < SSHKit::Coordinator
+ private
+ def default_options
+ { in: :groups, limit: 10, wait: 0 }
+ end
+ end
+
+ #
+ # Available options:
+ #
+ # :port -- ssh port
+ # :ip -- ssh ip
+ # :auth_methods -- e.g. ["pubkey", "password"]
+ # :user -- default 'root'
+ #
+ def self.remote_command(nodes, options={}, &block)
+ CustomCoordinator.new(
+ host_list(
+ nodes,
+ SSH::Options.options_from_args(options)
+ )
+ ).each do |ssh, host|
+ LeapCli.log 2, "ssh options for #{host.hostname}: #{host.ssh_options.inspect}"
+ if host.user != 'root'
+ # if the ssh user is not root, we want to make the ssh commands
+ # switch to root before they are run:
+ ssh.set_user('root')
+ end
+ yield ssh, host
+ end
+ end
+
+ #
+ # For example:
+ #
+ # SSH.remote_sync(nodes) do |sync, host|
+ # sync.source = '/from'
+ # sync.dest = '/to'
+ # sync.flags = ''
+ # sync.includes = []
+ # sync.excludes = []
+ # sync.exec
+ # end
+ #
+ def self.remote_sync(nodes, options={}, &block)
+ require 'rsync_command'
+ hosts = host_list(
+ nodes,
+ SSH::Options.options_from_args(options)
+ )
+ rsync = RsyncCommand.new(:logger => LeapCli::logger)
+ rsync.asynchronously(hosts) do |sync, host|
+ sync.logger = LeapCli.new_logger
+ sync.user = host.user || fetch(:user, ENV['USER'])
+ sync.host = host.hostname
+ sync.ssh = SSH::Options.global_options.merge(host.ssh_options)
+ sync.chdir = Path.provider
+ yield(sync, host)
+ end
+ if rsync.failed?
+ LeapCli::Util.bail! do
+ LeapCli.log :failed, "to rsync to #{rsync.failures.map{|f|f[:dest][:host]}.join(' ')}"
+ end
+ end
+ end
+
+ private
+
+ def self.host_list(nodes, ssh_options_override={})
+ if nodes.is_a?(Config::ObjectList)
+ list = nodes.values
+ elsif nodes.is_a?(Config::Node)
+ list = [nodes]
+ else
+ raise ArgumentError, "I don't understand the type of argument `nodes`"
+ end
+ list.collect do |node|
+ options = SSH::Options.node_options(node, ssh_options_override)
+ user = options.delete(:user) || 'root'
+ #
+ # note: whatever hostname is specified here will be what is used
+ # when loading options from .ssh/config. However, this value
+ # has no impact on the actual ip address that is connected to,
+ # which is determined by the :host_name value in ssh_options.
+ #
+ SSHKit::Host.new(
+ :hostname => node.domain.full,
+ :user => user,
+ :ssh_options => options
+ )
+ end
+ end
+
+ end
+end
+
+
diff --git a/lib/leap_cli/ssh/scripts.rb b/lib/leap_cli/ssh/scripts.rb
new file mode 100644
index 00000000..3dd6b604
--- /dev/null
+++ b/lib/leap_cli/ssh/scripts.rb
@@ -0,0 +1,163 @@
+#
+# Common commands that we would like to run on remote servers.
+#
+# These scripts are available via:
+#
+# SSH.remote_command(nodes) do |ssh, host|
+# ssh.script.custom_script_name
+# end
+#
+
+module LeapCli
+ module SSH
+ class Scripts
+
+ REQUIRED_PACKAGES = "puppet rsync lsb-release locales"
+
+ attr_reader :ssh, :host
+ def initialize(backend, hostname)
+ @ssh = backend
+ @host = hostname
+ end
+
+ #
+ # creates directories that are owned by root and 700 permissions
+ #
+ def mkdirs(*dirs)
+ raise ArgumentError.new('illegal dir name') if dirs.grep(/[\' ]/).any?
+ ssh.stream dirs.collect{|dir| "mkdir -m 700 -p #{dir}; "}.join
+ end
+
+ #
+ # echos "ok" if the node has been initialized and the required packages are installed, bails out otherwise.
+ #
+ def assert_initialized
+ begin
+ test_initialized_file = "test -f #{Leap::Platform.init_path}"
+ check_required_packages = "! dpkg-query -W --showformat='${Status}\n' #{REQUIRED_PACKAGES} 2>&1 | grep -q -E '(deinstall|no packages)'"
+ ssh.stream "#{test_initialized_file} && #{check_required_packages} && echo ok", :raise_error => true
+ rescue SSH::ExecuteError
+ ssh.log :error, "running deploy: node not initialized. Run `leap node init #{host}`.", :host => host
+ raise # will skip further action on this node
+ end
+ end
+
+ #
+ # bails out the deploy if the file /etc/leap/no-deploy exists.
+ #
+ def check_for_no_deploy
+ begin
+ ssh.stream "test ! -f /etc/leap/no-deploy", :raise_error => true, :log_output => false
+ rescue SSH::TimeoutError
+ raise
+ rescue SSH::ExecuteError
+ ssh.log :warning, "can't continue because file /etc/leap/no-deploy exists", :host => host
+ raise # will skip further action on this node
+ end
+ end
+
+ #
+ # dumps debugging information
+ #
+ def debug
+ output = ssh.capture "#{Leap::Platform.leap_dir}/bin/debug.sh"
+ ssh.log(output, :wrap => true, :host => host, :color => :cyan)
+ end
+
+ #
+ # dumps the recent deploy history to the console
+ #
+ def history(lines)
+ cmd = "(test -s /var/log/leap/deploy-summary.log && tail -n #{lines} /var/log/leap/deploy-summary.log) || (test -s /var/log/leap/deploy-summary.log.1 && tail -n #{lines} /var/log/leap/deploy-summary.log.1) || (echo 'no history')"
+ history = ssh.capture(cmd, :log_output => false)
+ if history
+ ssh.log host, :color => :cyan, :style => :bold do
+ ssh.log history, :wrap => true
+ end
+ end
+ end
+
+ #
+ # apply puppet! weeeeeee
+ #
+ def puppet_apply(options)
+ cmd = "#{Leap::Platform.leap_dir}/bin/puppet_command set_hostname apply #{flagize(options)}"
+ ssh.stream cmd, :log_finish => true
+ end
+
+ def install_authorized_keys
+ ssh.log :updating, "authorized_keys" do
+ mkdirs '/root/.ssh'
+ ssh.upload! LeapCli::Path.named_path(:authorized_keys), '/root/.ssh/authorized_keys', :mode => 0600
+ end
+ end
+
+ #
+ # for vagrant nodes, we install insecure vagrant key to authorized_keys2, since deploy
+ # will overwrite authorized_keys.
+ #
+ # why force the insecure vagrant key?
+ # if we don't do this, then first time initialization might fail if the user has many keys
+ # (ssh will bomb out before it gets to the vagrant key).
+ # and it really doesn't make sense to ask users to pin the insecure vagrant key in their
+ # .ssh/config files.
+ #
+ def install_insecure_vagrant_key
+ ssh.log :installing, "insecure vagrant key" do
+ mkdirs '/root/.ssh'
+ ssh.upload! LeapCli::Path.vagrant_ssh_pub_key_file, '/root/.ssh/authorized_keys2', :mode => 0600
+ end
+ end
+
+ def install_prerequisites
+ bin_dir = File.join(Leap::Platform.leap_dir, 'bin')
+ node_init_path = File.join(bin_dir, 'node_init')
+ ssh.log :running, "node_init script" do
+ mkdirs bin_dir
+ ssh.upload! LeapCli::Path.node_init_script, node_init_path, :mode => 0700
+ ssh.stream node_init_path, :log_wrap => true
+ end
+ end
+
+ #
+ # AWS debian images only allow you to login as admin. This is done with a
+ # custom command in /root/.ssh/authorized_keys, instead of by modifying
+ # /etc/ssh/sshd_config.
+ #
+ # We need to be able to ssh as root for scp and rsync to work.
+ #
+ # This command is run as 'admin', with a sudo wrapper. In order for the
+ # sudo to work, the command must be specified as separate arguments with
+ # no spaces (that is how ssh-kit works).
+ #
+ def allow_root_ssh
+ ssh.execute 'cp', '/home/admin/.ssh/authorized_keys', '/root/.ssh/authorized_keys'
+ end
+
+ #
+ # uploads an acme challenge for renewing certificates using Let's Encrypt CA.
+ #
+ # Filename is returned from acme api, so it must not be trusted.
+ #
+ def upload_acme_challenge(filename, content)
+ path = '/srv/acme/' + filename.gsub(/[^a-zA-Z0-9_-]/, '')
+ ssh.upload! StringIO.new(content), path, :mode => 0444
+ end
+
+ private
+
+ def flagize(hsh)
+ hsh.inject([]) {|str, item|
+ if item[1] === false
+ str
+ elsif item[1] === true
+ str << "--" + item[0].to_s
+ else
+ str << "--" + item[0].to_s + " " + item[1].inspect
+ end
+ }.join(' ')
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/leap_cli/util/console_table.rb b/lib/leap_cli/util/console_table.rb
new file mode 100644
index 00000000..ccdcc2ab
--- /dev/null
+++ b/lib/leap_cli/util/console_table.rb
@@ -0,0 +1,62 @@
+module LeapCli; module Util
+
+ class ConsoleTable
+ def table
+ @rows = []
+ @cell_options = []
+
+ @row_options = []
+ @column_widths = []
+ @column_options = []
+
+ @current_row = 0
+ @current_column = 0
+ yield
+ end
+
+ def row(options=nil)
+ @current_column = 0
+ @rows[@current_row] = []
+ @cell_options[@current_row] = []
+ @row_options[@current_row] ||= options
+ yield
+ @current_row += 1
+ end
+
+ def column(str, options={})
+ str ||= ""
+ @rows[@current_row][@current_column] = str
+ @cell_options[@current_row][@current_column] = options
+ @column_widths[@current_column] = [str.length, options[:min_width]||0, @column_widths[@current_column]||0].max
+ @column_options[@current_column] ||= options
+ @current_column += 1
+ end
+
+ def draw_table
+ @rows.each_with_index do |row, i|
+ color = (@row_options[i]||{})[:color]
+ row.each_with_index do |column, j|
+ align = (@column_options[j]||{})[:align] || "left"
+ width = @column_widths[j]
+ cell_color = @cell_options[i][j] && @cell_options[i][j][:color]
+ cell_color ||= color
+ if cell_color
+ str = LeapCli.logger.colorize(column, cell_color)
+ extra_width = str.length - column.length
+ else
+ str = column
+ extra_width = 0
+ end
+ if align == "right"
+ printf " %#{width+extra_width}s" % str
+ else
+ printf " %-#{width+extra_width}s" % str
+ end
+ end
+ puts
+ end
+ puts
+ end
+ end
+
+end; end \ No newline at end of file
diff --git a/lib/leap_cli/util/secret.rb b/lib/leap_cli/util/secret.rb
new file mode 100644
index 00000000..749b9595
--- /dev/null
+++ b/lib/leap_cli/util/secret.rb
@@ -0,0 +1,55 @@
+# encoding: utf-8
+#
+# A simple secret generator
+#
+# Uses OpenSSL random number generator instead of Ruby's rand function
+#
+autoload :OpenSSL, 'openssl'
+
+module LeapCli; module Util
+ class Secret
+ CHARS = (('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a) - "i1loO06G".split(//u)
+ HEX = (0..9).to_a + ('a'..'f').to_a
+
+ #
+ # generate a secret with with no ambiguous characters.
+ #
+ # +length+ is in chars
+ #
+ # Only alphanumerics are allowed, in order to make these passwords work
+ # for REST url calls and to allow you to easily copy and paste them.
+ #
+ def self.generate(length = 16)
+ seed
+ OpenSSL::Random.random_bytes(length).bytes.to_a.collect { |byte|
+ CHARS[ byte % CHARS.length ]
+ }.join
+ end
+
+ #
+ # generates a hex secret, instead of an alphanumeric on.
+ #
+ # length is in bits
+ #
+ def self.generate_hex(length = 128)
+ seed
+ OpenSSL::Random.random_bytes(length/4).bytes.to_a.collect { |byte|
+ HEX[ byte % HEX.length ]
+ }.join
+ end
+
+ private
+
+ def self.seed
+ @pid ||= 0
+ pid = $$
+ if @pid != pid
+ now = Time.now
+ ary = [now.to_i, now.nsec, @pid, pid]
+ OpenSSL::Random.seed(ary.to_s)
+ @pid = pid
+ end
+ end
+
+ end
+end; end
diff --git a/lib/leap_cli/util/vagrant.rb b/lib/leap_cli/util/vagrant.rb
new file mode 100644
index 00000000..c67ea4f1
--- /dev/null
+++ b/lib/leap_cli/util/vagrant.rb
@@ -0,0 +1,26 @@
+require 'fileutils'
+
+module LeapCli
+ module Util
+ module Vagrant
+
+ #
+ # 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 self.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
+
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/leap_cli/x509.rb b/lib/leap_cli/x509.rb
new file mode 100644
index 00000000..68d13ddf
--- /dev/null
+++ b/lib/leap_cli/x509.rb
@@ -0,0 +1,16 @@
+#
+# optional. load if you want access to any methods in the module X509
+#
+
+require 'date'
+require 'securerandom'
+require 'openssl'
+require 'digest'
+require 'digest/md5'
+require 'digest/sha1'
+
+require 'certificate_authority'
+
+require 'leap_cli/x509/certs'
+require 'leap_cli/x509/signing_profiles'
+require 'leap_cli/x509/utils'
diff --git a/lib/leap_cli/x509/certs.rb b/lib/leap_cli/x509/certs.rb
new file mode 100644
index 00000000..3b74d2fb
--- /dev/null
+++ b/lib/leap_cli/x509/certs.rb
@@ -0,0 +1,232 @@
+
+module LeapCli; module X509
+
+ #
+ # returns a fingerprint of a x509 certificate
+ #
+ # Note: there are different ways of computing a digest of a certificate.
+ # You can either take a digest of the entire cert in DER format, or you
+ # can take a digest of the public key.
+ #
+ # For now, we only support the DER method.
+ #
+ def self.fingerprint(digest, cert_file)
+ if cert_file.is_a? String
+ cert = OpenSSL::X509::Certificate.new(Util.read_file!(cert_file))
+ elsif cert_file.is_a? OpenSSL::X509::Certificate
+ cert = cert_file
+ elsif cert_file.is_a? CertificateAuthority::Certificate
+ cert = cert_file.openssl_body
+ end
+ digester = case digest
+ when "MD5" then Digest::MD5.new
+ when "SHA1" then Digest::SHA1.new
+ when "SHA256" then Digest::SHA256.new
+ when "SHA384" then Digest::SHA384.new
+ when "SHA512" then Digest::SHA512.new
+ end
+ digester.hexdigest(cert.to_der)
+ end
+
+ def self.ca_root
+ @ca_root ||= begin
+ load_certificate_file(:ca_cert, :ca_key)
+ end
+ end
+
+ def self.client_ca_root
+ @client_ca_root ||= begin
+ load_certificate_file(:client_ca_cert, :client_ca_key)
+ end
+ end
+
+ def self.load_certificate_file(crt_file, key_file=nil, password=nil)
+ crt = Util.read_file!(crt_file)
+ openssl_cert = OpenSSL::X509::Certificate.new(crt)
+ cert = CertificateAuthority::Certificate.from_openssl(openssl_cert)
+ if key_file
+ key = Util.read_file!(key_file)
+ cert.key_material.private_key = OpenSSL::PKey::RSA.new(key, password)
+ end
+ return cert
+ end
+
+ #
+ # creates a new certificate authority.
+ #
+ def self.new_ca(options, common_name)
+ root = CertificateAuthority::Certificate.new
+
+ # set subject
+ root.subject.common_name = common_name
+ possible = ['country', 'state', 'locality', 'organization', 'organizational_unit', 'email_address']
+ options.keys.each do |key|
+ if possible.include?(key)
+ root.subject.send(key + '=', options[key])
+ end
+ end
+
+ # set expiration
+ root.not_before = X509.yesterday
+ root.not_after = X509.yesterday_advance(options['life_span'])
+
+ # generate private key
+ root.serial_number.number = 1
+ root.key_material.generate_key(options['bit_size'])
+
+ # sign self
+ root.signing_entity = true
+ root.parent = root
+ root.sign!(ca_root_signing_profile)
+ return root
+ end
+
+ #
+ # creates a CSR in memory and returns it.
+ # with the correct extReq attribute so that the CA
+ # doens't generate certs with extensions we don't want.
+ #
+ def self.new_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
+
+ #
+ # creates new csr and cert files for a particular domain.
+ #
+ # The cert is signed with the ca_root, but should be replaced
+ # later with a real cert signed by a better ca
+ #
+ def self.create_csr_and_cert(options)
+ bit_size = options[:bits].to_i
+ digest = options[:digest]
+
+ # RSA key
+ keypair = CertificateAuthority::MemoryKeyMaterial.new
+ Util.log :generating, "%s bit RSA key" % bit_size do
+ keypair.generate_key(bit_size)
+ Util.write_file! [:commercial_key, options[:domain]], keypair.private_key.to_pem
+ end
+
+ # CSR
+ csr = nil
+ dn = CertificateAuthority::DistinguishedName.new
+ dn.common_name = options[:domain]
+ dn.organization = options[:organization]
+ dn.ou = options[:organizational_unit]
+ dn.email_address = options[:email]
+ dn.country = options[:country]
+ dn.state = options[:state]
+ dn.locality = options[:locality]
+ Util.log :generating, "CSR with #{digest} digest and #{print_dn(dn)}" do
+ csr = new_csr(dn, keypair, options[:digest])
+ Util.write_file! [:commercial_csr, options[: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.
+ Util.log :generating, "self-signed x509 server certificate for testing purposes" do
+ cert = csr.to_cert
+ cert.serial_number.number = cert_serial_number(options[:domain])
+ cert.not_before = yesterday
+ cert.not_after = yesterday.advance(:years => 1)
+ cert.parent = ca_root
+ cert.sign! domain_test_signing_profile
+ Util.write_file! [:commercial_cert, options[:domain]], cert.to_pem
+ Util.log "please replace this file with the real certificate you get from a CA using #{Path.relative_path([:commercial_csr, options[:domain]])}"
+ end
+
+ # Fake CA
+ unless Util.file_exists? :commercial_ca_cert
+ Util.log :using, "generated CA in place of commercial CA for testing purposes" do
+ Util.write_file! :commercial_ca_cert, Util.read_file!(:ca_cert)
+ Util.log "please also replace this file with the CA cert from the commercial authority you use."
+ end
+ end
+ end
+
+ #
+ # Return true if the given server cert has been signed by the given CA cert
+ #
+ # This does not actually validate the signature, it just checks the cert
+ # extensions.
+ #
+ def self.created_by_authority?(cert, ca=X509.ca_root)
+ authority_key_id = cert.extensions["authorityKeyIdentifier"].identifier.sub(/^keyid:/, '')
+ return authority_key_id == self.public_key_id_for_ca(ca)
+ 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 self.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.
+ #
+ def self.random_common_name(domain_name)
+ #cert_serial_number(domain_name).to_s(36)
+ SecureRandom.uuid
+ end
+
+ private
+
+ #
+ # calculate the "key id" for a root CA, that matches the value
+ # Authority Key Identifier in the x509 extensions of a cert.
+ #
+ def self.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
+
+ # prints CertificateAuthority::DistinguishedName fields
+ def self.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
+
+end; end
diff --git a/lib/leap_cli/x509/signing_profiles.rb b/lib/leap_cli/x509/signing_profiles.rb
new file mode 100644
index 00000000..56cd29c7
--- /dev/null
+++ b/lib/leap_cli/x509/signing_profiles.rb
@@ -0,0 +1,104 @@
+#
+# Signing profiles are used by CertificateAuthority in order to
+# set the correct flags when signing certificates.
+#
+
+module LeapCli; module X509
+
+ #
+ # For CA self-signing
+ #
+ def self.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 self.server_signing_profile(node)
+ {
+ "digest" => node.env.provider.ca.server_certificates.digest,
+ "extensions" => {
+ "keyUsage" => {
+ "usage" => ["digitalSignature", "keyEncipherment"]
+ },
+ "extendedKeyUsage" => {
+ "usage" => ["serverAuth", "clientAuth"]
+ },
+ "subjectAltName" => {
+ "ips" => [node.ip_address],
+ "dns_names" => node.all_dns_names
+ }
+ }
+ }
+ 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 self.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 self.client_test_signing_profile
+ {
+ "digest" => "SHA256",
+ "extensions" => {
+ "keyUsage" => {
+ "usage" => ["digitalSignature"]
+ },
+ "extendedKeyUsage" => {
+ "usage" => ["clientAuth"]
+ }
+ }
+ }
+ end
+
+end; end \ No newline at end of file
diff --git a/lib/leap_cli/x509/utils.rb b/lib/leap_cli/x509/utils.rb
new file mode 100644
index 00000000..98ff9c0b
--- /dev/null
+++ b/lib/leap_cli/x509/utils.rb
@@ -0,0 +1,26 @@
+module LeapCli; module X509
+
+ #
+ # TIME HELPERS
+ #
+ # note: we use 'yesterday' instead of 'today', because times are in UTC, and
+ # some people on the planet are behind UTC!
+ #
+
+ def self.yesterday
+ t = Time.now - 24*24*60
+ Time.utc t.year, t.month, t.day
+ end
+
+ def self.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 \ No newline at end of file
diff --git a/platform.rb b/platform.rb
index 61fb50ce..2ff0a27f 100644
--- a/platform.rb
+++ b/platform.rb
@@ -4,8 +4,8 @@
#
Leap::Platform.define do
- self.version = "0.8"
- self.compatible_cli = "1.8".."1.99"
+ self.version = "0.9"
+ self.compatible_cli = "1.9".."1.99"
#
# the facter facts that should be gathered
@@ -42,6 +42,7 @@ Leap::Platform.define do
:tag_config => 'tags/#{arg}.json',
:template_config => 'templates/#{arg}.json',
:secrets_config => 'secrets.json',
+ :cloud_config => 'cloud.json',
:node_config => 'nodes/#{arg}.json',
# input config files, environmentally scoped
@@ -65,6 +66,7 @@ Leap::Platform.define do
# output files
:facts => 'facts.json',
+ :user_dir => 'users/#{arg}',
:user_ssh => 'users/#{arg}/#{arg}_ssh.pub',
:user_pgp => 'users/#{arg}/#{arg}_pgp.pub',
:known_hosts => 'files/ssh/known_hosts',
@@ -76,6 +78,8 @@ Leap::Platform.define do
:client_ca_key => 'files/ca/client_ca.key',
:client_ca_cert => 'files/ca/client_ca.crt',
:dh_params => 'files/ca/dh.pem',
+ :acme_key => 'files/ca/lets-encrypt-account.key',
+ :acme_info => 'files/ca/lets-encrypt-account.json',
:commercial_key => 'files/cert/#{arg}.key',
:commercial_csr => 'files/cert/#{arg}.csr',
:commercial_cert => 'files/cert/#{arg}.crt',
diff --git a/provider_base/common.json b/provider_base/common.json
index 5e689109..893d5daf 100644
--- a/provider_base/common.json
+++ b/provider_base/common.json
@@ -29,12 +29,7 @@
"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"
+ "ca_cert": "= try_file :ca_cert"
},
"service_type": "internal_service",
"development": {
@@ -64,9 +59,9 @@
},
"sources": {
"apt": {
- "basic": "http://httpredir.debian.org/debian/",
+ "basic": "http://deb.debian.org/debian/",
"security": "http://security.debian.org/",
- "backports": "http://httpredir.debian.org/debian/"
+ "backports": "http://deb.debian.org/debian/"
},
"leap-mx": {
"type": "apt",
@@ -76,7 +71,7 @@
"nickserver": {
"type": "git",
"source": "https://leap.se/git/nickserver",
- "revision": "origin/version/0.8"
+ "revision": "origin/version/0.9"
},
"platform": {
"apt": {
diff --git a/provider_base/common.rb b/provider_base/common.rb
new file mode 100644
index 00000000..a8cc6717
--- /dev/null
+++ b/provider_base/common.rb
@@ -0,0 +1,72 @@
+##
+## common.rb -- evaluated (last) for every node.
+##
+## Because common.rb is evaluated last, it is good practice to only modify
+## values here if they are empty. This gives a chance for tags and services
+## to set values.
+##
+
+#
+# X509 server certificates that use our own CA
+#
+
+if self['x509.use']
+ if self['x509.cert'].nil?
+ self.set('x509.cert', lambda{file(
+ :node_x509_cert,
+ :missing => "x509 certificate for node $node. Run `leap cert update` to generate it."
+ )})
+ end
+ if self['x509.key'].nil?
+ self.set('x509.key', lambda{file(
+ :node_x509_key,
+ :missing => "x509 key for node $node. Run `leap cert update` to generate it."
+ )})
+ end
+else
+ self.set('x509.cert', nil)
+ self.set('x509.key', nil)
+end
+
+#
+# X509 server certificates that use an external CA
+#
+
+if self['x509.use_commercial']
+ domain = self['webapp.domain'] || self['domain.full_suffix']
+ if self['x509.commercial_cert'].nil?
+ self.set('x509.commercial_cert', lambda{file(
+ [:commercial_cert, domain],
+ :missing => "commercial x509 certificate for node `$node`. " +
+ "Add file $file, or run `leap cert csr %s`." % domain
+ )})
+ end
+ if self['x509.commercial_key'].nil?
+ self.set('x509.commercial_key', lambda{file(
+ [:commercial_key, domain],
+ :missing => "commercial x509 key for node `$node`. " +
+ "Add file $file, or run `leap cert csr %s`" % domain
+ )})
+ end
+
+ #
+ # the content of x509.commercial_cert might include the cert
+ # and the full CA chain, or it might just be the cert only.
+ #
+ # if it is the cert only, then we want to additionally specify
+ # 'commercial_ca_cert'. Otherwise, we leave this empty.
+ #
+ if self['x509.commercial_ca_cert'].nil?
+ self.set('x509.commercial_ca_cert', lambda{
+ if self['x509.commercial_cert'].scan(/BEGIN CERTIFICATE/).length == 1
+ try_file(:commercial_ca_cert)
+ else
+ nil
+ end
+ })
+ end
+else
+ self.set('x509.commercial_cert', nil)
+ self.set('x509.commercial_key', nil)
+ self.set('x509.commercial_ca_cert', nil)
+end
diff --git a/provider_base/provider.rb b/provider_base/provider.rb
new file mode 100644
index 00000000..40a93574
--- /dev/null
+++ b/provider_base/provider.rb
@@ -0,0 +1,5 @@
+unless ['open', 'invite', 'closed'].include?(self.enrollment_policy)
+ LeapCli.log :error, "in provider config" do
+ LeapCli.log "The value of enrollment_policy must be one of 'open', 'invite', or 'closed'."
+ end
+end \ No newline at end of file
diff --git a/provider_base/services/tor.json b/provider_base/services/tor.json
index 55d3d2ee..e80310fe 100644
--- a/provider_base/services/tor.json
+++ b/provider_base/services/tor.json
@@ -9,7 +9,7 @@
"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"
+ "address": "=> tor.hidden_service.active && onion_address(:node_tor_pub_key)"
}
}
}
diff --git a/provider_base/services/webapp.json b/provider_base/services/webapp.json
index b1d2ca59..feca9524 100644
--- a/provider_base/services/webapp.json
+++ b/provider_base/services/webapp.json
@@ -19,6 +19,7 @@
"allow_unlimited_certs": "= provider.service.allow_unlimited_bandwidth",
"allow_anonymous_certs": "= provider.service.allow_anonymous",
"allow_registration": "= provider.service.allow_registration",
+ "invite_required": "= provider.enrollment_policy == 'invite'",
"default_service_level": "= provider.service.default_service_level",
"service_levels": "= service_levels()",
"secret_token": "= secret :webapp_secret_token",
diff --git a/provider_base/tags/vm.json b/provider_base/tags/vm.json
new file mode 100644
index 00000000..7a73a41b
--- /dev/null
+++ b/provider_base/tags/vm.json
@@ -0,0 +1,2 @@
+{
+} \ No newline at end of file
diff --git a/puppet/manifests/site.pp b/puppet/manifests/site.pp
index ecda4012..3bf6a5c1 100644
--- a/puppet/manifests/site.pp
+++ b/puppet/manifests/site.pp
@@ -1,60 +1,62 @@
-# 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 = 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
+node default {
+ # 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'],
+ }
+
+ # 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 b/puppet/modules/apache
deleted file mode 160000
-Subproject 117bed9a9263c21d253d86b667eb165948efdc2
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/.gitrepo b/puppet/modules/apache/.gitrepo
new file mode 100644
index 00000000..fdeb3d77
--- /dev/null
+++ b/puppet/modules/apache/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_apache
+ branch = master
+ commit = 415e9504f99dca3ccaa4dfd389dde24ad9d0e01c
+ parent = f2019755fd724fb1020cb2d97cdf82b751450ebc
+ cmdver = 0.3.0
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 b/puppet/modules/apt
deleted file mode 160000
-Subproject 33c61e8df59db1abbed379a9e9790946060a8f1
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/.gitrepo b/puppet/modules/apt/.gitrepo
new file mode 100644
index 00000000..1dd57eb5
--- /dev/null
+++ b/puppet/modules/apt/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_apt
+ branch = master
+ commit = 33c61e8df59db1abbed379a9e9790946060a8f1e
+ parent = 4ccae8700fb136bfbc6b7ef7bb0ab482e632139f
+ cmdver = 0.3.0
diff --git a/Gemfile b/puppet/modules/apt/Gemfile
index 8925a904..8925a904 100644
--- a/Gemfile
+++ b/puppet/modules/apt/Gemfile
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 b/puppet/modules/augeas
deleted file mode 160000
-Subproject 58ab2b90c52a5d951fa41596827bc3b6f52310e
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/modules/augeas/.gitrepo b/puppet/modules/augeas/.gitrepo
new file mode 100644
index 00000000..b33003dd
--- /dev/null
+++ b/puppet/modules/augeas/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_augeas
+ branch = master
+ commit = 27e33591776f0226f78877df350d6a52995265d8
+ parent = 4a11e48e397f1a7eb4c68a1dd1f9e3c5a11352f8
+ cmdver = 0.3.0
diff --git a/puppet/modules/augeas/.puppet-lint.rc b/puppet/modules/augeas/.puppet-lint.rc
new file mode 100644
index 00000000..d8f5c59e
--- /dev/null
+++ b/puppet/modules/augeas/.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/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/puppet/modules/augeas/spec/.rspec b/puppet/modules/augeas/spec/.rspec
new file mode 100644
index 00000000..91cd6427
--- /dev/null
+++ b/puppet/modules/augeas/spec/.rspec
@@ -0,0 +1,6 @@
+--format
+s
+--colour
+--loadby
+mtime
+--backtrace
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 b/puppet/modules/backupninja
deleted file mode 160000
-Subproject 497513547be79f9d3c8e96f1650ec43ee634b27
diff --git a/puppet/modules/backupninja/.gitrepo b/puppet/modules/backupninja/.gitrepo
new file mode 100644
index 00000000..ea7862f0
--- /dev/null
+++ b/puppet/modules/backupninja/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_backupninja
+ branch = master
+ commit = 5268a87c329f895017f8ea6c6abc377a4f9a6a77
+ parent = 1e1e25286b64790141c9627f81b50f579b13b719
+ cmdver = 0.3.0
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 b/puppet/modules/bundler
deleted file mode 160000
-Subproject b4a4a8434616247156e59b860b47cc6256ead8d
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/.gitrepo b/puppet/modules/bundler/.gitrepo
new file mode 100644
index 00000000..4ec4aacc
--- /dev/null
+++ b/puppet/modules/bundler/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_bundler
+ branch = master
+ commit = bacec3e072649be4ade56f7df8506b46ae9c5166
+ parent = 4aff06cc2fecc0b59728d7fc825fb36394b847b7
+ cmdver = 0.3.0
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 b/puppet/modules/check_mk
deleted file mode 160000
-Subproject aa02571537af90ac73309e6e216c9417802548c
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/.gitrepo b/puppet/modules/check_mk/.gitrepo
new file mode 100644
index 00000000..05058447
--- /dev/null
+++ b/puppet/modules/check_mk/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_check_mk
+ branch = master
+ commit = aa02571537af90ac73309e6e216c9417802548c3
+ parent = a75fea409bf8e62e55ba341672c202aab5fa480e
+ cmdver = 0.3.0
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/clamav-daemon.path b/puppet/modules/clamav/files/clamav-daemon.path
new file mode 100644
index 00000000..6e57d187
--- /dev/null
+++ b/puppet/modules/clamav/files/clamav-daemon.path
@@ -0,0 +1,12 @@
+[Unit]
+Description=Path Activation for Clam AntiVirus userspace daemon
+Documentation=man:clamd(8) man:clamd.conf(5) http://www.clamav.net/lang/en/doc/
+
+[Path]
+# Check and wait for database existence before starting up
+PathExistsGlob=/var/lib/clamav/main.{c[vl]d,inc}
+PathExistsGlob=/var/lib/clamav/daily.{c[vl]d,inc}
+
+[Install]
+WantedBy=sockets.target
+
diff --git a/puppet/modules/clamav/manifests/daemon.pp b/puppet/modules/clamav/manifests/daemon.pp
index 2e13a8fb..322cb892 100644
--- a/puppet/modules/clamav/manifests/daemon.pp
+++ b/puppet/modules/clamav/manifests/daemon.pp
@@ -1,5 +1,6 @@
# deploy clamav daemon
class clamav::daemon {
+ include clamav::daemon::activation
$domain_hash = hiera('domain')
$domain = $domain_hash['full_suffix']
@@ -15,7 +16,6 @@ class clamav::daemon {
pattern => '/usr/sbin/clamd',
enable => true,
hasrestart => true,
- subscribe => File['/etc/default/clamav-daemon'],
require => Package['clamav-daemon'];
}
@@ -25,19 +25,23 @@ class clamav::daemon {
mode => '0750',
owner => clamav,
group => postfix,
- require => [Package['postfix'], Package['clamav-daemon']];
+ require => [Package['postfix'], Package['clamav-daemon']],
+ notify => Service['clamav-daemon'];
'/var/lib/clamav':
mode => '0755',
owner => clamav,
group => clamav,
- require => Package['clamav-daemon'];
+ require => Package['clamav-daemon'],
+ notify => Service['clamav-daemon'];
'/etc/default/clamav-daemon':
- source => 'puppet:///modules/clamav/clamav-daemon_default',
- mode => '0644',
- owner => root,
- group => root;
+ source => 'puppet:///modules/clamav/clamav-daemon_default',
+ mode => '0644',
+ owner => root,
+ group => root,
+ require => Package['clamav-daemon'],
+ notify => Service['clamav-daemon'];
# this file contains additional domains that we want the clamav
# phishing process to look for (our domain)
@@ -46,7 +50,8 @@ class clamav::daemon {
mode => '0644',
owner => clamav,
group => clamav,
- require => Package['clamav-daemon'];
+ require => Package['clamav-daemon'],
+ notify => Service['clamav-daemon'];
}
file_line {
diff --git a/puppet/modules/clamav/manifests/daemon/activation.pp b/puppet/modules/clamav/manifests/daemon/activation.pp
new file mode 100644
index 00000000..09c1e55e
--- /dev/null
+++ b/puppet/modules/clamav/manifests/daemon/activation.pp
@@ -0,0 +1,24 @@
+# ensure clamav starts after the definitions are downloaded
+# needed because sometimes clamd cannot get started by freshclam,
+# see https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=827909
+class clamav::daemon::activation {
+
+ file { '/etc/systemd/system/clamav-daemon.path':
+ source => 'puppet:///modules/clamav/clamav-daemon.path',
+ mode => '0644',
+ owner => root,
+ group => root,
+ notify => [ Exec['systemctl-daemon-reload'], Systemd::Enable['clamav-daemon.path'] ]
+ }
+
+ systemd::enable { 'clamav-daemon.path':
+ require => Exec['systemctl-daemon-reload'],
+ notify => Exec['start_clamd_path_monitor']
+ }
+
+ exec { 'start_clamd_path_monitor':
+ command => '/bin/systemctl start clamav-daemon.path',
+ refreshonly => true,
+ before => Service['freshclam']
+ }
+}
diff --git a/puppet/modules/common b/puppet/modules/common
deleted file mode 160000
-Subproject ae149624f9bc551865b93b9b7155af2de8deeb7
diff --git a/puppet/modules/common/.gitrepo b/puppet/modules/common/.gitrepo
new file mode 100644
index 00000000..7d45d07b
--- /dev/null
+++ b/puppet/modules/common/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_common
+ branch = master
+ commit = ae149624f9bc551865b93b9b7155af2de8deeb71
+ parent = 1f231f09a9b6911b2ca57ac82235c6028922d54f
+ cmdver = 0.3.0
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 b/puppet/modules/concat
deleted file mode 160000
-Subproject abce1280e07b544d8455f1572dd870bbd2f1489
diff --git a/puppet/modules/concat/.gitrepo b/puppet/modules/concat/.gitrepo
new file mode 100644
index 00000000..89eb24da
--- /dev/null
+++ b/puppet/modules/concat/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_concat
+ branch = master
+ commit = abce1280e07b544d8455f1572dd870bbd2f14892
+ parent = da37dd95c39f3f100020164473eed53a317fb53f
+ cmdver = 0.3.0
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 b/puppet/modules/couchdb
deleted file mode 160000
-Subproject 40d2289f8e10625cd45fdccdf492b5fb6490e66
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/.gitrepo b/puppet/modules/couchdb/.gitrepo
new file mode 100644
index 00000000..d72ab390
--- /dev/null
+++ b/puppet/modules/couchdb/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_couchdb
+ branch = master
+ commit = 76ff149a095023611c05bbb00157d06f87b07c05
+ parent = 81210aea5cf136194598e7a399ce307ecbe088f1
+ cmdver = 0.3.0
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 b/puppet/modules/git
deleted file mode 160000
-Subproject ba5dd8d5c8e09d521ff49f1ebc753601e449f82
diff --git a/puppet/modules/git/.gitrepo b/puppet/modules/git/.gitrepo
new file mode 100644
index 00000000..5b10e73e
--- /dev/null
+++ b/puppet/modules/git/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_git
+ branch = master
+ commit = ba5dd8d5c8e09d521ff49f1ebc753601e449f828
+ parent = 984375bab6546a7ef1e716402468a2f4cb6e1925
+ cmdver = 0.3.0
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 b/puppet/modules/haproxy
deleted file mode 160000
-Subproject af322a73c013f80a958ab7d5d31d0c75cf6d052
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/.gitrepo b/puppet/modules/haproxy/.gitrepo
new file mode 100644
index 00000000..ed92831a
--- /dev/null
+++ b/puppet/modules/haproxy/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_haproxy
+ branch = master
+ commit = af322a73c013f80a958ab7d5d31d0c75cf6d0523
+ parent = 04279dd8d1390d61d696d2c14817199304ccd4d8
+ cmdver = 0.3.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/lsb b/puppet/modules/lsb
deleted file mode 160000
-Subproject 3742c1a00c5602154a81834443ec5b0ca32c4ca
diff --git a/puppet/modules/lsb/.gitrepo b/puppet/modules/lsb/.gitrepo
new file mode 100644
index 00000000..640efc53
--- /dev/null
+++ b/puppet/modules/lsb/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_lsb
+ branch = master
+ commit = bac64e7595a6d4f8d409b024a26bddb1c06188d6
+ parent = 2e384e68fb867d8ba7178c4398e35653ab567538
+ cmdver = 0.3.0
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 b/puppet/modules/nagios
deleted file mode 160000
-Subproject 68dab01a85996e14efcccf856b623a2caf25782
diff --git a/puppet/modules/nagios/.gitrepo b/puppet/modules/nagios/.gitrepo
new file mode 100644
index 00000000..b832fa2a
--- /dev/null
+++ b/puppet/modules/nagios/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_nagios
+ branch = master
+ commit = e6fee3c731f68ccf8b6add8ada2162c7ad2b8407
+ parent = f5775156d8d8800247b8917ab6212c7eed16a124
+ cmdver = 0.3.0
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 b/puppet/modules/ntp
deleted file mode 160000
-Subproject 27f2bc72110b1001233eb0907aa07e06cdf3319
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/.gitrepo b/puppet/modules/ntp/.gitrepo
new file mode 100644
index 00000000..dd7d7267
--- /dev/null
+++ b/puppet/modules/ntp/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_ntp
+ branch = master
+ commit = 8a554ab4b00e25f52a337c4c974fd89f44042957
+ parent = 5552d592f9332e55bcf2a5d2c6b0258b8130c26b
+ cmdver = 0.3.0
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/openvpn b/puppet/modules/openvpn
deleted file mode 160000
-Subproject 25f1fe8d813f6128068d890a40f5e24be78fb47
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..12c29e7d
--- /dev/null
+++ b/puppet/modules/openvpn/.gitignore
@@ -0,0 +1,2 @@
+pkg
+spec/fixtures
diff --git a/puppet/modules/openvpn/.gitrepo b/puppet/modules/openvpn/.gitrepo
new file mode 100644
index 00000000..54c861da
--- /dev/null
+++ b/puppet/modules/openvpn/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_openvpn
+ branch = master
+ commit = ba7ec7abd25cd4c5031e11cd3ae17872ef31b24b
+ parent = d6719731dce8ee7e048a16a447a426abcaa44f24
+ cmdver = 0.3.0
diff --git a/puppet/modules/openvpn/Modulefile b/puppet/modules/openvpn/Modulefile
new file mode 100644
index 00000000..55b6eba0
--- /dev/null
+++ b/puppet/modules/openvpn/Modulefile
@@ -0,0 +1,11 @@
+name 'luxflux-openvpn'
+version '1.0.2'
+source 'https://github.com/luxflux/puppet-openvpn'
+author 'luxflux'
+license 'UNKNOWN'
+summary 'UNKNOWN'
+description 'UNKNOWN'
+project_page 'UNKNOWN'
+
+## Add dependencies, if any:
+dependency 'ripienaar/concat'
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..d2a1f67b
--- /dev/null
+++ b/puppet/modules/openvpn/Readme.markdown
@@ -0,0 +1,123 @@
+# OpenVPN Puppet module
+
+OpenVPN module for puppet including client config/cert creation (tarball to download)
+
+## Dependencies
+ - [puppet-concat](https://github.com/ripienaar/puppet-concat)
+
+## Supported OS
+ - Debian Squeeze (should, as it works on Ubuntu Lucid)
+ - Ubuntu 10.4, 12.04 (other untested)
+ - CentOS
+
+## Example
+
+ # add a server instance
+ openvpn::server {
+ "server1":
+ country => "CH",
+ province => "ZH",
+ city => "Winterthur",
+ organization => "example.org",
+ email => "root@example.org";
+ }
+
+ # configure server
+ openvpn::option {
+ "dev server1":
+ key => "dev",
+ value => "tun0",
+ server => "server1";
+ "script-security server1":
+ key => "script-security",
+ value => "3",
+ server => "server1";
+ "daemon server1":
+ key => "daemon",
+ server => "server1";
+ "keepalive server1":
+ key => "keepalive",
+ value => "10 60",
+ server => "server1";
+ "ping-timer-rem server1":
+ key => "ping-timer-rem",
+ server => "server1";
+ "persist-tun server1":
+ key => "persist-tun",
+ server => "server1";
+ "persist-key server1":
+ key => "persist-key",
+ server => "server1";
+ "proto server1":
+ key => "proto",
+ value => "tcp-server",
+ server => "server1";
+ "cipher server1":
+ key => "cipher",
+ value => "BF-CBC",
+ server => "server1";
+ "local server1":
+ key => "local",
+ value => $ipaddress,
+ server => "server1";
+ "tls-server server1":
+ key => "tls-server",
+ server => "server1";
+ "server server1":
+ key => "server",
+ value => "10.10.10.0 255.255.255.0",
+ server => "server1";
+ "lport server1":
+ key => "lport",
+ value => "1194",
+ server => "server1";
+ "management server1":
+ key => "management",
+ value => "/var/run/openvpn-server1.sock unix",
+ server => "server1";
+ "comp-lzo server1":
+ key => "comp-lzo",
+ server => "server1";
+ "topology server1":
+ key => "topology",
+ value => "subnet",
+ server => "server1";
+ "client-to-client server1":
+ key => "client-to-client",
+ server => "server1";
+ }
+
+
+ # define clients
+ openvpn::client {
+ [ "client1.example.org", "client2.example.org" ]:
+ server => "server1";
+ }
+
+ # add options to the client-config-dir file
+ openvpn::option {
+ "iroute server1 client1.example.org home network":
+ key => "iroute",
+ value => "192.168.0.0 255.255.255.0",
+ client => "client1.example.org",
+ server => "server1",
+ csc => true;
+ }
+
+ # add an option to the client config
+ openvpn::option {
+ "ifconfig server1 client2.example.org":
+ key => "ifconfig-push",
+ value => "10.10.10.2 255.255.255.0",
+ client => "client2.example.org",
+ server => "server1";
+ }
+
+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:
+* [@jlk](https://github.com/jlk)
+* [@jlambert121](https://github.com/jlambert121)
diff --git a/puppet/modules/openvpn/manifests/client.pp b/puppet/modules/openvpn/manifests/client.pp
new file mode 100644
index 00000000..ed11b3a9
--- /dev/null
+++ b/puppet/modules/openvpn/manifests/client.pp
@@ -0,0 +1,142 @@
+# client.pp
+
+define openvpn::client($server, $remote_host = $::fqdn) {
+ 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',
+ require => Exec["generate server cert ${server}"];
+ }
+
+ file {
+ "/etc/openvpn/${server}/download-configs/${name}":
+ ensure => directory,
+ require => File["/etc/openvpn/${server}/download-configs"];
+
+ "/etc/openvpn/${server}/download-configs/${name}/keys":
+ ensure => directory,
+ require => File["/etc/openvpn/${server}/download-configs/${name}"];
+
+ "/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}"],
+ File["/etc/openvpn/${server}/download-configs/${name}/keys"] ];
+
+ "/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}"],
+ File["/etc/openvpn/${server}/download-configs/${name}/keys"] ];
+
+ "/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}"],
+ File["/etc/openvpn/${server}/download-configs/${name}/keys"] ];
+ }
+
+
+ openvpn::option {
+ "ca ${server} with ${name}":
+ key => 'ca',
+ value => 'keys/ca.crt',
+ client => $name,
+ server => $server;
+ "cert ${server} with ${name}":
+ key => 'cert',
+ value => "keys/${name}.crt",
+ client => $name,
+ server => $server;
+ "key ${server} with ${name}":
+ key => 'key',
+ value => "keys/${name}.key",
+ client => $name,
+ server => $server;
+ "client ${server} with ${name}":
+ key => 'client',
+ client => $name,
+ server => $server;
+ "dev ${server} with ${name}":
+ key => 'dev',
+ value => 'tun',
+ client => $name,
+ server => $server;
+ "proto ${server} with ${name}":
+ key => 'proto',
+ value => 'tcp',
+ client => $name,
+ server => $server;
+ "remote ${server} with ${name}":
+ key => 'remote',
+ value => "${remote_host} 1194",
+ client => $name,
+ server => $server;
+ "resolv-retry ${server} with ${name}":
+ key => 'resolv-retry',
+ value => 'infinite',
+ client => $name,
+ server => $server;
+ "nobind ${server} with ${name}":
+ key => 'nobind',
+ client => $name,
+ server => $server;
+ "persist-key ${server} with ${name}":
+ key => 'persist-key',
+ client => $name,
+ server => $server;
+ "persist-tun ${server} with ${name}":
+ key => 'persist-tun',
+ client => $name,
+ server => $server;
+ "mute-replay-warnings ${server} with ${name}":
+ key => 'mute-replay-warnings',
+ client => $name,
+ server => $server;
+ "ns-cert-type ${server} with ${name}":
+ key => 'ns-cert-type',
+ value => 'server',
+ client => $name,
+ server => $server;
+ "comp-lzo ${server} with ${name}":
+ key => 'comp-lzo',
+ client => $name,
+ server => $server;
+ "verb ${server} with ${name}":
+ key => 'verb',
+ value => '3',
+ client => $name,
+ server => $server;
+ "mute ${server} with ${name}":
+ key => 'mute',
+ value => '20',
+ client => $name,
+ server => $server;
+ }
+
+ 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"] ];
+ }
+
+
+ concat {
+ [ "/etc/openvpn/${server}/client-configs/${name}", "/etc/openvpn/${server}/download-configs/${name}/${name}.conf" ]:
+ owner => root,
+ group => root,
+ mode => 644,
+ warn => true,
+ force => true,
+ notify => Exec["tar the thing ${server} with ${name}"],
+ require => [ File['/etc/openvpn'], File["/etc/openvpn/${server}/download-configs/${name}"] ];
+ }
+
+}
diff --git a/puppet/modules/openvpn/manifests/init.pp b/puppet/modules/openvpn/manifests/init.pp
new file mode 100644
index 00000000..a3dd70c0
--- /dev/null
+++ b/puppet/modules/openvpn/manifests/init.pp
@@ -0,0 +1,45 @@
+# openvpn.pp
+
+class openvpn {
+ package {
+ 'openvpn':
+ ensure => installed;
+ }
+ service {
+ 'openvpn':
+ ensure => running,
+ enable => true,
+ hasrestart => true,
+ hasstatus => true,
+ require => Exec['concat_/etc/default/openvpn'];
+ }
+ file {
+ '/etc/openvpn':
+ ensure => directory,
+ require => Package['openvpn'];
+ }
+ file {
+ '/etc/openvpn/keys':
+ ensure => directory,
+ require => File['/etc/openvpn'];
+ }
+
+ include concat::setup
+
+ 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;
+ }
+
+}
diff --git a/puppet/modules/openvpn/manifests/option.pp b/puppet/modules/openvpn/manifests/option.pp
new file mode 100644
index 00000000..eb3d5a72
--- /dev/null
+++ b/puppet/modules/openvpn/manifests/option.pp
@@ -0,0 +1,24 @@
+# option.pp
+
+define openvpn::option($key, $server, $value = '', $client = '', $csc = false) {
+ $content = $value ? {
+ '' => $key,
+ default => "${key} ${value}"
+ }
+
+ if $client == '' {
+ $path = "/etc/openvpn/${server}.conf"
+ } else {
+ if $csc {
+ $path = "/etc/openvpn/${server}/client-configs/${client}"
+ } else {
+ $path = "/etc/openvpn/${server}/download-configs/${client}/${client}.conf"
+ }
+ }
+
+ concat::fragment {
+ "openvpn.${server}.${client}.${name}":
+ target => $path,
+ content => "${content}\n";
+ }
+}
diff --git a/puppet/modules/openvpn/manifests/server.pp b/puppet/modules/openvpn/manifests/server.pp
new file mode 100644
index 00000000..bfcaad83
--- /dev/null
+++ b/puppet/modules/openvpn/manifests/server.pp
@@ -0,0 +1,153 @@
+# server.pp
+
+define openvpn::server($country, $province, $city, $organization, $email) {
+ include openvpn
+
+ $easyrsa_source = $::osfamily ? {
+ 'RedHat' => '/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
+ }
+
+ file {
+ "/etc/openvpn/${name}":
+ ensure => directory,
+ require => Package['openvpn'];
+ }
+ file {
+ "/etc/openvpn/${name}/client-configs":
+ ensure => directory,
+ require => File["/etc/openvpn/${name}"];
+ "/etc/openvpn/${name}/download-configs":
+ ensure => directory,
+ require => File["/etc/openvpn/${name}"];
+ }
+
+ openvpn::option {
+ "client-config-dir ${name}":
+ key => 'client-config-dir',
+ value => "/etc/openvpn/${name}/client-configs",
+ server => $name,
+ require => File["/etc/openvpn/${name}"];
+ "mode ${name}":
+ key => 'mode',
+ value => 'server',
+ server => $name;
+ }
+
+ exec {
+ "copy easy-rsa to openvpn config folder ${name}":
+ command => "/bin/cp -r ${easyrsa_source} /etc/openvpn/${name}/easy-rsa",
+ creates => "/etc/openvpn/${name}/easy-rsa",
+ notify => Exec['fix_easyrsa_file_permissions'],
+ require => File["/etc/openvpn/${name}"];
+ }
+ exec {
+ 'fix_easyrsa_file_permissions':
+ 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 $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}"];
+ }
+
+ openvpn::option {
+ "ca ${name}":
+ key => 'ca',
+ value => "/etc/openvpn/${name}/keys/ca.crt",
+ require => Exec["initca ${name}"],
+ server => $name;
+ "cert ${name}":
+ key => 'cert',
+ value => "/etc/openvpn/${name}/keys/server.crt",
+ require => Exec["generate server cert ${name}"],
+ server => $name;
+ "key ${name}":
+ key => 'key',
+ value => "/etc/openvpn/${name}/keys/server.key",
+ require => Exec["generate server cert ${name}"],
+ server => $name;
+ "dh ${name}":
+ key => 'dh',
+ value => "/etc/openvpn/${name}/keys/dh1024.pem",
+ require => Exec["generate dh param ${name}"],
+ server => $name;
+
+ "proto ${name}":
+ key => 'proto',
+ value => 'tcp',
+ require => Exec["generate dh param ${name}"],
+ server => $name;
+
+ "comp-lzo ${name}":
+ key => 'comp-lzo',
+ require => Exec["generate dh param ${name}"],
+ server => $name;
+ }
+
+ concat::fragment {
+ "openvpn.default.autostart.${name}":
+ content => "AUTOSTART=\"\$AUTOSTART ${name}\"\n",
+ target => '/etc/default/openvpn',
+ order => 10;
+ }
+
+ concat {
+ "/etc/openvpn/${name}.conf":
+ owner => root,
+ group => root,
+ mode => 644,
+ warn => true,
+ require => File['/etc/openvpn'],
+ notify => Service['openvpn'];
+ }
+
+}
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..cdfdea19
--- /dev/null
+++ b/puppet/modules/openvpn/spec/classes/openvpn_init_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe 'openvpn', :type => :class do
+
+ let (:facts) { { :concat_basedir => '/var/lib/puppet/concat' } }
+
+ it { should create_class('openvpn') }
+ it { should contain_class('concat::setup') }
+ it { should contain_package('openvpn') }
+ it { should contain_service('openvpn').with(
+ 'ensure' => 'running',
+ 'enable' => true
+ ) }
+
+ it { should contain_file('/etc/openvpn').with('ensure' => 'directory') }
+ it { should contain_file('/etc/openvpn/keys').with('ensure' => 'directory') }
+
+ it { should contain_concat__fragment('openvpn.default.header') }
+
+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..da71d63d
--- /dev/null
+++ b/puppet/modules/openvpn/spec/defines/openvpn_client_spec.rb
@@ -0,0 +1,116 @@
+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' } }
+
+ 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'
+ ) }
+
+ it { should contain_openvpn__option('ca test_server with test_client').with(
+ 'server' => 'test_server',
+ 'client' => 'test_client',
+ 'key' => 'ca',
+ 'value' => 'keys/ca.crt'
+ )}
+ it { should contain_openvpn__option('cert test_server with test_client').with(
+ 'server' => 'test_server',
+ 'client' => 'test_client',
+ 'key' => 'cert',
+ 'value' => 'keys/test_client.crt'
+ )}
+ it { should contain_openvpn__option('key test_server with test_client').with(
+ 'server' => 'test_server',
+ 'client' => 'test_client',
+ 'key' => 'key',
+ 'value' => 'keys/test_client.key'
+ )}
+ it { should contain_openvpn__option('client test_server with test_client').with(
+ 'server' => 'test_server',
+ 'client' => 'test_client',
+ 'key' => 'client'
+ )}
+ it { should contain_openvpn__option('dev test_server with test_client').with(
+ 'server' => 'test_server',
+ 'client' => 'test_client',
+ 'key' => 'dev',
+ 'value' => 'tun'
+ )}
+ it { should contain_openvpn__option('proto test_server with test_client').with(
+ 'server' => 'test_server',
+ 'client' => 'test_client',
+ 'key' => 'proto',
+ 'value' => 'tcp'
+ )}
+ it { should contain_openvpn__option('remote test_server with test_client').with(
+ 'server' => 'test_server',
+ 'client' => 'test_client',
+ 'key' => 'remote',
+ 'value' => 'somehost 1194'
+ )}
+ it { should contain_openvpn__option('resolv-retry test_server with test_client').with(
+ 'server' => 'test_server',
+ 'client' => 'test_client',
+ 'key' => 'resolv-retry',
+ 'value' => 'infinite'
+ )}
+ it { should contain_openvpn__option('nobind test_server with test_client').with(
+ 'server' => 'test_server',
+ 'client' => 'test_client',
+ 'key' => 'nobind'
+ )}
+ it { should contain_openvpn__option('persist-key test_server with test_client').with(
+ 'server' => 'test_server',
+ 'client' => 'test_client',
+ 'key' => 'persist-key'
+ )}
+ it { should contain_openvpn__option('persist-tun test_server with test_client').with(
+ 'server' => 'test_server',
+ 'client' => 'test_client',
+ 'key' => 'persist-tun'
+ )}
+ it { should contain_openvpn__option('mute-replay-warnings test_server with test_client').with(
+ 'server' => 'test_server',
+ 'client' => 'test_client',
+ 'key' => 'mute-replay-warnings'
+ )}
+ it { should contain_openvpn__option('ns-cert-type test_server with test_client').with(
+ 'server' => 'test_server',
+ 'client' => 'test_client',
+ 'key' => 'ns-cert-type',
+ 'value' => 'server'
+ )}
+ it { should contain_openvpn__option('comp-lzo test_server with test_client').with(
+ 'server' => 'test_server',
+ 'client' => 'test_client',
+ 'key' => 'comp-lzo'
+ )}
+ it { should contain_openvpn__option('verb test_server with test_client').with(
+ 'server' => 'test_server',
+ 'client' => 'test_client',
+ 'key' => 'verb',
+ 'value' => '3'
+ )}
+ it { should contain_openvpn__option('mute test_server with test_client').with(
+ 'server' => 'test_server',
+ 'client' => 'test_client',
+ 'key' => 'mute',
+ 'value' => '20'
+ )}
+end
diff --git a/puppet/modules/openvpn/spec/defines/openvpn_option_spec.rb b/puppet/modules/openvpn/spec/defines/openvpn_option_spec.rb
new file mode 100644
index 00000000..a2d1661d
--- /dev/null
+++ b/puppet/modules/openvpn/spec/defines/openvpn_option_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+
+describe 'openvpn::option', :type => :define do
+
+ let(:title) { 'test_param' }
+
+ context "when key => 'test_key', server => 'test_server'" do
+ let(:params) { { 'key' => 'test_key', 'server' => 'test_server' } }
+
+ it { should contain_concat__fragment('openvpn.test_server..test_param').with(
+ 'target' => '/etc/openvpn/test_server.conf',
+ 'content' => "test_key\n"
+ ) }
+ end
+
+ context "when key => 'test_key', value => 'test_value', server => 'test_server'" do
+ let(:params) { { 'key' => 'test_key', 'value' => 'test_value', 'server' => 'test_server' } }
+
+ it { should contain_concat__fragment('openvpn.test_server..test_param').with(
+ 'target' => '/etc/openvpn/test_server.conf',
+ 'content' => "test_key test_value\n"
+ ) }
+ end
+
+ context "when key => 'test_key', server => 'test_server', client => 'test_client'" do
+ let(:params) { { 'key' => 'test_key', 'server' => 'test_server', 'client' => 'test_client' } }
+
+ it { should contain_concat__fragment('openvpn.test_server.test_client.test_param').with(
+ 'target' => '/etc/openvpn/test_server/download-configs/test_client/test_client.conf',
+ 'content' => "test_key\n"
+ ) }
+ end
+
+ context "when key => 'test_key', server => 'test_server', client => 'test_client', csc => true" do
+ let(:params) { { 'key' => 'test_key', 'server' => 'test_server', 'client' => 'test_client', 'csc' => 'true' } }
+
+ it { should contain_concat__fragment('openvpn.test_server.test_client.test_param').with(
+ 'target' => '/etc/openvpn/test_server/client-configs/test_client',
+ 'content' => "test_key\n"
+ ) }
+ 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..1032302e
--- /dev/null
+++ b/puppet/modules/openvpn/spec/defines/openvpn_server_spec.rb
@@ -0,0 +1,109 @@
+require 'spec_helper'
+
+describe 'openvpn::server', :type => :define do
+
+ let(:title) { 'test_server' }
+ let(:params) { {
+ 'country' => 'CO',
+ 'province' => 'ST',
+ 'city' => 'Some City',
+ 'organization' => 'example.org',
+ 'email' => 'testemail@example.org'
+ } }
+
+ let (:facts) { { :concat_basedir => '/var/lib/puppet/concat' } }
+
+ # 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'
+ )}
+
+ it { should contain_concat__fragment('openvpn.default.autostart.test_server').with(
+ 'content' => "AUTOSTART=\"$AUTOSTART test_server\"\n",
+ 'target' => '/etc/default/openvpn'
+ )}
+
+ # 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') }
+
+ # Options that should be set
+ it { should contain_openvpn__option('client-config-dir test_server').with(
+ 'server' => 'test_server',
+ 'key' => 'client-config-dir',
+ 'value' => '/etc/openvpn/test_server/client-configs'
+ )}
+ it { should contain_openvpn__option('mode test_server').with(
+ 'server' => 'test_server',
+ 'key' => 'mode',
+ 'value' => 'server'
+ )}
+ it { should contain_openvpn__option('ca test_server').with(
+ 'server' => 'test_server',
+ 'key' => 'ca',
+ 'value' => '/etc/openvpn/test_server/keys/ca.crt'
+ )}
+ it { should contain_openvpn__option('cert test_server').with(
+ 'server' => 'test_server',
+ 'key' => 'cert',
+ 'value' => '/etc/openvpn/test_server/keys/server.crt'
+ )}
+ it { should contain_openvpn__option('key test_server').with(
+ 'server' => 'test_server',
+ 'key' => 'key',
+ 'value' => '/etc/openvpn/test_server/keys/server.key'
+ )}
+ it { should contain_openvpn__option('dh test_server').with(
+ 'server' => 'test_server',
+ 'key' => 'dh',
+ 'value' => '/etc/openvpn/test_server/keys/dh1024.pem'
+ )}
+ it { should contain_openvpn__option('proto test_server').with(
+ 'server' => 'test_server',
+ 'key' => 'proto',
+ 'value' => 'tcp'
+ )}
+ it { should contain_openvpn__option('comp-lzo test_server').with(
+ 'server' => 'test_server',
+ 'key' => 'comp-lzo'
+ )}
+
+ context "when RedHat based machine" do
+ 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'
+ )}
+
+ end
+
+ context "when Debian based machine" do
+ 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'
+ )}
+
+ 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/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/vars.erb b/puppet/modules/openvpn/templates/vars.erb
new file mode 100644
index 00000000..de988f45
--- /dev/null
+++ b/puppet/modules/openvpn/templates/vars.erb
@@ -0,0 +1,69 @@
+# 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/passenger b/puppet/modules/passenger
deleted file mode 160000
-Subproject d1b46de84acf4d9e3582b64e019935fb1125f9b
diff --git a/puppet/modules/passenger/.gitrepo b/puppet/modules/passenger/.gitrepo
new file mode 100644
index 00000000..7a402ad5
--- /dev/null
+++ b/puppet/modules/passenger/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_passenger
+ branch = master
+ commit = 47fca117b594d30aa29d33f8d8846eeec0a88d5f
+ parent = 95d95925e53ec98f3f5868479328a69449de3ca7
+ cmdver = 0.3.0
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 b/puppet/modules/postfix
deleted file mode 160000
-Subproject cce918f784ebf8a8875f43c79bc3a1f39ab9456
diff --git a/puppet/modules/postfix/.gitrepo b/puppet/modules/postfix/.gitrepo
new file mode 100644
index 00000000..dfa4389c
--- /dev/null
+++ b/puppet/modules/postfix/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_postfix
+ branch = master
+ commit = cce918f784ebf8a8875f43c79bc3a1f39ab9456b
+ parent = d8a8d30b04d34387f309d9f5b7afdbcad01f7cbc
+ cmdver = 0.3.0
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/resolvconf b/puppet/modules/resolvconf
deleted file mode 160000
-Subproject c7eca077fdda063edc96d3bea02c4774569e4b1
diff --git a/puppet/modules/resolvconf/.gitrepo b/puppet/modules/resolvconf/.gitrepo
new file mode 100644
index 00000000..3359b659
--- /dev/null
+++ b/puppet/modules/resolvconf/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_resolvconf
+ branch = master
+ commit = c7eca077fdda063edc96d3bea02c4774569e4b10
+ parent = 6209061fd112fed1715676abb7b6ae4697f21d83
+ cmdver = 0.3.0
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 b/puppet/modules/rsyslog
deleted file mode 160000
-Subproject b8ef11c23949d12732ad5cdaebb3023ff39a297
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/.gitrepo b/puppet/modules/rsyslog/.gitrepo
new file mode 100644
index 00000000..fa9db13d
--- /dev/null
+++ b/puppet/modules/rsyslog/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_rsyslog
+ branch = master
+ commit = b8ef11c23949d12732ad5cdaebb3023ff39a297a
+ parent = 850a14b59444737f703686d0d1996bf09ab08e2b
+ cmdver = 0.3.0
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 b/puppet/modules/ruby
deleted file mode 160000
-Subproject 9ccd853c49af7d0b57ebd9c2ea7673b193fce24
diff --git a/puppet/modules/ruby/.gitrepo b/puppet/modules/ruby/.gitrepo
new file mode 100644
index 00000000..f19ddac8
--- /dev/null
+++ b/puppet/modules/ruby/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_ruby
+ branch = master
+ commit = 9ccd853c49af7d0b57ebd9c2ea7673b193fce24b
+ parent = 10c80c65126cd84e6010495d30cb921d245f0d33
+ cmdver = 0.3.0
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 b/puppet/modules/rubygems
deleted file mode 160000
-Subproject e704c9fe1c40fea5b10fe3ca2b4f5de825341cc
diff --git a/puppet/modules/rubygems/.gitrepo b/puppet/modules/rubygems/.gitrepo
new file mode 100644
index 00000000..a6121a0f
--- /dev/null
+++ b/puppet/modules/rubygems/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_rubygems
+ branch = master
+ commit = 510a3693eab5dc78ed27d3728ee4d3b12334ea12
+ parent = b5c3c14ae094bff896d19ee37f967e8fc8c34017
+ cmdver = 0.3.0
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 b/puppet/modules/shorewall
deleted file mode 160000
-Subproject e4a54e30bf2ad7fa45c73cc544e1da4524a287a
diff --git a/puppet/modules/shorewall/.gitrepo b/puppet/modules/shorewall/.gitrepo
new file mode 100644
index 00000000..7ae31f9d
--- /dev/null
+++ b/puppet/modules/shorewall/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_shorewall
+ branch = master
+ commit = 06e89ed3486916ae12186e46b8ec59c8c7c79142
+ parent = 6083b23278927189de58c11bbb5bc7d93ccced24
+ cmdver = 0.3.0
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.md b/puppet/modules/shorewall/README.md
new file mode 100644
index 00000000..e7e29859
--- /dev/null
+++ b/puppet/modules/shorewall/README.md
@@ -0,0 +1,224 @@
+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://gitlab.com/shared-puppet-modules-group/augeas.git
+
+This module requires the concat module, you can find that here:
+https://github.com/puppetlabs/puppetlabs-concat.git
+
+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
+- https://gitlab.com/shared-puppet-modules-group/shorewall.git
+
+
+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..663e4367
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/interfaces.header
@@ -0,0 +1,10 @@
+#
+# Shorewall version 4 - Interfaces File
+#
+# For information about entries in this file, type "man shorewall-interfaces"
+#
+# For additional information, see
+# http://www.shorewall.net/manpages/shorewall-interfaces.html
+#
+###############################################################################
+#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..cc9781f0
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/policy.header
@@ -0,0 +1,11 @@
+#
+# Shorewall version 4 - Policy File
+#
+# For information about entries in this file, type "man shorewall-policy"
+#
+# The manpage is also online at
+# http://www.shorewall.net/manpages/shorewall-policy.html
+#
+###############################################################################
+#SOURCE DEST POLICY LOG LIMIT: CONNLIMIT:
+# LEVEL BURST MASK
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..5dada523
--- /dev/null
+++ b/puppet/modules/shorewall/files/boilerplate/zones.header
@@ -0,0 +1,12 @@
+#
+# Shorewall version 4 - Zones File
+#
+# For information about this file, type "man shorewall-zones"
+#
+# The manpage is also online at
+# http://www.shorewall.net/manpages/shorewall-zones.html
+#
+###############################################################################
+#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..6599759e
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/base.pp
@@ -0,0 +1,78 @@
+# 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 => Exec['shorewall_check'],
+ owner => 'root',
+ group => 'root',
+ mode => '0644';
+ '/etc/shorewall/puppet':
+ ensure => directory,
+ require => Package['shorewall'],
+ owner => 'root',
+ group => 'root',
+ mode => '0644';
+ }
+
+ if $shorewall::conf_source {
+ File['/etc/shorewall/shorewall.conf']{
+ source => $shorewall::conf_source,
+ }
+ } else {
+
+ include ::augeas
+ 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 => Exec['shorewall_check'],
+ require => Package['shorewall'];
+ }
+ }
+
+ exec{'shorewall_check':
+ command => 'shorewall check',
+ refreshonly => true,
+ notify => Service['shorewall'],
+ }
+ service{'shorewall':
+ ensure => running,
+ enable => true,
+ hasstatus => true,
+ hasrestart => true,
+ require => Package['shorewall'],
+ }
+
+ file{'/etc/cron.daily/shorewall_check':}
+ if $shorewall::daily_check {
+ File['/etc/cron.daily/shorewall_check']{
+ content => '#!/bin/bash
+
+output=$(shorewall check 2>&1)
+if [ $? -gt 0 ]; then
+ echo "Error while checking firewall!"
+ echo $output
+ exit 1
+fi
+exit 0
+',
+ owner => root,
+ group => 0,
+ mode => '0700',
+ require => Service['shorewall'],
+ }
+ } else {
+ File['/etc/cron.daily/shorewall_check']{
+ ensure => absent,
+ }
+ }
+}
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..1f8b37dd
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/centos.pp
@@ -0,0 +1,13 @@
+# things needed on centos
+class shorewall::centos inherits shorewall::base {
+ if versioncmp($::operatingsystemmajrelease,'5') > 0 {
+ augeas{'enable_shorewall':
+ context => '/files/etc/sysconfig/shorewall',
+ changes => 'set startup 1',
+ lens => 'Shellvars.lns',
+ incl => '/etc/sysconfig/shorewall',
+ require => Package['shorewall'],
+ notify => Exec['shorewall_check'],
+ }
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/debian.pp b/puppet/modules/shorewall/manifests/debian.pp
new file mode 100644
index 00000000..07176a32
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/debian.pp
@@ -0,0 +1,11 @@
+# debian specific things
+class shorewall::debian inherits shorewall::base {
+ file{'/etc/default/shorewall':
+ content => template('shorewall/debian_default.erb'),
+ require => Package['shorewall'],
+ notify => Exec['shorewall_check'],
+ owner => 'root',
+ group => 'root',
+ mode => '0644';
+ }
+}
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..80b83d3b
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/extension_script.pp
@@ -0,0 +1,16 @@
+# 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 => Exec['shorewall_check'];
+ }
+ }
+ 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..d6b2d2a4
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/init.pp
@@ -0,0 +1,123 @@
+# 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'
+ },
+ $zones = {},
+ $zones_defaults = {},
+ $interfaces = {},
+ $interfaces_defaults = {},
+ $hosts = {},
+ $hosts_defaults = {},
+ $policy = {},
+ $policy_defaults = {},
+ $rules = {},
+ $rules_defaults = {},
+ $rulesections = {},
+ $rulesections_defaults = {},
+ $masq = {},
+ $masq_defaults = {},
+ $proxyarp = {},
+ $proxyarp_defaults = {},
+ $nat = {},
+ $nat_defaults = {},
+ $blacklist = {},
+ $blacklist_defaults = {},
+ $rfc1918 = {},
+ $rfc1918_defaults = {},
+ $routestopped = {},
+ $routestopped_defaults = {},
+ $params = {},
+ $params_defaults = {},
+ $tcdevices = {},
+ $tcdevices_defaults = {},
+ $tcrules = {},
+ $tcrules_defaults = {},
+ $tcclasses = {},
+ $tcclasses_defaults = {},
+ $tunnels = {},
+ $tunnels_defaults = {},
+ $rtrules = {},
+ $rtrules_defaults = {},
+ $daily_check = true,
+) {
+
+ case $::operatingsystem {
+ 'Gentoo': { include ::shorewall::gentoo }
+ 'Debian','Ubuntu': { include ::shorewall::debian }
+ 'CentOS': { include ::shorewall::centos }
+ 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',
+ ]:;
+ }
+
+ create_resources('shorewall::zone',$zones,$zones_defaults)
+ create_resources('shorewall::interface',$interfaces,$interfaces_defaults)
+ create_resources('shorewall::host',$hosts,$hosts_defaults)
+ create_resources('shorewall::policy',$policy,$policy_defaults)
+ create_resources('shorewall::rule',$rules,$rules_defaults)
+ create_resources('shorewall::rule_section',$rulesections,$rulesections_defaults)
+ create_resources('shorewall::masq',$masq,$masq_defaults)
+ create_resources('shorewall::proxyarp',$proxyarp,$proxyarp_defaults)
+ create_resources('shorewall::nat',$nat,$nat_defaults)
+ create_resources('shorewall::blacklist',$blacklist,$blacklist_defaults)
+ create_resources('shorewall::rfc1918',$rfc1918,$rfc1918_defaults)
+ create_resources('shorewall::routestopped',$routestopped,
+ $routestopped_defaults)
+ create_resources('shorewall::params',$params,$params_defaults)
+ create_resources('shorewall::tcdevices',$tcdevices,$tcdevices_defaults)
+ create_resources('shorewall::tcrules',$tcrules,$tcrules_defaults)
+ create_resources('shorewall::tcclasses',$tcclasses,$tcclasses_defaults)
+ create_resources('shorewall::tunnel',$tunnels,$tunnels_defaults)
+ create_resources('shorewall::rtrules',$rtrules,$rtrules_defaults)
+}
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..b3538145
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/managed_file.pp
@@ -0,0 +1,20 @@
+# manage a certain file
+define shorewall::managed_file() {
+ concat{ "/etc/shorewall/puppet/${name}":
+ notify => Exec['shorewall_check'],
+ require => File['/etc/shorewall/puppet'],
+ owner => 'root',
+ group => 'root',
+ 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..cd404e7c
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/mangle.pp
@@ -0,0 +1,20 @@
+define shorewall::mangle(
+ $source,
+ $destination,
+ $action = $name,
+ $proto = '-',
+ $destinationport = '-',
+ $sourceport = '-',
+ $user = '-',
+ $test = '-',
+ $length = '-',
+ $tos = '-',
+ $connbytes = '-',
+ $helper = '-',
+ $headers = '-',
+ $order = '100'
+){
+ shorewall::entry{"mangle-${order}-${name}":
+ line => "${action} ${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..e775eeed
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/dns.pp
@@ -0,0 +1,6 @@
+# open dns port
+class shorewall::rules::dns {
+ shorewall::rules::dns_rules{
+ 'net':
+ }
+}
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..7de923bd
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/dns/disable.pp
@@ -0,0 +1,6 @@
+# disable dns acccess
+class shorewall::rules::dns::disable inherits shorewall::rules::dns {
+ Shorewall::Rules::Dns_rules['net']{
+ action => 'DROP',
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/dns_rules.pp b/puppet/modules/shorewall/manifests/rules/dns_rules.pp
new file mode 100644
index 00000000..abe0eb5a
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/dns_rules.pp
@@ -0,0 +1,22 @@
+# open dns port
+define shorewall::rules::dns_rules(
+ $source = $name,
+ $action = 'ACCEPT',
+) {
+ shorewall::rule {
+ "${source}-me-tcp_dns":
+ source => $source,
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '53',
+ order => 240,
+ action => $action;
+ "${source}-me-udp_dns":
+ source => $source,
+ destination => '$FW',
+ proto => 'udp',
+ destinationport => '53',
+ order => 240,
+ action => $action;
+ }
+}
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..413406e1
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/ipsec.pp
@@ -0,0 +1,32 @@
+# manage ipsec rules for zone specified in
+# $name
+define shorewall::rules::ipsec() {
+ shorewall::rule {
+ "${name}-me-ipsec-udp":
+ source => $name,
+ destination => '$FW',
+ proto => 'udp',
+ destinationport => '500',
+ order => 240,
+ action => 'ACCEPT';
+ "me-${name}-ipsec-udp":
+ source => '$FW',
+ destination => $name,
+ proto => 'udp',
+ destinationport => '500',
+ order => 240,
+ action => 'ACCEPT';
+ "${name}-me-ipsec":
+ source => $name,
+ destination => '$FW',
+ proto => 'esp',
+ order => 240,
+ action => 'ACCEPT';
+ "me-${name}-ipsec":
+ source => '$FW',
+ destination => $name,
+ 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..226d6274
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/jabberserver.pp
@@ -0,0 +1,34 @@
+# open ports used by a jabberserver
+# in and outbound.
+class shorewall::rules::jabberserver(
+ $open_stun = true,
+) {
+ 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';
+ }
+
+ if $open_stun {
+ shorewall::rule {
+ 'net-me-udp_jabber_stun_server':
+ source => 'net',
+ destination => '$FW',
+ proto => 'udp',
+ destinationport => '3478',
+ 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..dc3970d1
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/libvirt/host.pp
@@ -0,0 +1,80 @@
+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_${vmz_iface}":
+ action => '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..ce1c321f
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/managesieve.pp
@@ -0,0 +1,25 @@
+# manage managesieve ports
+class shorewall::rules::managesieve(
+ $legacy_port = false,
+) {
+ shorewall::rule {
+ 'net-me-tcp_managesieve':
+ source => 'net',
+ destination => '$FW',
+ proto => 'tcp',
+ destinationport => '4190',
+ order => 260,
+ action => 'ACCEPT';
+ }
+ if $legacy_port {
+ shorewall::rule {
+ 'net-me-tcp_managesieve_legacy':
+ 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/openvpn.pp b/puppet/modules/shorewall/manifests/rules/openvpn.pp
new file mode 100644
index 00000000..55a20d2d
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/openvpn.pp
@@ -0,0 +1,18 @@
+class shorewall::rules::openvpn {
+ shorewall::rule { 'net-me-openvpn-udp':
+ source => 'net',
+ destination => '$FW',
+ proto => 'udp',
+ destinationport => '1194',
+ order => 240,
+ action => 'ACCEPT';
+ }
+ shorewall::rule { 'me-net-openvpn-udp':
+ source => '$FW',
+ destination => 'net',
+ proto => 'udp',
+ destinationport => '1194',
+ 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..c4147d4b
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/out/managesieve.pp
@@ -0,0 +1,25 @@
+# manage outgoing traffic to managesieve
+class shorewall::rules::out::managesieve(
+ $legacy_port = false
+) {
+ shorewall::rule {
+ 'me-net-tcp_managesieve':
+ source => '$FW',
+ destination => 'net',
+ proto => 'tcp',
+ destinationport => '4190',
+ order => 260,
+ action => 'ACCEPT';
+ }
+ if $legacy_port {
+ shorewall::rule {
+ 'me-net-tcp_managesieve_legacy':
+ 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/pyzor.pp b/puppet/modules/shorewall/manifests/rules/out/pyzor.pp
new file mode 100644
index 00000000..f4f5151a
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/out/pyzor.pp
@@ -0,0 +1,12 @@
+# pyzor calls out on 24441
+# https://wiki.apache.org/spamassassin/NetTestFirewallIssues
+class shorewall::rules::out::pyzor {
+ shorewall::rule { 'me-net-udp_pyzor':
+ source => '$FW',
+ destination => 'net',
+ proto => 'udp',
+ destinationport => '24441',
+ order => 240,
+ action => 'ACCEPT';
+ }
+}
diff --git a/puppet/modules/shorewall/manifests/rules/out/razor.pp b/puppet/modules/shorewall/manifests/rules/out/razor.pp
new file mode 100644
index 00000000..1f8397ce
--- /dev/null
+++ b/puppet/modules/shorewall/manifests/rules/out/razor.pp
@@ -0,0 +1,12 @@
+# razor calls out on 2703
+# https://wiki.apache.org/spamassassin/NetTestFirewallIssues
+class shorewall::rules::out::razor {
+ shorewall::rule { 'me-net-tcp_razor':
+ source => '$FW',
+ destination => 'net',
+ proto => 'tcp',
+ destinationport => '2703',
+ order => 240,
+ 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/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 b/puppet/modules/site-apache
deleted file mode 120000
index f0517fa5..00000000
--- a/puppet/modules/site-apache
+++ /dev/null
@@ -1 +0,0 @@
-site_apache \ No newline at end of file
diff --git a/puppet/modules/site_apache/files/conf.d/acme.conf b/puppet/modules/site_apache/files/conf.d/acme.conf
new file mode 100644
index 00000000..cdddf53e
--- /dev/null
+++ b/puppet/modules/site_apache/files/conf.d/acme.conf
@@ -0,0 +1,10 @@
+#
+# Allow ACME certificate verification if /srv/acme exists.
+#
+<IfModule mod_headers.c>
+ Alias "/.well-known/acme-challenge/" "/srv/acme/"
+ <Directory "/srv/acme/*">
+ Require all granted
+ Header set Content-Type "application/jose+json"
+ </Directory>
+</IfModule>
diff --git a/puppet/modules/site_apache/manifests/common.pp b/puppet/modules/site_apache/manifests/common.pp
index 8a11759a..208c15d5 100644
--- a/puppet/modules/site_apache/manifests/common.pp
+++ b/puppet/modules/site_apache/manifests/common.pp
@@ -27,4 +27,6 @@ class site_apache::common {
}
include site_apache::common::tls
+ include site_apache::common::acme
+
}
diff --git a/puppet/modules/site_apache/manifests/common/acme.pp b/puppet/modules/site_apache/manifests/common/acme.pp
new file mode 100644
index 00000000..eda4148b
--- /dev/null
+++ b/puppet/modules/site_apache/manifests/common/acme.pp
@@ -0,0 +1,38 @@
+#
+# Allows for potential ACME validations (aka Let's Encrypt)
+#
+class site_apache::common::acme {
+ #
+ # well, this doesn't work:
+ #
+ # apache::config::global {'acme.conf':}
+ #
+ # since /etc/apache2/conf.d is NEVER LOADED BY APACHE
+ # https://gitlab.com/shared-puppet-modules-group/apache/issues/11
+ #
+
+ file {
+ '/etc/apache2/conf-available/acme.conf':
+ ensure => present,
+ source => 'puppet:///modules/site_apache/conf.d/acme.conf',
+ require => Package[apache],
+ notify => Service[apache];
+ '/etc/apache2/conf-enabled/acme.conf':
+ ensure => link,
+ target => '/etc/apache2/conf-available/acme.conf',
+ require => Package[apache],
+ notify => Service[apache];
+ }
+
+ file {
+ '/srv/acme':
+ ensure => 'directory',
+ owner => 'www-data',
+ group => 'www-data',
+ mode => '0755';
+ '/srv/acme/ok':
+ owner => 'www-data',
+ group => 'www-data',
+ content => 'ok';
+ }
+}
diff --git a/puppet/modules/site_apache/templates/vhosts.d/api.conf.erb b/puppet/modules/site_apache/templates/vhosts.d/api.conf.erb
index bfa5d04d..e68b9ebe 100644
--- a/puppet/modules/site_apache/templates/vhosts.d/api.conf.erb
+++ b/puppet/modules/site_apache/templates/vhosts.d/api.conf.erb
@@ -23,6 +23,8 @@ Listen 0.0.0.0:<%= @api_port %>
<% end -%>
Header always unset X-Powered-By
Header always unset X-Runtime
+ Header always set X-XSS-Protection "1; mode=block"
+ Header always set X-Content-Type-Options: nosniff
</IfModule>
DocumentRoot /srv/leap/webapp/public
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
index 232b1577..1d19094e 100644
--- a/puppet/modules/site_apache/templates/vhosts.d/hidden_service.conf.erb
+++ b/puppet/modules/site_apache/templates/vhosts.d/hidden_service.conf.erb
@@ -37,19 +37,4 @@
</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/manifests/init.pp b/puppet/modules/site_apt/manifests/init.pp
index 455425c1..26bd2c6a 100644
--- a/puppet/modules/site_apt/manifests/init.pp
+++ b/puppet/modules/site_apt/manifests/init.pp
@@ -25,7 +25,8 @@ class site_apt {
debian_url => $apt_url_basic,
security_url => $apt_url_security,
backports_url => $apt_url_backports,
- use_next_release => $use_next_release
+ use_next_release => $use_next_release,
+ repos => 'main'
}
# enable http://deb.leap.se debian package repository
diff --git a/puppet/modules/site_apt/manifests/preferences/twisted.pp b/puppet/modules/site_apt/manifests/preferences/twisted.pp
new file mode 100644
index 00000000..a3fa0950
--- /dev/null
+++ b/puppet/modules/site_apt/manifests/preferences/twisted.pp
@@ -0,0 +1,11 @@
+# Pin twisted to jessie-backports in order to
+# use 16.2.0 for i.e. soledad
+class site_apt::preferences::twisted {
+
+ apt::preferences_snippet { 'twisted':
+ package => 'python-twisted*',
+ release => "${::lsbdistcodename}-backports",
+ priority => 999;
+ }
+
+}
diff --git a/puppet/modules/site_check_mk/files/agent/logwatch/bigcouch.cfg b/puppet/modules/site_check_mk/files/agent/logwatch/bigcouch.cfg
deleted file mode 100644
index 0f378a5a..00000000
--- a/puppet/modules/site_check_mk/files/agent/logwatch/bigcouch.cfg
+++ /dev/null
@@ -1,28 +0,0 @@
-/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/soledad.cfg b/puppet/modules/site_check_mk/files/agent/logwatch/soledad.cfg
index 3af5045b..11ad3a54 100644
--- a/puppet/modules/site_check_mk/files/agent/logwatch/soledad.cfg
+++ b/puppet/modules/site_check_mk/files/agent/logwatch/soledad.cfg
@@ -1,4 +1,7 @@
/var/log/soledad.log
+# Ignore 401 errors because they are quite noisy due to scanners giving us many false
+# positives, and we do not need to see those
+ I \".*401 [0-9]+
C WSGI application error
C Error
C error
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
index 71395c50..7daf0cac 100644
--- a/puppet/modules/site_check_mk/files/agent/logwatch/syslog_tail.cfg
+++ b/puppet/modules/site_check_mk/files/agent/logwatch/syslog_tail.cfg
@@ -15,3 +15,7 @@
# 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/nagios_plugins/check_unix_open_fds.pl b/puppet/modules/site_check_mk/files/agent/nagios_plugins/check_unix_open_fds.pl
deleted file mode 100755
index 06163d49..00000000
--- a/puppet/modules/site_check_mk/files/agent/nagios_plugins/check_unix_open_fds.pl
+++ /dev/null
@@ -1,322 +0,0 @@
-#!/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/ignored_services.mk b/puppet/modules/site_check_mk/files/ignored_services.mk
index 35dc4433..8a6705ac 100644
--- a/puppet/modules/site_check_mk/files/ignored_services.mk
+++ b/puppet/modules/site_check_mk/files/ignored_services.mk
@@ -1,3 +1,5 @@
-ignored_services = [
+# ignore NTP Time because this check was
+# very flaky in the past (see https://leap.se/code/issues/6407)
+ignored_services += [
( ALL_HOSTS, [ "NTP Time" ] )
]
diff --git a/puppet/modules/site_check_mk/manifests/agent/couchdb.pp b/puppet/modules/site_check_mk/manifests/agent/couchdb.pp
index 1554fd3c..9fc771e0 100644
--- a/puppet/modules/site_check_mk/manifests/agent/couchdb.pp
+++ b/puppet/modules/site_check_mk/manifests/agent/couchdb.pp
@@ -1,5 +1,4 @@
-# configure logwatch and nagios checks for couchdb (both bigcouch and plain
-# couchdb installations)
+# configure logwatch and nagios checks for couchdb
class site_check_mk::agent::couchdb {
concat::fragment { 'syslog_couchdb':
@@ -14,21 +13,4 @@ class site_check_mk::agent::couchdb {
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
deleted file mode 100644
index 82c3ac72..00000000
--- a/puppet/modules/site_check_mk/manifests/agent/couchdb/bigcouch.pp
+++ /dev/null
@@ -1,49 +0,0 @@
-# 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
deleted file mode 100644
index 3ec2267b..00000000
--- a/puppet/modules/site_check_mk/manifests/agent/couchdb/plain.pp
+++ /dev/null
@@ -1,23 +0,0 @@
-# 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/soledad.pp b/puppet/modules/site_check_mk/manifests/agent/soledad.pp
index f4a3f3a6..a8febaae 100644
--- a/puppet/modules/site_check_mk/manifests/agent/soledad.pp
+++ b/puppet/modules/site_check_mk/manifests/agent/soledad.pp
@@ -1,17 +1,8 @@
+# Configure soledad check_mk checks
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/lib/puppet/parser/functions/create_resources_hash_from.rb b/puppet/modules/site_config/lib/puppet/parser/functions/create_resources_hash_from.rb
index 47d0df9c..47d0df9c 100644
--- a/puppet/lib/puppet/parser/functions/create_resources_hash_from.rb
+++ b/puppet/modules/site_config/lib/puppet/parser/functions/create_resources_hash_from.rb
diff --git a/puppet/lib/puppet/parser/functions/sorted_json.rb b/puppet/modules/site_config/lib/puppet/parser/functions/sorted_json.rb
index 605da00e..605da00e 100644
--- a/puppet/lib/puppet/parser/functions/sorted_json.rb
+++ b/puppet/modules/site_config/lib/puppet/parser/functions/sorted_json.rb
diff --git a/puppet/lib/puppet/parser/functions/sorted_yaml.rb b/puppet/modules/site_config/lib/puppet/parser/functions/sorted_yaml.rb
index 46cd46ce..46cd46ce 100644
--- a/puppet/lib/puppet/parser/functions/sorted_yaml.rb
+++ b/puppet/modules/site_config/lib/puppet/parser/functions/sorted_yaml.rb
diff --git a/puppet/modules/site_config/manifests/caching_resolver.pp b/puppet/modules/site_config/manifests/caching_resolver.pp
index 8bf465c1..4da13d9c 100644
--- a/puppet/modules/site_config/manifests/caching_resolver.pp
+++ b/puppet/modules/site_config/manifests/caching_resolver.pp
@@ -1,20 +1,33 @@
# deploy local caching resolver
class site_config::caching_resolver {
tag 'leap_base'
+ $domain = hiera('domain')
+ $internal_domain = $domain['internal_suffix']
+
+ # We need to make sure Package['bind9'] isn't installed because when it is, it
+ # keeps unbound from running. Some base debian installs will install bind9,
+ # and then start it, so unbound will never get properly started. So this will
+ # make sure bind9 is removed before.
+ package { 'bind9':
+ ensure => purged
+ }
class { 'unbound':
root_hints => false,
anchor => false,
ssl => false,
+ require => Package['bind9'],
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' ]
+ 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' ],
+ module-config => '"validator iterator"',
+ domain-insecure => $internal_domain
}
}
}
diff --git a/puppet/modules/site_config/manifests/remove/bigcouch.pp b/puppet/modules/site_config/manifests/remove/bigcouch.pp
index 3535c3c1..9fd3e7ee 100644
--- a/puppet/modules/site_config/manifests/remove/bigcouch.pp
+++ b/puppet/modules/site_config/manifests/remove/bigcouch.pp
@@ -10,6 +10,33 @@ class site_config::remove::bigcouch {
]
}
+ tidy {
+ '/etc/logrotate/bigcouch':;
+ '/srv/leap/nagios/plugins/check_unix_open_fds.pl':;
+ }
+
+ augeas {
+ 'Couchdb_open_files':
+ incl => '/etc/check_mk/mrpe.cfg',
+ lens => 'Spacevars.lns',
+ changes => [
+ 'rm /files/etc/check_mk/mrpe.cfg/Couchdb_open_files',
+ 'rm /files/etc/check_mk/mrpe.cfg/Bigcouch_epmd_procs',
+ 'rm /files/etc/check_mk/mrpe.cfg/Bigcouch_beam_procs',
+ 'rm /files/etc/check_mk/mrpe.cfg/Bigcouch_open_files' ],
+ require => File['/etc/check_mk/mrpe.cfg'];
+ }
+
+ # check syslog msg from:
+ # - empd
+ # - /usr/local/bin/couch-doc-update
+ concat::fragment { 'syslog_bigcouch':
+ ensure => absent,
+ source => 'puppet:///modules/site_check_mk/agent/logwatch/syslog/bigcouch.cfg',
+ target => '/etc/check_mk/logwatch.d/syslog.cfg',
+ order => '02';
+ }
+
exec { 'remove_bigcouch_logwatch_stateline':
command => "sed -i '/bigcouch.log/d' /etc/check_mk/logwatch.state",
refreshonly => true,
diff --git a/puppet/modules/site_config/manifests/remove/files.pp b/puppet/modules/site_config/manifests/remove/files.pp
index 41d6462e..ac2350a0 100644
--- a/puppet/modules/site_config/manifests/remove/files.pp
+++ b/puppet/modules/site_config/manifests/remove/files.pp
@@ -11,7 +11,35 @@
class site_config::remove::files {
+ #
+ # Platform 0.9 removals
+ #
+
+ tidy {
+ # moved to /srv/static/public/provider.json
+ # for permissions reasons.
+ '/srv/leap/provider.json':;
+
+ # tests are moved to /srv/leap/tests/server-tests
+ # by rsync is not able to clean up the old location,
+ # so, we do it here:
+ '/srv/leap/tests/order.rb':;
+ '/srv/leap/tests/README.md':;
+ '/srv/leap/tests/helpers':
+ recurse => true,
+ rmdirs => true;
+ '/srv/leap/tests/puppet':
+ recurse => true,
+ rmdirs => true;
+ '/srv/leap/tests/white-box':
+ recurse => true,
+ rmdirs => true;
+ }
+
+ #
# Platform 0.8 removals
+ #
+
tidy {
'/etc/default/leap_mx':;
'/etc/logrotate.d/mx':;
diff --git a/puppet/modules/site_config/manifests/remove/soledad.pp b/puppet/modules/site_config/manifests/remove/soledad.pp
new file mode 100644
index 00000000..46c23f26
--- /dev/null
+++ b/puppet/modules/site_config/manifests/remove/soledad.pp
@@ -0,0 +1,12 @@
+# remove possible leftovers on soledad nodes
+class site_config::remove::soledad {
+
+ # remove soledad procs check because leap_cli already checks for them
+ augeas { 'Soledad_Procs':
+ incl => '/etc/check_mk/mrpe.cfg',
+ lens => 'Spacevars.lns',
+ changes => [ 'rm /files/etc/check_mk/mrpe.cfg/Soledad_Procs' ],
+ require => File['/etc/check_mk/mrpe.cfg'];
+ }
+
+}
diff --git a/puppet/modules/site_config/manifests/x509/commercial/ca.pp b/puppet/modules/site_config/manifests/x509/commercial/ca.pp
index c76a9dbb..21d57445 100644
--- a/puppet/modules/site_config/manifests/x509/commercial/ca.pp
+++ b/puppet/modules/site_config/manifests/x509/commercial/ca.pp
@@ -5,7 +5,13 @@ class site_config::x509::commercial::ca {
$x509 = hiera('x509')
$ca = $x509['commercial_ca_cert']
- x509::ca { $site_config::params::commercial_ca_name:
- content => $ca
+ #
+ # CA cert might be empty, if it was bundled with 'commercial_cert'
+ # instead of specified separately.
+ #
+ if ($ca) {
+ x509::ca { $site_config::params::commercial_ca_name:
+ content => $ca
+ }
}
}
diff --git a/puppet/modules/site_couchdb/files/local.ini b/puppet/modules/site_couchdb/files/local.ini
index 22aa0177..a6f4d981 100644
--- a/puppet/modules/site_couchdb/files/local.ini
+++ b/puppet/modules/site_couchdb/files/local.ini
@@ -1,91 +1,11 @@
-; CouchDB Configuration Settings
+; 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.
-[couchdb]
-;max_document_size = 4294967296 ; bytes
+[compactions]
+_default = [{db_fragmentation, "70%"}, {view_fragmentation, "60%"}, {from, "03:00"}, {to, "05:00"}]
[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">>}
-
-# futon is enabled by default on bigcouch in default.ini
-# we need to find another way to disable futon, it won't work disabling it here
-# enable futon
-#_utils = {couch_httpd_misc_handlers, handle_utils_dir_req, "/usr/share/couchdb/www"}
-# disable futon
-#_utils = {couch_httpd_misc_handlers, handle_welcome_req, <<"Welcome, Futon is disabled!">>}
-
-[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 = /etc/couchdb/server_cert.pem
-;key_file = /etc/couchdb/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
+socket_options = [{nodelay, true}]
diff --git a/puppet/modules/site_couchdb/files/runit_config b/puppet/modules/site_couchdb/files/runit_config
deleted file mode 100644
index 169b4832..00000000
--- a/puppet/modules/site_couchdb/files/runit_config
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/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/manifests/add_users.pp b/puppet/modules/site_couchdb/manifests/add_users.pp
index c905316b..f12c5a5e 100644
--- a/puppet/modules/site_couchdb/manifests/add_users.pp
+++ b/puppet/modules/site_couchdb/manifests/add_users.pp
@@ -33,7 +33,8 @@ class site_couchdb::add_users {
roles => '["tokens"]',
pw => $site_couchdb::couchdb_soledad_pw,
salt => $site_couchdb::couchdb_soledad_salt,
- require => Couchdb::Query::Setup['localhost']
+ require => Couchdb::Query::Setup['localhost'],
+ notify => Service['soledad-server'];
}
## webapp couchdb user
diff --git a/puppet/modules/site_couchdb/manifests/backup.pp b/puppet/modules/site_couchdb/manifests/backup.pp
index 8b5aa6ea..a9771776 100644
--- a/puppet/modules/site_couchdb/manifests/backup.pp
+++ b/puppet/modules/site_couchdb/manifests/backup.pp
@@ -1,8 +1,8 @@
class site_couchdb::backup {
# general backupninja config
- backupninja::config { 'backupninja_config':
- usecolors => false,
+ class { 'backupninja':
+ usecolors => false
}
# dump all DBs locally to /var/backups/couchdb once a day
diff --git a/puppet/modules/site_couchdb/manifests/bigcouch.pp b/puppet/modules/site_couchdb/manifests/bigcouch.pp
deleted file mode 100644
index 2de3d4d0..00000000
--- a/puppet/modules/site_couchdb/manifests/bigcouch.pp
+++ /dev/null
@@ -1,50 +0,0 @@
-# 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
deleted file mode 100644
index c8c43275..00000000
--- a/puppet/modules/site_couchdb/manifests/bigcouch/add_nodes.pp
+++ /dev/null
@@ -1,8 +0,0 @@
-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
deleted file mode 100644
index 84aab4ef..00000000
--- a/puppet/modules/site_couchdb/manifests/bigcouch/compaction.pp
+++ /dev/null
@@ -1,8 +0,0 @@
-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
deleted file mode 100644
index 820b5be2..00000000
--- a/puppet/modules/site_couchdb/manifests/bigcouch/settle_cluster.pp
+++ /dev/null
@@ -1,11 +0,0 @@
-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
index a2d1c655..ddfb7d65 100644
--- a/puppet/modules/site_couchdb/manifests/create_dbs.pp
+++ b/puppet/modules/site_couchdb/manifests/create_dbs.pp
@@ -44,7 +44,8 @@ class site_couchdb::create_dbs {
## r/w: soledad
couchdb::create_db { 'shared':
members => "{ \"names\": [\"${site_couchdb::couchdb_soledad_user}\"], \"roles\": [\"replication\"] }",
- require => Couchdb::Query::Setup['localhost']
+ require => Couchdb::Query::Setup['localhost'],
+ notify => Service['soledad-server'];
}
## tickets database
diff --git a/puppet/modules/site_couchdb/manifests/init.pp b/puppet/modules/site_couchdb/manifests/init.pp
index c4fe6277..5a73ae87 100644
--- a/puppet/modules/site_couchdb/manifests/init.pp
+++ b/puppet/modules/site_couchdb/manifests/init.pp
@@ -1,4 +1,4 @@
-# entry class for configuring couchdb/bigcouch node
+# entry class for configuring couchdb node
# couchdb node
class site_couchdb {
tag 'leap_service'
@@ -39,16 +39,10 @@ class site_couchdb {
$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['shorewall_check']
-> Exec['refresh_stunnel']
-> Class['couchdb']
-> Class['site_couchdb::setup']
@@ -60,7 +54,6 @@ class site_couchdb {
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 }
diff --git a/puppet/modules/site_couchdb/manifests/logrotate.pp b/puppet/modules/site_couchdb/manifests/logrotate.pp
deleted file mode 100644
index bb8843bb..00000000
--- a/puppet/modules/site_couchdb/manifests/logrotate.pp
+++ /dev/null
@@ -1,14 +0,0 @@
-# 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/plain.pp b/puppet/modules/site_couchdb/manifests/plain.pp
index b40fc100..710ff7ca 100644
--- a/puppet/modules/site_couchdb/manifests/plain.pp
+++ b/puppet/modules/site_couchdb/manifests/plain.pp
@@ -6,8 +6,6 @@ class site_couchdb::plain {
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
index 710d3c1c..a749c628 100644
--- a/puppet/modules/site_couchdb/manifests/setup.pp
+++ b/puppet/modules/site_couchdb/manifests/setup.pp
@@ -3,13 +3,6 @@
#
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
@@ -24,11 +17,11 @@ class site_couchdb::setup {
# (i.e. using curl/wget without passing credentials)
file {
'/etc/couchdb/couchdb.netrc':
- ensure => link,
- target => "/etc/couchdb/couchdb-${user}.netrc";
+ ensure => link,
+ target => "/etc/couchdb/couchdb-${user}.netrc";
'/root/.netrc':
- ensure => link,
- target => '/etc/couchdb/couchdb.netrc';
+ ensure => link,
+ target => '/etc/couchdb/couchdb.netrc';
}
# setup /etc/couchdb/couchdb-soledad-admin.netrc file for couchdb admin
@@ -40,7 +33,8 @@ class site_couchdb::setup {
mode => '0400',
owner => 'soledad-admin',
group => 'root',
- require => [ Package['couchdb'], User['soledad-admin'] ];
+ require => [ Package['couchdb'], User['soledad-admin'] ],
+ notify => Service['soledad-server'];
}
}
diff --git a/puppet/modules/site_mx/manifests/init.pp b/puppet/modules/site_mx/manifests/init.pp
index a9b0198b..c910a45a 100644
--- a/puppet/modules/site_mx/manifests/init.pp
+++ b/puppet/modules/site_mx/manifests/init.pp
@@ -1,20 +1,23 @@
+# Configure leap_mx on mx server
class site_mx {
tag 'leap_service'
- Class['site_config::default'] -> Class['site_mx']
+ 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_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_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
+ 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
+ # install twisted from jessie backports
+ include ::site_apt::preferences::twisted
}
diff --git a/puppet/modules/site_nagios/manifests/server.pp b/puppet/modules/site_nagios/manifests/server.pp
index aa9b956e..6537124d 100644
--- a/puppet/modules/site_nagios/manifests/server.pp
+++ b/puppet/modules/site_nagios/manifests/server.pp
@@ -59,7 +59,7 @@ class site_nagios::server inherits nagios::base {
include site_webapp::common_vhost
include apache::module::headers
- File ['nagios_htpasswd'] {
+ File['nagios_htpasswd'] {
source => undef,
content => "nagiosadmin:${nagiosadmin_pw}",
mode => '0640',
diff --git a/puppet/modules/site_nickserver/manifests/init.pp b/puppet/modules/site_nickserver/manifests/init.pp
index eb4415e7..ad97f829 100644
--- a/puppet/modules/site_nickserver/manifests/init.pp
+++ b/puppet/modules/site_nickserver/manifests/init.pp
@@ -149,7 +149,7 @@ class site_nickserver {
file { '/etc/shorewall/macro.nickserver':
content => "PARAM - - tcp ${nickserver_port}",
- notify => Service['shorewall'],
+ notify => Exec['shorewall_check'],
require => Package['shorewall'];
}
diff --git a/puppet/modules/site_openvpn/manifests/server_config.pp b/puppet/modules/site_openvpn/manifests/server_config.pp
index 6decc665..15e6fb38 100644
--- a/puppet/modules/site_openvpn/manifests/server_config.pp
+++ b/puppet/modules/site_openvpn/manifests/server_config.pp
@@ -30,7 +30,7 @@
# 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
+# 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
@@ -40,14 +40,14 @@
# 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
+# 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.”
+# version 2.2.1) says "CBC is recommended and CFB and OFB should be considered
+# advanced modes."
#
# note: the default is BF-CBC (blowfish)
#
diff --git a/puppet/modules/site_postfix/files/checks/helo_access.pcre b/puppet/modules/site_postfix/files/checks/helo_access.pcre
new file mode 100644
index 00000000..4ebd42e6
--- /dev/null
+++ b/puppet/modules/site_postfix/files/checks/helo_access.pcre
@@ -0,0 +1,2 @@
+!/[[:alpha:]]/ REJECT Numeric HELO is a sign of spam, please contact us if this is in error
+
diff --git a/puppet/modules/site_postfix/manifests/mx.pp b/puppet/modules/site_postfix/manifests/mx.pp
index 0b760eb4..2dac85f5 100644
--- a/puppet/modules/site_postfix/manifests/mx.pp
+++ b/puppet/modules/site_postfix/manifests/mx.pp
@@ -57,10 +57,6 @@ class site_postfix::mx {
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
@@ -90,6 +86,35 @@ class site_postfix::mx {
value => 'permit_mynetworks';
'postscreen_greet_action':
value => 'enforce';
+ # Level of DNS support in the Postfix SMTP client. Enable DNS lookups
+ # (default: empty). When empty, then the legacy "disable_dns_lookups"
+ # (default: no) parameter is used. Setting 'smtp_dns_support_level' to
+ # enabled sets the previous behavior with the new parameter. When set to
+ # 'dnssec" this enables DNSSEC lookups.
+ 'smtp_dns_support_level':
+ value => 'dnssec';
+
+ # http://www.postfix.org/TLS_README.html#client_tls_dane The "dane" level is
+ # a stronger form of opportunistic TLS that is resistant to man in the
+ # middle and downgrade attacks when the destination domain uses DNSSEC to
+ # publish DANE TLSA records for its MX hosts. If a remote SMTP server has
+ # "usable" (see RFC 6698) DANE TLSA records, the server connection will be
+ # authenticated. When DANE authentication fails, there is no fallback to
+ # unauthenticated or plaintext delivery.
+ #
+ # If TLSA records are published for a given remote SMTP server (implying TLS
+ # support), but are all "unusable" due to unsupported parameters or
+ # malformed data, the Postfix SMTP client will use mandatory unauthenticated
+ # TLS. Otherwise, when no TLSA records are published, the Postfix SMTP
+ # client behavior is the same as with may.
+ #
+ # This requires postfix to be able to send its DNS queries to a recursive
+ # DNS nameserver that is able to validate the signed records
+ #
+ # 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 => 'dane';
}
# Make sure that the cleanup serivce is not chrooted, otherwise it cannot
diff --git a/puppet/modules/site_postfix/manifests/mx/checks.pp b/puppet/modules/site_postfix/manifests/mx/checks.pp
index f406ad34..9678c205 100644
--- a/puppet/modules/site_postfix/manifests/mx/checks.pp
+++ b/puppet/modules/site_postfix/manifests/mx/checks.pp
@@ -1,3 +1,4 @@
+# management of specific MTA checks
class site_postfix::mx::checks {
file {
@@ -13,6 +14,12 @@ class site_postfix::mx::checks {
mode => '0644',
owner => root,
group => root;
+
+ '/etc/postfix/checks/helo_checks.pcre':
+ source => 'puppet:///modules/site_postfix/checks/helo_access.pcre',
+ mode => '0644',
+ owner => root,
+ group => root;
}
exec {
diff --git a/puppet/modules/site_postfix/manifests/mx/smtpd_checks.pp b/puppet/modules/site_postfix/manifests/mx/smtpd_checks.pp
index 291d7ee4..162e6d86 100644
--- a/puppet/modules/site_postfix/manifests/mx/smtpd_checks.pp
+++ b/puppet/modules/site_postfix/manifests/mx/smtpd_checks.pp
@@ -28,7 +28,7 @@ class site_postfix::mx::smtpd_checks {
'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';
+ value => 'permit_mynetworks, check_helo_access hash:$checks_dir/helo_checks, check_helo_access pcre:$checks_dir/helo_checks.pcre, permit';
'smtpd_sender_restrictions':
value => 'permit_mynetworks, reject_non_fqdn_sender, reject_unknown_sender_domain, permit';
}
diff --git a/puppet/modules/site_postfix/templates/checks/helo_access.erb b/puppet/modules/site_postfix/templates/checks/helo_access.erb
index bac2c45a..e0708605 100644
--- a/puppet/modules/site_postfix/templates/checks/helo_access.erb
+++ b/puppet/modules/site_postfix/templates/checks/helo_access.erb
@@ -19,3 +19,5 @@
# 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 %>
+localhost 554 You are not localhost
+
diff --git a/puppet/modules/site_rsyslog/templates/client.conf.erb b/puppet/modules/site_rsyslog/templates/client.conf.erb
index 7f94759d..1a1e4b6d 100644
--- a/puppet/modules/site_rsyslog/templates/client.conf.erb
+++ b/puppet/modules/site_rsyslog/templates/client.conf.erb
@@ -83,7 +83,7 @@ $ActionSendStreamDriverAuthMode anon
<% if scope.lookupvar('rsyslog::log_style') == 'debian' -%>
# Log auth messages locally
-.*;auth,authpriv.none;mail.none -/var/log/syslog
+auth,authpriv.* /var/log/auth.log
<% elsif scope.lookupvar('rsyslog::log_style') == 'redhat' -%>
# Log auth messages locally
auth,authpriv.* /var/log/secure
@@ -93,7 +93,7 @@ auth,authpriv.* /var/log/secure
<% if scope.lookupvar('rsyslog::log_style') == 'debian' -%>
# First some standard log files. Log by facility.
#
-*.*;auth,authpriv.none -/var/log/syslog
+*.*;auth,authpriv.none;mail.none -/var/log/syslog
cron.* /var/log/cron.log
daemon.* -/var/log/daemon.log
kern.* -/var/log/kern.log
@@ -106,10 +106,6 @@ user.* -/var/log/user.log
*.=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!
diff --git a/puppet/modules/site_shorewall/manifests/defaults.pp b/puppet/modules/site_shorewall/manifests/defaults.pp
index ceb17868..725e0880 100644
--- a/puppet/modules/site_shorewall/manifests/defaults.pp
+++ b/puppet/modules/site_shorewall/manifests/defaults.pp
@@ -1,3 +1,4 @@
+# Configure basic firewall rules for shorewall
class site_shorewall::defaults {
include shorewall
@@ -55,7 +56,7 @@ class site_shorewall::defaults {
mode => '0644',
source => 'puppet:///modules/site_shorewall/Debian/shorewall.service',
require => Package['shorewall'],
- notify => Service['shorewall'],
+ notify => Exec['shorewall_check'],
} ~>
Exec['systemctl-daemon-reload']
@@ -66,14 +67,14 @@ class site_shorewall::defaults {
lens => 'Shellvars.lns',
incl => '/etc/shorewall/shorewall.conf',
require => Package['shorewall'],
- notify => Service['shorewall'];
+ notify => Exec['shorewall_check'];
# 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'];
+ notify => Exec['shorewall_check'];
# configure shorewall-init
'shorewall-init':
changes => 'set /files/etc/default/shorewall-init/PRODUCTS shorewall',
diff --git a/puppet/modules/site_shorewall/manifests/eip.pp b/puppet/modules/site_shorewall/manifests/eip.pp
index 8fbba658..5aac4fdd 100644
--- a/puppet/modules/site_shorewall/manifests/eip.pp
+++ b/puppet/modules/site_shorewall/manifests/eip.pp
@@ -1,3 +1,4 @@
+# Configure shorewall on eip/vpn nodes
class site_shorewall::eip {
include site_shorewall::defaults
@@ -9,7 +10,7 @@ class site_shorewall::eip {
content => "PARAM - - tcp 1194
PARAM - - udp 1194
",
- notify => Service['shorewall'],
+ notify => Exec['shorewall_check'],
require => Package['shorewall']
}
@@ -84,6 +85,154 @@ class site_shorewall::eip {
proto => 'tcp',
destinationport => 'domain',
order => 301;
+
+ 'accept_all_eip_to_eip_gateway_udp_unlimited':
+ action => 'ACCEPT',
+ source => 'eip',
+ destination => 'eip:10.41.0.1',
+ proto => 'all',
+ order => 302;
+
+ 'accept_all_eip_to_eip_gateway_tcp_unlimited':
+ action => 'ACCEPT',
+ source => 'eip',
+ destination => 'eip:10.42.0.1',
+ proto => 'all',
+ order => 303;
+
+ 'accept_all_eip_to_eip_gateway_udp_limited':
+ action => 'ACCEPT',
+ source => 'eip',
+ destination => 'eip:10.43.0.1',
+ proto => 'all',
+ order => 304;
+
+ 'accept_all_eip_to_eip_gateway_tcp_limited':
+ action => 'ACCEPT',
+ source => 'eip',
+ destination => 'eip:10.44.0.1',
+ order => 305;
+
+ 'reject_all_other_eip_to_eip':
+ action => 'REJECT',
+ source => 'eip',
+ destination => 'eip',
+ order => 306;
+ # Strict egress filtering:
+ # SMTP (TCP 25)
+ # Trivial File Transfer Protocol - TFTP (UDP 69)
+ # MS RPC (TCP & UDP 135)
+ # NetBIOS/IP (TCP/UDP 139 & UDP 137, UDP 138)
+ # Simple Network Management Protocol – SNMP (UDP/TCP 161-162)
+ # SMB/IP (TCP/UDP 445)
+ # Syslog (UDP 514)
+ # Gamqowi trojan: TCP 4661
+ # Mneah trojan: TCP 4666
+ 'reject_outgoing_smtp':
+ action => 'REJECT',
+ source => 'eip',
+ destination => 'net',
+ proto => 'tcp',
+ destinationport => 'smtp',
+ order => 401;
+ 'reject_outgoing_tftp':
+ action => 'REJECT',
+ source => 'eip',
+ destination => 'net',
+ proto => 'udp',
+ destinationport => 'tftp',
+ order => 402;
+ 'reject_outgoing_ms_rpc_tcp':
+ action => 'REJECT',
+ source => 'eip',
+ destination => 'net',
+ proto => 'tcp',
+ destinationport => '135',
+ order => 403;
+ 'reject_outgoing_ms_rpc_udp':
+ action => 'REJECT',
+ source => 'eip',
+ destination => 'net',
+ proto => 'udp',
+ destinationport => '135',
+ order => 404;
+ 'reject_outgoing_netbios_tcp':
+ action => 'REJECT',
+ source => 'eip',
+ destination => 'net',
+ proto => 'tcp',
+ destinationport => '139',
+ order => 405;
+ 'reject_outgoing_netbios_udp':
+ action => 'REJECT',
+ source => 'eip',
+ destination => 'net',
+ proto => 'tcp',
+ destinationport => '139',
+ order => 406;
+ 'reject_outgoing_netbios_2':
+ action => 'REJECT',
+ source => 'eip',
+ destination => 'net',
+ proto => 'udp',
+ destinationport => '137',
+ order => 407;
+ 'reject_outgoing_netbios_3':
+ action => 'REJECT',
+ source => 'eip',
+ destination => 'net',
+ proto => 'udp',
+ destinationport => '138',
+ order => 408;
+ 'reject_outgoing_snmp_udp':
+ action => 'REJECT',
+ source => 'eip',
+ destination => 'net',
+ proto => 'udp',
+ destinationport => 'snmp',
+ order => 409;
+ 'reject_outgoing_snmp_tcp':
+ action => 'REJECT',
+ source => 'eip',
+ destination => 'net',
+ proto => 'tcp',
+ destinationport => 'snmp',
+ order => 410;
+ 'reject_outgoing_smb_udp':
+ action => 'REJECT',
+ source => 'eip',
+ destination => 'net',
+ proto => 'udp',
+ destinationport => '445',
+ order => 411;
+ 'reject_outgoing_smb_tcp':
+ action => 'REJECT',
+ source => 'eip',
+ destination => 'net',
+ proto => 'tcp',
+ destinationport => '445',
+ order => 412;
+ 'reject_outgoing_syslog':
+ action => 'REJECT',
+ source => 'eip',
+ destination => 'net',
+ proto => 'udp',
+ destinationport => 'syslog',
+ order => 413;
+ 'reject_outgoing_gamqowi':
+ action => 'REJECT',
+ source => 'eip',
+ destination => 'net',
+ proto => 'tcp',
+ destinationport => '4661',
+ order => 414;
+ 'reject_outgoing_mneah':
+ action => 'REJECT',
+ source => 'eip',
+ destination => 'net',
+ proto => 'tcp',
+ destinationport => '4666',
+ order => 415;
}
# create dnat rule for each port
diff --git a/puppet/modules/site_shorewall/manifests/ip_forward.pp b/puppet/modules/site_shorewall/manifests/ip_forward.pp
index d53ee8a5..beb1f055 100644
--- a/puppet/modules/site_shorewall/manifests/ip_forward.pp
+++ b/puppet/modules/site_shorewall/manifests/ip_forward.pp
@@ -1,10 +1,11 @@
+# Configure ip forwarding for shorewall
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],
+ notify => Exec['shorewall_check'],
require => [ Class[augeas], Package[shorewall] ];
}
}
diff --git a/puppet/modules/site_shorewall/manifests/mx.pp b/puppet/modules/site_shorewall/manifests/mx.pp
index 332f164e..2500668f 100644
--- a/puppet/modules/site_shorewall/manifests/mx.pp
+++ b/puppet/modules/site_shorewall/manifests/mx.pp
@@ -1,3 +1,4 @@
+# Configure leap-mx shorewall rules
class site_shorewall::mx {
include site_shorewall::defaults
@@ -7,7 +8,7 @@ class site_shorewall::mx {
# define macro for incoming services
file { '/etc/shorewall/macro.leap_mx':
content => "PARAM - - tcp ${smtpd_ports} ",
- notify => Service['shorewall'],
+ notify => Exec['shorewall_check'],
require => Package['shorewall']
}
diff --git a/puppet/modules/site_shorewall/manifests/obfsproxy.pp b/puppet/modules/site_shorewall/manifests/obfsproxy.pp
index 75846705..3c82dc40 100644
--- a/puppet/modules/site_shorewall/manifests/obfsproxy.pp
+++ b/puppet/modules/site_shorewall/manifests/obfsproxy.pp
@@ -10,7 +10,7 @@ class site_shorewall::obfsproxy {
# define macro for incoming services
file { '/etc/shorewall/macro.leap_obfsproxy':
content => "PARAM - - tcp ${scram_port} ",
- notify => Service['shorewall'],
+ notify => Exec['shorewall_check'],
require => Package['shorewall']
}
diff --git a/puppet/modules/site_shorewall/manifests/service/webapp_api.pp b/puppet/modules/site_shorewall/manifests/service/webapp_api.pp
index d3a1aeed..e3ae4200 100644
--- a/puppet/modules/site_shorewall/manifests/service/webapp_api.pp
+++ b/puppet/modules/site_shorewall/manifests/service/webapp_api.pp
@@ -7,7 +7,7 @@ class site_shorewall::service::webapp_api {
# define macro for incoming services
file { '/etc/shorewall/macro.leap_webapp_api':
content => "PARAM - - tcp ${api_port} ",
- notify => Service['shorewall'],
+ notify => Exec['shorewall_check'],
require => Package['shorewall']
}
diff --git a/puppet/modules/site_shorewall/manifests/soledad.pp b/puppet/modules/site_shorewall/manifests/soledad.pp
index 518d8689..5bee07af 100644
--- a/puppet/modules/site_shorewall/manifests/soledad.pp
+++ b/puppet/modules/site_shorewall/manifests/soledad.pp
@@ -1,3 +1,4 @@
+# Setup soledad server
class site_shorewall::soledad {
$soledad = hiera('soledad')
@@ -8,7 +9,7 @@ class site_shorewall::soledad {
# define macro for incoming services
file { '/etc/shorewall/macro.leap_soledad':
content => "PARAM - - tcp ${soledad_port}",
- notify => Service['shorewall'],
+ notify => Exec['shorewall_check'],
require => Package['shorewall']
}
diff --git a/puppet/modules/site_shorewall/manifests/sshd.pp b/puppet/modules/site_shorewall/manifests/sshd.pp
index e2332592..ba129002 100644
--- a/puppet/modules/site_shorewall/manifests/sshd.pp
+++ b/puppet/modules/site_shorewall/manifests/sshd.pp
@@ -9,7 +9,7 @@ class site_shorewall::sshd {
# define macro for incoming sshd
file { '/etc/shorewall/macro.leap_sshd':
content => "PARAM - - tcp ${ssh_port}",
- notify => Service['shorewall'],
+ notify => Exec['shorewall_check'],
require => Package['shorewall']
}
diff --git a/puppet/modules/site_shorewall/manifests/stunnel/server.pp b/puppet/modules/site_shorewall/manifests/stunnel/server.pp
index 798cd631..dae4142a 100644
--- a/puppet/modules/site_shorewall/manifests/stunnel/server.pp
+++ b/puppet/modules/site_shorewall/manifests/stunnel/server.pp
@@ -8,7 +8,7 @@ define site_shorewall::stunnel::server($port) {
file { "/etc/shorewall/macro.stunnel_server_${name}":
content => "PARAM - - tcp ${port}",
- notify => Service['shorewall'],
+ notify => Exec['shorewall_check'],
require => Package['shorewall']
}
shorewall::rule {
diff --git a/puppet/modules/site_shorewall/manifests/tor.pp b/puppet/modules/site_shorewall/manifests/tor.pp
index 324b4844..f4d5ed92 100644
--- a/puppet/modules/site_shorewall/manifests/tor.pp
+++ b/puppet/modules/site_shorewall/manifests/tor.pp
@@ -9,7 +9,7 @@ class site_shorewall::tor {
# define macro for incoming services
file { '/etc/shorewall/macro.leap_tor':
content => "PARAM - - tcp ${tor_port} ",
- notify => Service['shorewall'],
+ notify => Exec['shorewall_check'],
require => Package['shorewall']
}
diff --git a/puppet/modules/site_sshd/manifests/mosh.pp b/puppet/modules/site_sshd/manifests/mosh.pp
index 49f56ca0..5282d239 100644
--- a/puppet/modules/site_sshd/manifests/mosh.pp
+++ b/puppet/modules/site_sshd/manifests/mosh.pp
@@ -1,3 +1,4 @@
+# setup mosh on server
class site_sshd::mosh ( $ensure = present, $ports = '60000-61000' ) {
package { 'mosh':
@@ -7,7 +8,7 @@ class site_sshd::mosh ( $ensure = present, $ports = '60000-61000' ) {
file { '/etc/shorewall/macro.mosh':
ensure => $ensure,
content => "PARAM - - udp ${ports}",
- notify => Service['shorewall'],
+ notify => Exec['shorewall_check'],
require => Package['shorewall'];
}
diff --git a/puppet/modules/site_static/manifests/domain.pp b/puppet/modules/site_static/manifests/domain.pp
index b26cc9e3..6cf2c653 100644
--- a/puppet/modules/site_static/manifests/domain.pp
+++ b/puppet/modules/site_static/manifests/domain.pp
@@ -4,6 +4,7 @@ define site_static::domain (
$key,
$cert,
$tls_only=true,
+ $use_hidden_service=false,
$locations=undef,
$aliases=undef,
$apache_config=undef) {
diff --git a/puppet/modules/site_static/manifests/hidden_service.pp b/puppet/modules/site_static/manifests/hidden_service.pp
new file mode 100644
index 00000000..f1f15f8e
--- /dev/null
+++ b/puppet/modules/site_static/manifests/hidden_service.pp
@@ -0,0 +1,37 @@
+# create hidden service for static sites
+class site_static::hidden_service {
+
+ include tor::daemon
+ tor::daemon::hidden_service { 'static': 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/static/private_key':
+ ensure => present,
+ source => "/srv/leap/files/nodes/${::hostname}/tor.key",
+ owner => 'debian-tor',
+ group => 'debian-tor',
+ mode => '0600',
+ notify => Service['tor'];
+
+ '/var/lib/tor/static/hostname':
+ ensure => present,
+ content => "${::site_static::tor_domain}\n",
+ owner => 'debian-tor',
+ group => 'debian-tor',
+ mode => '0600',
+ notify => Service['tor'];
+ }
+
+ # 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 => ' ' }
+
+ include site_shorewall::tor
+}
+
diff --git a/puppet/modules/site_static/manifests/init.pp b/puppet/modules/site_static/manifests/init.pp
index 4a722d62..dd3f912d 100644
--- a/puppet/modules/site_static/manifests/init.pp
+++ b/puppet/modules/site_static/manifests/init.pp
@@ -7,26 +7,40 @@ class site_static {
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)
+ $static = hiera('static')
+ $domains = $static['domains']
+ $formats = $static['formats']
+ $bootstrap = $static['bootstrap_files']
+ $tor = hiera('tor', false)
+
+ file {
+ '/srv/static/':
+ ensure => 'directory',
+ owner => 'root',
+ group => 'root',
+ mode => '0744';
+ '/srv/static/public':
+ ensure => 'directory',
+ owner => 'root',
+ group => 'root',
+ mode => '0744';
+ }
if $bootstrap['enabled'] {
$bootstrap_domain = $bootstrap['domain']
$bootstrap_client = $bootstrap['client_version']
- file { '/srv/leap/provider.json':
+ file { '/srv/static/public/provider.json':
content => $bootstrap['provider_json'],
owner => 'www-data',
group => 'www-data',
- mode => '0444';
+ mode => '0444',
+ notify => Service[apache];
}
# 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'];
+ exec { '/bin/touch /srv/static/public/provider.json':
+ require => File['/srv/static/public/provider.json'];
}
}
@@ -42,8 +56,8 @@ class site_static {
if (member($formats, 'rack')) {
include site_apt::preferences::passenger
class { 'passenger':
- use_munin => false,
- require => Class['site_apt::preferences::passenger']
+ manage_munin => false,
+ require => Class['site_apt::preferences::passenger']
}
}
@@ -57,15 +71,24 @@ class site_static {
}
}
- create_resources(site_static::domain, $domains)
-
if $tor {
$hidden_service = $tor['hidden_service']
+ $tor_domain = "${hidden_service['address']}.onion"
if $hidden_service['active'] {
- include site_webapp::hidden_service
+ include site_static::hidden_service
+ }
+ # Currently, we only support a single hidden service address per server.
+ # So if there is more than one domain configured, then we need to make sure
+ # we don't enable the hidden service for every domain.
+ if size(keys($domains)) == 1 {
+ $always_use_hidden_service = true
+ } else {
+ $always_use_hidden_service = false
}
}
+ create_resources(site_static::domain, $domains)
+
include site_shorewall::defaults
include site_shorewall::service::http
include site_shorewall::service::https
diff --git a/puppet/modules/site_static/templates/amber.erb b/puppet/modules/site_static/templates/amber.erb
index 694f1136..b34458c3 100644
--- a/puppet/modules/site_static/templates/amber.erb
+++ b/puppet/modules/site_static/templates/amber.erb
@@ -4,10 +4,10 @@
<%- end -%>
<Directory "<%=@directory%>/">
AllowOverride FileInfo Indexes Options=All,MultiViews
-<% if scope.function_guess_apache_version([]) == '2.4' %>
+<%- if scope.function_guess_apache_version([]) == '2.4' -%>
Require all granted
-<% else %>
+<%- else -%>
Order deny,allow
Allow from all
-<% end %>
- </Directory>
+<%- end -%>
+ </Directory> \ No newline at end of file
diff --git a/puppet/modules/site_static/templates/apache.conf.erb b/puppet/modules/site_static/templates/apache.conf.erb
index 6b969d1c..dd04ca43 100644
--- a/puppet/modules/site_static/templates/apache.conf.erb
+++ b/puppet/modules/site_static/templates/apache.conf.erb
@@ -11,6 +11,9 @@
end
end
+ #
+ # document root
+ #
@document_root = begin
root = '/var/www'
@locations && @locations.each do |name, location|
@@ -19,22 +22,104 @@
root.gsub(%r{^/|/$}, '')
end
+ #
+ # provider.json
+ #
+ # if the domain is a bootstrap domain, we need to expose
+ # a /provider.json file.
+ #
bootstrap_domain = scope.lookupvar('site_static::bootstrap_domain')
bootstrap_client = scope.lookupvar('site_static::bootstrap_client')
+ if ([@aliases]+[@domain]).flatten.include?(bootstrap_domain)
+ provider_json = \
+%(
+ Alias /provider.json /srv/static/public/provider.json
+ <Location /provider.json>
+ Header set X-Minimum-Client-Version #{bootstrap_client['min']}
+ </Location>
+)
+ else
+ provider_json = ""
+ end
+
+ #
+ # locations
+ #
+ locations = ""
+ @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)
+ locations += \
+%(
+ #
+ # #{name} (#{location['format']})
+ #
+#{scope.function_templatewlv([template_path, local_vars])}
+)
+ end
+
+ #
+ # allow custom apache config
+ #
+ custom_apache_config = if @apache_config
+ @apache_config.gsub(':percent:','%')
+ end
+
-%>
+<Directory "/srv/static/public/">
+ Require all granted
+</Directory>
+
+<%- if @tor && (@always_use_hidden_service || @use_hidden_service) -%>
+##
+## Tor
+##
+<VirtualHost 127.0.0.1:80>
+ ServerName <%= @tor_domain %>
+ ServerAlias www.<%= @tor_domain %>
+
+ <IfModule mod_headers.c>
+ Header set X-Frame-Options "deny"
+ Header always unset X-Powered-By
+ Header always unset X-Runtime
+ </IfModule>
+
+ DocumentRoot "/<%= @document_root %>/"
+ AccessFileName .htaccess
+
+<%= provider_json %>
+<%= custom_apache_config %>
+<%= locations %>
+</VirtualHost>
+<%- end -%>
+
+##
+## HTTP
+##
<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]
+<%- else -%>
+<%= provider_json %>
+<%= custom_apache_config %>
+<%= locations %>
<%- end -%>
</VirtualHost>
+##
+## HTTPS
+##
<VirtualHost *:443>
ServerName <%= @domain %>
ServerAlias www.<%= @domain %>
@@ -46,13 +131,15 @@
#RewriteLogLevel 3
Include include.d/ssl_common.inc
-
+
+ <IfModule mod_headers.c>
<%- if @tls_only -%>
- Header always set Strict-Transport-Security: "max-age=15768000;includeSubdomains"
+ 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
+ Header set X-Frame-Options "deny"
+ Header always unset X-Powered-By
+ Header always unset X-Runtime
+ </IfModule>
SSLCertificateKeyFile /etc/x509/keys/<%= @domain %>.key
SSLCertificateFile /etc/x509/certs/<%= @domain %>.crt
@@ -62,27 +149,7 @@
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 -%>
-
+<%= provider_json %>
+<%= custom_apache_config %>
+<%= locations %>
</VirtualHost>
diff --git a/puppet/modules/site_static/templates/rack.erb b/puppet/modules/site_static/templates/rack.erb
index 431778bb..1cbf84d2 100644
--- a/puppet/modules/site_static/templates/rack.erb
+++ b/puppet/modules/site_static/templates/rack.erb
@@ -10,10 +10,10 @@
<%- end -%>
<Directory "<%=@directory%>">
Options -MultiViews
-<% if scope.function_guess_apache_version([]) == '2.4' %>
+<%- if scope.function_guess_apache_version([]) == '2.4' -%>
Require all granted
-<% else %>
+<%- else -%>
Order deny,allow
Allow from all
-<% end %>
+<%- end -%>
</Directory>
diff --git a/puppet/modules/site_webapp/manifests/apache.pp b/puppet/modules/site_webapp/manifests/apache.pp
index 80c7b29b..e559217d 100644
--- a/puppet/modules/site_webapp/manifests/apache.pp
+++ b/puppet/modules/site_webapp/manifests/apache.pp
@@ -18,7 +18,7 @@ class site_webapp::apache {
include apache::module::removeip
include site_webapp::common_vhost
- class { 'passenger': use_munin => false }
+ class { 'passenger': }
apache::vhost::file {
'api':
diff --git a/puppet/modules/site_webapp/manifests/hidden_service.pp b/puppet/modules/site_webapp/manifests/hidden_service.pp
index 72a2ce95..d2662b65 100644
--- a/puppet/modules/site_webapp/manifests/hidden_service.pp
+++ b/puppet/modules/site_webapp/manifests/hidden_service.pp
@@ -1,3 +1,4 @@
+# Configure tor hidden service for webapp
class site_webapp::hidden_service {
$tor = hiera('tor')
$hidden_service = $tor['hidden_service']
@@ -8,7 +9,7 @@ class site_webapp::hidden_service {
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'] }
@@ -24,14 +25,16 @@ class site_webapp::hidden_service {
source => "/srv/leap/files/nodes/${::hostname}/tor.key",
owner => 'debian-tor',
group => 'debian-tor',
- mode => '0600';
+ mode => '0600',
+ notify => Service['tor'];
'/var/lib/tor/webapp/hostname':
ensure => present,
- content => $tor_domain,
+ content => "${tor_domain}\n",
owner => 'debian-tor',
group => 'debian-tor',
- mode => '0600';
+ mode => '0600',
+ notify => Service['tor'];
}
# it is necessary to zero out the config of the status module
@@ -40,7 +43,7 @@ class site_webapp::hidden_service {
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');
diff --git a/puppet/modules/site_webapp/manifests/init.pp b/puppet/modules/site_webapp/manifests/init.pp
index 15925aba..83cf99a9 100644
--- a/puppet/modules/site_webapp/manifests/init.pp
+++ b/puppet/modules/site_webapp/manifests/init.pp
@@ -16,21 +16,22 @@ class site_webapp {
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
+ 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
+ include ::site_apt::preferences::twisted
# remove leftovers from previous installations on webapp nodes
- include site_config::remove::webapp
+ include ::site_config::remove::webapp
group { 'leap-webapp':
ensure => present,
@@ -91,12 +92,16 @@ class site_webapp {
'/srv/leap/webapp/config/provider':
ensure => directory,
require => Vcsrepo['/srv/leap/webapp'],
- owner => leap-webapp, group => leap-webapp, mode => '0755';
+ 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';
+ owner => 'leap-webapp',
+ group => 'leap-webapp',
+ mode => '0644';
'/srv/leap/webapp/public/ca.crt':
ensure => link,
@@ -106,27 +111,37 @@ class site_webapp {
"/srv/leap/webapp/public/${api_version}":
ensure => directory,
require => Vcsrepo['/srv/leap/webapp'],
- owner => leap-webapp, group => leap-webapp, mode => '0755';
+ 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';
+ 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';
+ 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';
+ 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';
+ owner => 'leap-webapp',
+ group => 'leap-webapp',
+ mode => '0644';
}
try::file {
@@ -135,8 +150,8 @@ class site_webapp {
recurse => true,
purge => true,
force => true,
- owner => leap-webapp,
- group => leap-webapp,
+ owner => 'leap-webapp',
+ group => 'leap-webapp',
mode => 'u=rwX,go=rX',
require => Vcsrepo['/srv/leap/webapp'],
notify => Exec['compile_assets'],
@@ -153,8 +168,8 @@ class site_webapp {
file {
'/srv/leap/webapp/config/config.yml':
content => template('site_webapp/config.yml.erb'),
- owner => leap-webapp,
- group => leap-webapp,
+ owner => 'leap-webapp',
+ group => 'leap-webapp',
mode => '0600',
require => Vcsrepo['/srv/leap/webapp'],
notify => Service['apache'];
@@ -163,17 +178,17 @@ class site_webapp {
if $tor {
$hidden_service = $tor['hidden_service']
if $hidden_service['active'] {
- include site_webapp::hidden_service
+ include ::site_webapp::hidden_service
}
}
# needed for the soledad-sync check which is run on the
# webapp node
- include soledad::client
+ include ::soledad::client
leap::logfile { 'webapp': }
- include site_shorewall::webapp
- include site_check_mk::agent::webapp
+ include ::site_shorewall::webapp
+ include ::site_check_mk::agent::webapp
}
diff --git a/puppet/modules/soledad/manifests/common.pp b/puppet/modules/soledad/manifests/common.pp
index 8d8339d4..35969362 100644
--- a/puppet/modules/soledad/manifests/common.pp
+++ b/puppet/modules/soledad/manifests/common.pp
@@ -1,6 +1,9 @@
# install soledad-common, both needed both soledad-client and soledad-server
class soledad::common {
+ # install twisted from jessie backports
+ include ::site_apt::preferences::twisted
+
package { 'soledad-common':
ensure => latest;
}
diff --git a/puppet/modules/soledad/manifests/server.pp b/puppet/modules/soledad/manifests/server.pp
index 6cf806d0..81f51188 100644
--- a/puppet/modules/soledad/manifests/server.pp
+++ b/puppet/modules/soledad/manifests/server.pp
@@ -2,8 +2,9 @@
class soledad::server {
tag 'leap_service'
- include site_config::default
- include soledad::common
+ include ::site_config::default
+ include ::soledad::common
+ include ::site_config::remove::soledad
$soledad = hiera('soledad')
$couchdb_user = $soledad['couchdb_soledad_user']['username']
@@ -90,11 +91,11 @@ class soledad::server {
}
user {
'soledad':
- ensure => present,
- system => true,
- gid => 'soledad',
- home => '/srv/leap/soledad',
- require => Group['soledad'];
+ ensure => present,
+ system => true,
+ gid => 'soledad',
+ home => '/srv/leap/soledad',
+ require => Group['soledad'];
'soledad-admin':
ensure => present,
system => true,
diff --git a/puppet/modules/squid_deb_proxy b/puppet/modules/squid_deb_proxy
deleted file mode 160000
-Subproject e796aac43aa9781069e167459253d040504c209
diff --git a/puppet/modules/squid_deb_proxy/.gitrepo b/puppet/modules/squid_deb_proxy/.gitrepo
new file mode 100644
index 00000000..78765952
--- /dev/null
+++ b/puppet/modules/squid_deb_proxy/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_squid_deb_proxy
+ branch = master
+ commit = 08bfaccaea01fd2d334946428504e71a51748e3d
+ parent = a658f5c30ada5e03468257f90d08f6cd2ba25488
+ cmdver = 0.3.0
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 b/puppet/modules/sshd
deleted file mode 160000
-Subproject 76f4f872f81209a52df2205fd88b5619df58f00
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/.gitrepo b/puppet/modules/sshd/.gitrepo
new file mode 100644
index 00000000..70f55711
--- /dev/null
+++ b/puppet/modules/sshd/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_sshd
+ branch = master
+ commit = 76f4f872f81209a52df2205fd88b5619df58f003
+ parent = b85f8c1b914a09b6001d4c1b5c7d07ef17ac766f
+ cmdver = 0.3.0
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 b/puppet/modules/stdlib
deleted file mode 160000
-Subproject 71123634744b9fe2ec7d6a3e38e9789fd84801e
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/.gitrepo b/puppet/modules/stdlib/.gitrepo
new file mode 100644
index 00000000..300f04b0
--- /dev/null
+++ b/puppet/modules/stdlib/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_stdlib
+ branch = master
+ commit = 71123634744b9fe2ec7d6a3e38e9789fd84801e3
+ parent = 95374aacb857ed35c2fdfe6be7c0bfab86653963
+ cmdver = 0.3.0
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 b/puppet/modules/stunnel
deleted file mode 160000
-Subproject 523612fb6daff51837423619f5014e62dc83555
diff --git a/puppet/modules/stunnel/.gitrepo b/puppet/modules/stunnel/.gitrepo
new file mode 100644
index 00000000..d7540f10
--- /dev/null
+++ b/puppet/modules/stunnel/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_stunnel
+ branch = master
+ commit = 523612fb6daff51837423619f5014e62dc835559
+ parent = 297fadc8e6ad4729589d4ec21683f05a1e50bdf9
+ cmdver = 0.3.0
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..1135b98d
--- /dev/null
+++ b/puppet/modules/stunnel/manifests/debian.pp
@@ -0,0 +1,23 @@
+class stunnel::debian inherits stunnel::linux {
+
+ Package['stunnel'] {
+ name => 'stunnel4',
+ }
+
+ Service['stunnel'] {
+ name => 'stunnel4',
+ pattern => '/usr/bin/stunnel4',
+ subscribe => File['/etc/default/stunnel4'],
+ require => Package['stunnel4']
+ }
+
+ file { '/etc/default/stunnel4':
+ content => template('stunnel/Debian/default'),
+ before => 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..544ac04e
--- /dev/null
+++ b/puppet/modules/stunnel/manifests/init.pp
@@ -0,0 +1,65 @@
+#
+# 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,
+ notify => Exec['refresh_stunnel'];
+
+ "${stunnel_staging}/bin/refresh_stunnel.sh":
+ owner => 0,
+ group => 0,
+ mode => '0755',
+ content => template('stunnel/refresh_stunnel.sh.erb');
+ }
+
+ exec { 'refresh_stunnel':
+ command => "${stunnel_staging}/bin/refresh_stunnel.sh",
+ require => [ Package['stunnel4'], File['/etc/default/stunnel4'] ]
+ }
+}
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 b/puppet/modules/sysctl
deleted file mode 160000
-Subproject 975852b7acc1125b4cd9d4d490b9abd8d31217e
diff --git a/puppet/modules/sysctl/.gitrepo b/puppet/modules/sysctl/.gitrepo
new file mode 100644
index 00000000..a6d7f8fe
--- /dev/null
+++ b/puppet/modules/sysctl/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_sysctl
+ branch = master
+ commit = 975852b7acc1125b4cd9d4d490b9abd8d31217e6
+ parent = 6a895ece94a86c9ccc32c9bec51413d4e4f0df8e
+ cmdver = 0.3.0
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 b/puppet/modules/systemd
deleted file mode 160000
-Subproject 6d47fd4999fe03eba6fb11c4490dcbb90d93790
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/.gitrepo b/puppet/modules/systemd/.gitrepo
new file mode 100644
index 00000000..1548a815
--- /dev/null
+++ b/puppet/modules/systemd/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_systemd
+ branch = master
+ commit = 6d47fd4999fe03eba6fb11c4490dcbb90d937900
+ parent = 56a771a3008d10720dd05fd815aeafbacdd1e08e
+ cmdver = 0.3.0
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/puppet/modules/systemd/.sync.yml b/puppet/modules/systemd/.sync.yml
new file mode 100644
index 00000000..5fffcb05
--- /dev/null
+++ b/puppet/modules/systemd/.sync.yml
@@ -0,0 +1,3 @@
+---
+.travis.yml:
+ forge_password: "ASTRdmLjJNa1NvHy2LRGvmvUeth6W3Fh/alYWvcvI8nDDsdkweHk0iXhcXZwtMQReb0NI5vJiRNXNy7a3XySC4+SP3hfHuDU58H2FqC4Ff0EHRPRHTEiXf7xmN53RxXYXZQvrFfqUb6tIsBNVKVmsYWNe01k8NVKPyYDfQB75PQ="
diff --git a/puppet/modules/systemd/.travis.yml b/puppet/modules/systemd/.travis.yml
new file mode 100644
index 00000000..467045c5
--- /dev/null
+++ b/puppet/modules/systemd/.travis.yml
@@ -0,0 +1,32 @@
+---
+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"
+ - rvm: 2.1.6
+ env: PUPPET_GEM_VERSION="~> 4.0"
+notifications:
+ email: false
+deploy:
+ provider: puppetforge
+ user: camptocamp
+ password:
+ secure: "ASTRdmLjJNa1NvHy2LRGvmvUeth6W3Fh/alYWvcvI8nDDsdkweHk0iXhcXZwtMQReb0NI5vJiRNXNy7a3XySC4+SP3hfHuDU58H2FqC4Ff0EHRPRHTEiXf7xmN53RxXYXZQvrFfqUb6tIsBNVKVmsYWNe01k8NVKPyYDfQB75PQ="
+ 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/systemd/CHANGELOG.md b/puppet/modules/systemd/CHANGELOG.md
new file mode 100644
index 00000000..11e84399
--- /dev/null
+++ b/puppet/modules/systemd/CHANGELOG.md
@@ -0,0 +1,65 @@
+# Change Log
+
+## [0.2.2](https://forge.puppetlabs.com/camptocamp/systemd/0.2.2) (2015-08-25)
+[Full Changelog](https://github.com/camptocamp/puppet-systemd/compare/0.2.1...0.2.2)
+
+**Implemented enhancements:**
+
+- Add 'systemd-tmpfiles-create' [\#1](https://github.com/camptocamp/puppet-systemd/pull/1) ([roidelapluie](https://github.com/roidelapluie))
+
+
+## [0.2.1](https://forge.puppetlabs.com/camptocamp/systemd/0.2.1) (2015-08-21)
+[Full Changelog](https://github.com/camptocamp/puppet-systemd/compare/0.2.0...0.2.1)
+
+- Use docker for acceptance tests
+
+## [0.1.15](https://forge.puppetlabs.com/camptocamp/systemd/0.1.15) (2015-06-26)
+[Full Changelog](https://github.com/camptocamp/puppet-systemd/compare/0.1.14...0.1.15)
+
+- Fix strict_variables activation with rspec-puppet 2.2
+
+## [0.1.14](https://forge.puppetlabs.com/camptocamp/systemd/0.1.14) (2015-05-28)
+[Full Changelog](https://github.com/camptocamp/puppet-systemd/compare/0.1.13...0.1.14)
+
+- Add beaker_spec_helper to Gemfile
+
+## [0.1.13](https://forge.puppetlabs.com/camptocamp/systemd/0.1.13) (2015-05-26)
+[Full Changelog](https://github.com/camptocamp/puppet-systemd/compare/0.1.12...0.1.13)
+
+- Use random application order in nodeset
+
+## [0.1.12](https://forge.puppetlabs.com/camptocamp/systemd/0.1.12) (2015-05-26)
+[Full Changelog](https://github.com/camptocamp/puppet-systemd/compare/0.1.11...0.1.12)
+
+- Add utopic & vivid nodesets
+
+## [0.1.11](https://forge.puppetlabs.com/camptocamp/systemd/0.1.11) (2015-05-25)
+[Full Changelog](https://github.com/camptocamp/puppet-systemd/compare/0.1.10...0.1.11)
+
+- Don't allow failure on Puppet 4
+
+## [0.1.10](https://forge.puppetlabs.com/camptocamp/systemd/0.1.10) (2015-05-13)
+[Full Changelog](https://github.com/camptocamp/puppet-systemd/compare/0.1.9...0.1.10)
+
+- Add puppet-lint-file_source_rights-check gem
+
+## [0.1.9](https://forge.puppetlabs.com/camptocamp/systemd/0.1.9) (2015-05-12)
+[Full Changelog](https://github.com/camptocamp/puppet-systemd/compare/0.1.8...0.1.9)
+
+- Don't pin beaker
+
+## [0.1.8](https://forge.puppetlabs.com/camptocamp/systemd/0.1.8) (2015-04-27)
+[Full Changelog](https://github.com/camptocamp/puppet-systemd/compare/0.1.7...0.1.8)
+
+- Add nodeset ubuntu-12.04-x86_64-openstack
+
+## [0.1.7](https://forge.puppetlabs.com/camptocamp/systemd/0.1.7) (2015-04-03)
+[Full Changelog](https://github.com/camptocamp/puppet-systemd/compare/0.1.6...0.1.7)
+
+- Confine rspec pinning to ruby 1.8
+
+
+\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
+
+
+\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* \ No newline at end of file
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/puppet/modules/systemd/HISTORY.md b/puppet/modules/systemd/HISTORY.md
new file mode 100644
index 00000000..c7bf2b4e
--- /dev/null
+++ b/puppet/modules/systemd/HISTORY.md
@@ -0,0 +1,62 @@
+## [0.2.2](https://forge.puppetlabs.com/camptocamp/systemd/0.2.2) (2015-08-25)
+[Full Changelog](https://github.com/camptocamp/puppet-systemd/compare/0.2.1...0.2.2)
+
+**Implemented enhancements:**
+
+- Add 'systemd-tmpfiles-create' [\#1](https://github.com/camptocamp/puppet-systemd/pull/1) ([roidelapluie](https://github.com/roidelapluie))
+
+## [0.2.1](https://forge.puppetlabs.com/camptocamp/systemd/0.2.1) (2015-08-21)
+[Full Changelog](https://github.com/camptocamp/puppet-systemd/compare/0.2.0...0.2.1)
+
+- Use docker for acceptance tests
+
+## [0.1.15](https://forge.puppetlabs.com/camptocamp/systemd/0.1.15) (2015-06-26)
+[Full Changelog](https://github.com/camptocamp/puppet-systemd/compare/0.1.14...0.1.15)
+
+- Fix strict_variables activation with rspec-puppet 2.2
+
+## [0.1.14](https://forge.puppetlabs.com/camptocamp/systemd/0.1.14) (2015-05-28)
+[Full Changelog](https://github.com/camptocamp/puppet-systemd/compare/0.1.13...0.1.14)
+
+- Add beaker_spec_helper to Gemfile
+
+## [0.1.13](https://forge.puppetlabs.com/camptocamp/systemd/0.1.13) (2015-05-26)
+[Full Changelog](https://github.com/camptocamp/puppet-systemd/compare/0.1.12...0.1.13)
+
+- Use random application order in nodeset
+
+## [0.1.12](https://forge.puppetlabs.com/camptocamp/systemd/0.1.12) (2015-05-26)
+[Full Changelog](https://github.com/camptocamp/puppet-systemd/compare/0.1.11...0.1.12)
+
+- Add utopic & vivid nodesets
+
+## [0.1.11](https://forge.puppetlabs.com/camptocamp/systemd/0.1.11) (2015-05-25)
+[Full Changelog](https://github.com/camptocamp/puppet-systemd/compare/0.1.10...0.1.11)
+
+- Don't allow failure on Puppet 4
+
+## [0.1.10](https://forge.puppetlabs.com/camptocamp/systemd/0.1.10) (2015-05-13)
+[Full Changelog](https://github.com/camptocamp/puppet-systemd/compare/0.1.9...0.1.10)
+
+- Add puppet-lint-file_source_rights-check gem
+
+## [0.1.9](https://forge.puppetlabs.com/camptocamp/systemd/0.1.9) (2015-05-12)
+[Full Changelog](https://github.com/camptocamp/puppet-systemd/compare/0.1.8...0.1.9)
+
+- Don't pin beaker
+
+## [0.1.8](https://forge.puppetlabs.com/camptocamp/systemd/0.1.8) (2015-04-27)
+[Full Changelog](https://github.com/camptocamp/puppet-systemd/compare/0.1.7...0.1.8)
+
+- Add nodeset ubuntu-12.04-x86_64-openstack
+
+## [0.1.7](https://forge.puppetlabs.com/camptocamp/systemd/0.1.7) (2015-04-03)
+[Full Changelog](https://github.com/camptocamp/puppet-systemd/compare/0.1.6...0.1.7)
+
+- Confine rspec pinning to ruby 1.8
+
+
+\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
+
+
+\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
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/puppet/modules/systemd/manifests/enable.pp b/puppet/modules/systemd/manifests/enable.pp
new file mode 100644
index 00000000..e1bee18a
--- /dev/null
+++ b/puppet/modules/systemd/manifests/enable.pp
@@ -0,0 +1,8 @@
+# enables a systemd resource
+define systemd::enable () {
+
+ exec { "enable_systemd_${name}":
+ refreshonly => true,
+ command => "/bin/systemctl enable ${name}"
+ }
+}
diff --git a/puppet/modules/systemd/manifests/init.pp b/puppet/modules/systemd/manifests/init.pp
new file mode 100644
index 00000000..5e6ad792
--- /dev/null
+++ b/puppet/modules/systemd/manifests/init.pp
@@ -0,0 +1,18 @@
+class systemd {
+
+ Exec {
+ refreshonly => true,
+ path => $::path,
+ }
+
+ exec {
+ 'systemctl-daemon-reload':
+ command => 'systemctl daemon-reload',
+ }
+
+ exec {
+ 'systemd-tmpfiles-create':
+ command => 'systemd-tmpfiles --create',
+ }
+
+}
diff --git a/puppet/modules/systemd/metadata.json b/puppet/modules/systemd/metadata.json
new file mode 100644
index 00000000..abdd481e
--- /dev/null
+++ b/puppet/modules/systemd/metadata.json
@@ -0,0 +1,48 @@
+{
+ "name": "camptocamp-systemd",
+ "version": "0.2.2",
+ "author": "camptocamp",
+ "summary": "Puppet Systemd module",
+ "license": "Apache-2.0",
+ "source": "https://github.com/camptocamp/puppet-systemd",
+ "project_page": "https://github.com/camptocamp/puppet-systemd",
+ "issues_url": "https://github.com/camptocamp/puppet-systemd/issues",
+ "dependencies": [
+
+ ],
+ "requirements": [
+ {
+ "name": "pe",
+ "version_requirement": "3.x"
+ },
+ {
+ "name": "puppet",
+ "version_requirement": "3.x"
+ }
+ ],
+ "operatingsystem_support": [
+ {
+ "operatingsystem": "Debian",
+ "operatingsystemrelease": [
+ "8"
+ ]
+ },
+ {
+ "operatingsystem": "RedHat",
+ "operatingsystemrelease": [
+ "7"
+ ]
+ }
+ ],
+ "puppet_version": [
+ "2.7",
+ "3.0",
+ "3.1",
+ "3.2",
+ "3.3",
+ "3.4",
+ "3.5",
+ "3.6",
+ "3.7"
+ ]
+}
diff --git a/puppet/modules/systemd/spec/acceptance/nodesets/centos-5-x86_64-docker.yml b/puppet/modules/systemd/spec/acceptance/nodesets/centos-5-x86_64-docker.yml
new file mode 100644
index 00000000..679afb04
--- /dev/null
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/centos-5-x86_64-docker.yml
@@ -0,0 +1,15 @@
+HOSTS:
+ centos-5-x64:
+ default_apply_opts:
+ order: random
+ strict_variables:
+ platform: el-5-x86_64
+ hypervisor : docker
+ image: centos:5
+ docker_preserve_image: true
+ docker_cmd: '["/sbin/init"]'
+ docker_image_commands:
+ - 'yum install -y crontabs tar wget'
+CONFIG:
+ type: foss
+ log_level: debug
diff --git a/puppet/modules/systemd/spec/acceptance/nodesets/centos-6-x86_64-docker.yml b/puppet/modules/systemd/spec/acceptance/nodesets/centos-6-x86_64-docker.yml
new file mode 100644
index 00000000..9cab03d0
--- /dev/null
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/centos-6-x86_64-docker.yml
@@ -0,0 +1,15 @@
+HOSTS:
+ centos-6-x64:
+ default_apply_opts:
+ order: random
+ strict_variables:
+ platform: el-6-x86_64
+ hypervisor : docker
+ image: centos:6
+ docker_preserve_image: true
+ docker_cmd: '["/sbin/init"]'
+ docker_image_commands:
+ - 'yum install -y crontabs tar wget'
+CONFIG:
+ type: foss
+ log_level: debug
diff --git a/puppet/modules/systemd/spec/acceptance/nodesets/centos-6-x86_64-openstack.yml b/puppet/modules/systemd/spec/acceptance/nodesets/centos-6-x86_64-openstack.yml
new file mode 100644
index 00000000..e325b9e9
--- /dev/null
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/centos-6-x86_64-openstack.yml
@@ -0,0 +1,14 @@
+HOSTS:
+ centos-6-x64:
+ default_apply_opts:
+ order: random
+ strict_variables:
+ platform: el-6-x86_64
+ hypervisor : openstack
+ flavor: m1.small
+ image: centos-6-latest
+ user: root
+CONFIG:
+ type: foss
+ log_level: debug
+ openstack_network: default
diff --git a/puppet/modules/systemd/spec/acceptance/nodesets/centos-6-x86_64-vagrant.yml b/puppet/modules/systemd/spec/acceptance/nodesets/centos-6-x86_64-vagrant.yml
new file mode 100644
index 00000000..f06036ec
--- /dev/null
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/centos-6-x86_64-vagrant.yml
@@ -0,0 +1,11 @@
+HOSTS:
+ centos-6-x64:
+ default_apply_opts:
+ order: random
+ strict_variables:
+ platform: el-6-x86_64
+ hypervisor : vagrant
+ box : camptocamp/centos-6-x86_64
+CONFIG:
+ type: foss
+ log_level: debug
diff --git a/puppet/modules/systemd/spec/acceptance/nodesets/centos-7-x86_64-docker.yml b/puppet/modules/systemd/spec/acceptance/nodesets/centos-7-x86_64-docker.yml
new file mode 100644
index 00000000..0bc97271
--- /dev/null
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/centos-7-x86_64-docker.yml
@@ -0,0 +1,15 @@
+HOSTS:
+ centos-7-x64:
+ default_apply_opts:
+ order: random
+ strict_variables:
+ platform: el-7-x86_64
+ hypervisor : docker
+ image: centos:7
+ docker_preserve_image: true
+ docker_cmd: '["/usr/sbin/init"]'
+ docker_image_commands:
+ - 'yum install -y crontabs tar wget'
+CONFIG:
+ type: foss
+ log_level: debug
diff --git a/puppet/modules/systemd/spec/acceptance/nodesets/centos-7-x86_64-openstack.yml b/puppet/modules/systemd/spec/acceptance/nodesets/centos-7-x86_64-openstack.yml
new file mode 100644
index 00000000..9003c867
--- /dev/null
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/centos-7-x86_64-openstack.yml
@@ -0,0 +1,14 @@
+HOSTS:
+ centos-7-x64:
+ default_apply_opts:
+ order: random
+ strict_variables:
+ platform: el-7-x86_64
+ hypervisor : openstack
+ flavor: m1.small
+ image: centos-7-latest
+ user: centos
+CONFIG:
+ type: foss
+ log_level: debug
+ openstack_network: default
diff --git a/puppet/modules/systemd/spec/acceptance/nodesets/centos-7-x86_64-vagrant.yml b/puppet/modules/systemd/spec/acceptance/nodesets/centos-7-x86_64-vagrant.yml
new file mode 100644
index 00000000..95402e54
--- /dev/null
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/centos-7-x86_64-vagrant.yml
@@ -0,0 +1,11 @@
+HOSTS:
+ centos-7-x64:
+ default_apply_opts:
+ order: random
+ strict_variables:
+ platform: el-7-x86_64
+ hypervisor : vagrant
+ box : camptocamp/centos-7-x86_64
+CONFIG:
+ type: foss
+ log_level: debug
diff --git a/puppet/modules/systemd/spec/acceptance/nodesets/debian-6-x86_64-docker.yml b/puppet/modules/systemd/spec/acceptance/nodesets/debian-6-x86_64-docker.yml
new file mode 100644
index 00000000..359dae7d
--- /dev/null
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/debian-6-x86_64-docker.yml
@@ -0,0 +1,15 @@
+HOSTS:
+ debian-6-x64:
+ default_apply_opts:
+ order: random
+ strict_variables:
+ platform: debian-6-amd64
+ hypervisor : docker
+ image: debian:6
+ docker_preserve_image: true
+ docker_cmd: '["/sbin/init"]'
+ docker_image_commands:
+ - 'apt-get install -y wget'
+CONFIG:
+ type: foss
+ log_level: debug
diff --git a/puppet/modules/systemd/spec/acceptance/nodesets/debian-6-x86_64-openstack.yml b/puppet/modules/systemd/spec/acceptance/nodesets/debian-6-x86_64-openstack.yml
new file mode 100644
index 00000000..c6c192fe
--- /dev/null
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/debian-6-x86_64-openstack.yml
@@ -0,0 +1,14 @@
+HOSTS:
+ debian-6-x64:
+ default_apply_opts:
+ order: random
+ strict_variables:
+ platform: debian-6-amd64
+ hypervisor : openstack
+ flavor: m1.small
+ image: debian-6-latest
+ user: debian
+CONFIG:
+ type: foss
+ log_level: debug
+ openstack_network: default
diff --git a/puppet/modules/systemd/spec/acceptance/nodesets/debian-6-x86_64-vagrant.yml b/puppet/modules/systemd/spec/acceptance/nodesets/debian-6-x86_64-vagrant.yml
new file mode 100644
index 00000000..03db0fa7
--- /dev/null
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/debian-6-x86_64-vagrant.yml
@@ -0,0 +1,11 @@
+HOSTS:
+ debian-6-x64:
+ default_apply_opts:
+ order: random
+ 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/systemd/spec/acceptance/nodesets/debian-7-x86_64-docker.yml b/puppet/modules/systemd/spec/acceptance/nodesets/debian-7-x86_64-docker.yml
new file mode 100644
index 00000000..fc11f574
--- /dev/null
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/debian-7-x86_64-docker.yml
@@ -0,0 +1,15 @@
+HOSTS:
+ debian-7-x64:
+ default_apply_opts:
+ order: random
+ strict_variables:
+ platform: debian-7-amd64
+ hypervisor : docker
+ image: debian:7
+ docker_preserve_image: true
+ docker_cmd: '["/sbin/init"]'
+ docker_image_commands:
+ - 'apt-get install -y cron wget'
+CONFIG:
+ type: foss
+ log_level: debug
diff --git a/puppet/modules/systemd/spec/acceptance/nodesets/debian-7-x86_64-openstack.yml b/puppet/modules/systemd/spec/acceptance/nodesets/debian-7-x86_64-openstack.yml
new file mode 100644
index 00000000..017b4c74
--- /dev/null
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/debian-7-x86_64-openstack.yml
@@ -0,0 +1,14 @@
+HOSTS:
+ debian-7-x64:
+ default_apply_opts:
+ order: random
+ strict_variables:
+ platform: debian-7-amd64
+ hypervisor : openstack
+ flavor: m1.small
+ image: debian-7-latest
+ user: debian
+CONFIG:
+ type: foss
+ log_level: debug
+ openstack_network: default
diff --git a/puppet/modules/systemd/spec/acceptance/nodesets/debian-7-x86_64-vagrant.yml b/puppet/modules/systemd/spec/acceptance/nodesets/debian-7-x86_64-vagrant.yml
new file mode 100644
index 00000000..8ed1264d
--- /dev/null
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/debian-7-x86_64-vagrant.yml
@@ -0,0 +1,11 @@
+HOSTS:
+ debian-7-x64:
+ default_apply_opts:
+ order: random
+ strict_variables:
+ platform: debian-7-amd64
+ hypervisor : vagrant
+ box : camptocamp/debian-7-amd64
+CONFIG:
+ type: foss
+ log_level: debug
diff --git a/puppet/modules/systemd/spec/acceptance/nodesets/debian-8-x86_64-docker.yml b/puppet/modules/systemd/spec/acceptance/nodesets/debian-8-x86_64-docker.yml
new file mode 100644
index 00000000..86a55e15
--- /dev/null
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/debian-8-x86_64-docker.yml
@@ -0,0 +1,15 @@
+HOSTS:
+ debian-8-x64:
+ default_apply_opts:
+ order: random
+ strict_variables:
+ platform: debian-8-amd64
+ hypervisor : docker
+ image: debian:8
+ docker_preserve_image: true
+ docker_cmd: '["/sbin/init"]'
+ docker_image_commands:
+ - 'apt-get install -y cron wget'
+CONFIG:
+ type: foss
+ log_level: debug
diff --git a/puppet/modules/systemd/spec/acceptance/nodesets/debian-8-x86_64-openstack.yml b/puppet/modules/systemd/spec/acceptance/nodesets/debian-8-x86_64-openstack.yml
new file mode 100644
index 00000000..003b6f4b
--- /dev/null
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/debian-8-x86_64-openstack.yml
@@ -0,0 +1,14 @@
+HOSTS:
+ debian-8-x64:
+ default_apply_opts:
+ order: random
+ strict_variables:
+ platform: debian-8-amd64
+ hypervisor : openstack
+ flavor: m1.small
+ image: debian-8-latest
+ user: debian
+CONFIG:
+ type: foss
+ log_level: debug
+ openstack_network: default
diff --git a/puppet/modules/systemd/spec/acceptance/nodesets/debian-8-x86_64-vagrant.yml b/puppet/modules/systemd/spec/acceptance/nodesets/debian-8-x86_64-vagrant.yml
new file mode 100644
index 00000000..5cc7f0c5
--- /dev/null
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/debian-8-x86_64-vagrant.yml
@@ -0,0 +1,11 @@
+HOSTS:
+ debian-8-x64:
+ default_apply_opts:
+ order: random
+ strict_variables:
+ platform: debian-8-amd64
+ hypervisor : vagrant
+ box : camptocamp/debian-8-amd64
+CONFIG:
+ type: foss
+ log_level: debug
diff --git a/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-10.04-x86_64-docker.yml b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-10.04-x86_64-docker.yml
new file mode 100644
index 00000000..933dee60
--- /dev/null
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-10.04-x86_64-docker.yml
@@ -0,0 +1,13 @@
+HOSTS:
+ ubuntu-1004-x64:
+ default_apply_opts:
+ order: random
+ strict_variables:
+ platform: ubuntu-10.04-amd64
+ hypervisor : docker
+ image: ubuntu:10.04
+ # 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/systemd/spec/acceptance/nodesets/ubuntu-12.04-x86_64-docker.yml b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-12.04-x86_64-docker.yml
new file mode 100644
index 00000000..f0ec72b8
--- /dev/null
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-12.04-x86_64-docker.yml
@@ -0,0 +1,15 @@
+HOSTS:
+ ubuntu-1204-x64:
+ default_apply_opts:
+ order: random
+ strict_variables:
+ platform: ubuntu-12.04-amd64
+ hypervisor : docker
+ image: ubuntu:12.04
+ docker_preserve_image: true
+ docker_cmd: '["/sbin/init"]'
+ docker_image_commands:
+ - 'apt-get install -y wget'
+CONFIG:
+ type: foss
+ log_level: debug
diff --git a/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-12.04-x86_64-openstack.yml b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-12.04-x86_64-openstack.yml
new file mode 100644
index 00000000..f81b04b7
--- /dev/null
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-12.04-x86_64-openstack.yml
@@ -0,0 +1,14 @@
+HOSTS:
+ ubuntu-1204-x64:
+ default_apply_opts:
+ order: random
+ strict_variables:
+ platform: ubuntu-12.04-amd64
+ hypervisor : openstack
+ flavor: m1.small
+ image: ubuntu-1204-latest
+ user: ubuntu
+CONFIG:
+ type: foss
+ log_level: debug
+ openstack_network: default
diff --git a/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.04-x86_64-docker.yml b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.04-x86_64-docker.yml
new file mode 100644
index 00000000..6fb9281e
--- /dev/null
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.04-x86_64-docker.yml
@@ -0,0 +1,15 @@
+HOSTS:
+ ubuntu-1404-x64:
+ default_apply_opts:
+ order: random
+ strict_variables:
+ platform: ubuntu-14.04-amd64
+ hypervisor : docker
+ image: ubuntu:14.04
+ docker_preserve_image: true
+ docker_cmd: '["/sbin/init"]'
+ docker_image_commands:
+ - 'apt-get install -y wget'
+CONFIG:
+ type: foss
+ log_level: debug
diff --git a/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.04-x86_64-openstack.yml b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.04-x86_64-openstack.yml
new file mode 100644
index 00000000..2eeb912d
--- /dev/null
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.04-x86_64-openstack.yml
@@ -0,0 +1,14 @@
+HOSTS:
+ ubuntu-1404-x64:
+ default_apply_opts:
+ order: random
+ strict_variables:
+ platform: ubuntu-14.04-amd64
+ hypervisor : openstack
+ flavor: m1.small
+ image: ubuntu-1404-latest
+ user: ubuntu
+CONFIG:
+ type: foss
+ log_level: debug
+ openstack_network: default
diff --git a/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.04-x86_64-vagrant.yml b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.04-x86_64-vagrant.yml
new file mode 100644
index 00000000..3b376953
--- /dev/null
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.04-x86_64-vagrant.yml
@@ -0,0 +1,11 @@
+HOSTS:
+ ubuntu-1404-x64:
+ default_apply_opts:
+ order: random
+ 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/systemd/spec/acceptance/nodesets/ubuntu-14.10-x86_64-docker.yml b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.10-x86_64-docker.yml
new file mode 100644
index 00000000..2be425c5
--- /dev/null
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.10-x86_64-docker.yml
@@ -0,0 +1,15 @@
+HOSTS:
+ ubuntu-1410-x64:
+ default_apply_opts:
+ order: random
+ strict_variables:
+ platform: ubuntu-14.10-amd64
+ hypervisor : docker
+ image: ubuntu:14.10
+ docker_preserve_image: true
+ docker_cmd: '["/sbin/init"]'
+ docker_image_commands:
+ - 'apt-get install -y wget'
+CONFIG:
+ type: foss
+ log_level: debug
diff --git a/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.10-x86_64-openstack.yml b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.10-x86_64-openstack.yml
new file mode 100644
index 00000000..58a2acd2
--- /dev/null
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-14.10-x86_64-openstack.yml
@@ -0,0 +1,14 @@
+HOSTS:
+ ubuntu-1410-x64:
+ default_apply_opts:
+ order: random
+ strict_variables:
+ platform: ubuntu-14.10-amd64
+ hypervisor : openstack
+ flavor: m1.small
+ image: ubuntu-1410-latest
+ user: ubuntu
+CONFIG:
+ type: foss
+ log_level: debug
+ openstack_network: default
diff --git a/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-15.04-x86_64-docker.yml b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-15.04-x86_64-docker.yml
new file mode 100644
index 00000000..caed722c
--- /dev/null
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-15.04-x86_64-docker.yml
@@ -0,0 +1,15 @@
+HOSTS:
+ ubuntu-1504-x64:
+ default_apply_opts:
+ order: random
+ strict_variables:
+ platform: ubuntu-15.04-amd64
+ hypervisor : docker
+ image: ubuntu:15.04
+ docker_preserve_image: true
+ docker_cmd: '["/sbin/init"]'
+ docker_image_commands:
+ - 'apt-get install -y wget'
+CONFIG:
+ type: foss
+ log_level: debug
diff --git a/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-15.04-x86_64-openstack.yml b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-15.04-x86_64-openstack.yml
new file mode 100644
index 00000000..22ef76c4
--- /dev/null
+++ b/puppet/modules/systemd/spec/acceptance/nodesets/ubuntu-15.04-x86_64-openstack.yml
@@ -0,0 +1,14 @@
+HOSTS:
+ ubuntu-1504-x64:
+ default_apply_opts:
+ order: random
+ strict_variables:
+ platform: ubuntu-15.04-amd64
+ hypervisor : openstack
+ flavor: m1.small
+ image: ubuntu-1504-latest
+ user: ubuntu
+CONFIG:
+ type: foss
+ log_level: debug
+ openstack_network: default
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/puppet/modules/systemd/spec/spec_helper.rb b/puppet/modules/systemd/spec/spec_helper.rb
new file mode 100644
index 00000000..94d30d5c
--- /dev/null
+++ b/puppet/modules/systemd/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]}
+
+ c.strict_variables = Gem::Version.new(Puppet.version) >= 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 || Gem::Requirement.new("~> 4.0")
+ 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/tor b/puppet/modules/tor
deleted file mode 160000
-Subproject 8c936c166b6da1ebd0e8d95e56ceee5167357d6
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/.gitrepo b/puppet/modules/tor/.gitrepo
new file mode 100644
index 00000000..dfc1b3d9
--- /dev/null
+++ b/puppet/modules/tor/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_tor
+ branch = master
+ commit = 9981a70f7ba1f9e4fe33e4eb46654295287c1fc1
+ parent = 26aac7ccf240b06d65616bdd00ae472d980aaea9
+ cmdver = 0.3.0
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/unbound b/puppet/modules/unbound
deleted file mode 160000
-Subproject a26b91dfea3189e6777629fa00d54f51dc41f4d
diff --git a/puppet/modules/unbound/.gitrepo b/puppet/modules/unbound/.gitrepo
new file mode 100644
index 00000000..863c109f
--- /dev/null
+++ b/puppet/modules/unbound/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_unbound
+ branch = master
+ commit = a26b91dfea3189e6777629fa00d54f51dc41f4d4
+ parent = 40ea2656f072e23bbbccd22c39fb29a36390fa3a
+ cmdver = 0.3.0
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 b/puppet/modules/vcsrepo
deleted file mode 160000
-Subproject 4e23209eaccf1ab504d35158f4141b3053327c2
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/.gitrepo b/puppet/modules/vcsrepo/.gitrepo
new file mode 100644
index 00000000..d4a41d8a
--- /dev/null
+++ b/puppet/modules/vcsrepo/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_vcsrepo
+ branch = master
+ commit = 4e23209eaccf1ab504d35158f4141b3053327c2f
+ parent = 5247b7ccf5b5889ee16262dd976b03047e34e32c
+ cmdver = 0.3.0
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 b/puppet/modules/x509
deleted file mode 160000
-Subproject 19254a38c1c372ae7912ea9f15500b9b1cbffe8
diff --git a/puppet/modules/x509/.gitrepo b/puppet/modules/x509/.gitrepo
new file mode 100644
index 00000000..ed6eb7ac
--- /dev/null
+++ b/puppet/modules/x509/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+ remote = https://leap.se/git/puppet_x509
+ branch = master
+ commit = 19254a38c1c372ae7912ea9f15500b9b1cbffe81
+ parent = d8ecd5d2f933c40f2413a58e6324558d0e689b6a
+ cmdver = 0.3.0
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
index 814c25b1..ea6bcaa9 100644
--- a/tests/README.md
+++ b/tests/README.md
@@ -1,25 +1,24 @@
-Tests
----------------------------------
+What is here?
-tests/white-box/
+**server-tests/**
- These tests are run on the server as superuser. They are for
- troubleshooting any problems with the internal setup of the server.
+These are the tests run on a provider's servers using the command:
-tests/black-box/
+ workstation$ leap test
- These test are run the user's local machine. They are for troubleshooting
- any external problems with the service exposed by the server.
+Or the command:
-Additional Files
----------------------------------
+ server# run_tests
-tests/helpers/
+These tests are to confirm that a provider's infrasture is working and to troubleshoot any possible problems.
- Utility functions made available to all tests.
+**example-provider/**
-tests/order.rb
+Allows you to generate a pre-configured provider using Vagrant virtual
+machines.
- Configuration file to specify which nodes should be tested in which order.
+**platform-ci/**
+Continous integration tests run for the LEAP Platform. These tests are for the
+platform code itself.
diff --git a/tests/example-provider/README.md b/tests/example-provider/README.md
new file mode 100644
index 00000000..80cb3ae9
--- /dev/null
+++ b/tests/example-provider/README.md
@@ -0,0 +1,8 @@
+Here lies a script to generate a pre-configured provider using Vagrant virtual
+machines. This virtual provider includes only a single node.
+
+All you have to do is this:
+
+ cd leap_platform/tests/example-provider
+ vagrant up
+
diff --git a/tests/example-provider/Vagrantfile b/tests/example-provider/Vagrantfile
new file mode 100644
index 00000000..1e410f5e
--- /dev/null
+++ b/tests/example-provider/Vagrantfile
@@ -0,0 +1,58 @@
+# -*- mode: ruby -*-
+# vi: set ft=ruby :
+
+Vagrant.configure("2") do |config|
+
+ # shared config for all boxes
+
+ # make the leap_platform directory available as /srv/leap_platform
+ # inside the virtual machine.
+ config.vm.synced_folder "../..", "/srv/leap_platform"
+
+ # 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.network "forwarded_port", guest: 4430, host:4430
+
+ 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/hiera.yaml b/tests/example-provider/hiera.yaml
index 3ff857b8..3ff857b8 100644
--- a/hiera.yaml
+++ b/tests/example-provider/hiera.yaml
diff --git a/vagrant/add-pixelated.sh b/tests/example-provider/vagrant/add-pixelated.sh
index f9908947..f9908947 100755
--- a/vagrant/add-pixelated.sh
+++ b/tests/example-provider/vagrant/add-pixelated.sh
diff --git a/tests/example-provider/vagrant/configure-leap.sh b/tests/example-provider/vagrant/configure-leap.sh
new file mode 100755
index 00000000..fd34d7ea
--- /dev/null
+++ b/tests/example-provider/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="$PLATFORMDIR" .
+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/tests/example-provider/vagrant/install-platform.pp
index 223853c1..223853c1 100755
--- a/vagrant/install-platform.pp
+++ b/tests/example-provider/vagrant/install-platform.pp
diff --git a/tests/example-provider/vagrant/vagrant.config b/tests/example-provider/vagrant/vagrant.config
new file mode 100644
index 00000000..60d2a52c
--- /dev/null
+++ b/tests/example-provider/vagrant/vagrant.config
@@ -0,0 +1,23 @@
+# 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"
+PLATFORMDIR="/srv/leap_platform"
+LEAP="$SUDO /usr/local/bin/leap"
+GIT="$SUDO git"
+
diff --git a/tests/helpers/couchdb_helper.rb b/tests/helpers/couchdb_helper.rb
deleted file mode 100644
index b9085c1e..00000000
--- a/tests/helpers/couchdb_helper.rb
+++ /dev/null
@@ -1,142 +0,0 @@
-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/os_helper.rb b/tests/helpers/os_helper.rb
deleted file mode 100644
index da9ac843..00000000
--- a/tests/helpers/os_helper.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-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}`
- if $?.exitstatus != 0
- fail "Error running `#{command}`:\n#{output}"
- end
- end
-
-end \ No newline at end of file
diff --git a/tests/platform-ci/Gemfile b/tests/platform-ci/Gemfile
new file mode 100644
index 00000000..36f556e5
--- /dev/null
+++ b/tests/platform-ci/Gemfile
@@ -0,0 +1,17 @@
+source "https://rubygems.org"
+
+group :test do
+ gem "rake"
+ gem "rspec"
+ gem "puppet", ENV['PUPPET_VERSION'] || ENV['GEM_PUPPET_VERSION'] || ENV['PUPPET_GEM_VERSION'] || '~> 3.8'
+ 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"
+ # Use puppet-catalog-test from git because last released gem 0.4.2 gives a deprecation
+ # warning: "[DEPRECATION] `last_comment` is deprecated. Please use `last_description` instead."
+ gem "puppet-catalog-test", :git => 'https://github.com/invadersmustdie/puppet-catalog-test.git'
+ gem "leap_cli", :git => 'https://leap.se/git/leap_cli.git', :branch => 'develop'
+end
diff --git a/tests/platform-ci/README.md b/tests/platform-ci/README.md
new file mode 100644
index 00000000..60c17e41
--- /dev/null
+++ b/tests/platform-ci/README.md
@@ -0,0 +1,15 @@
+Continuous integration tests for the leap_platform code.
+
+Usage:
+
+ ./setup.sh
+ bin/rake test:syntax
+ bin/rake test:catalog
+
+For a list of all tasks:
+
+ bin/rake -T
+
+To create a virtual provider, run tests on it, then tear it down:
+
+ ./ci-build.sh
diff --git a/tests/platform-ci/Rakefile b/tests/platform-ci/Rakefile
new file mode 100644
index 00000000..5443be36
--- /dev/null
+++ b/tests/platform-ci/Rakefile
@@ -0,0 +1,121 @@
+require 'puppetlabs_spec_helper/rake_tasks'
+require 'puppet-lint/tasks/puppet-lint'
+require 'puppet-syntax/tasks/puppet-syntax'
+require 'puppet-catalog-test'
+
+CI_DIR = File.dirname(__FILE__)
+PLATFORM_DIR = File.expand_path('../..', CI_DIR)
+PROVIDER_DIR = File.join(CI_DIR, 'provider')
+
+#
+# return list of modules, either "external" (submodules or subrepos), "custom"
+# (no submodules nor subrepos) or all modules so we can check each array
+# seperately
+#
+def modules_pattern (type)
+ external = Array.new
+ internal = Array.new
+ all = Array.new
+
+ Dir.chdir(PLATFORM_DIR) do
+ Dir['puppet/modules/*'].sort.each do |m|
+
+ # submodule or subrepo ?
+ system("grep -q #{m} .gitmodules 2>/dev/null || test -f #{m}/.gitrepo")
+ if $?.exitstatus == 0
+ external << m + '/**/*.pp'
+ else
+ internal << m + '/**/*.pp'
+ end
+ all << m + '/**/*.pp'
+ end
+
+ case type
+ when 'external'
+ external
+ when 'internal'
+ internal
+ when 'all'
+ all
+ end
+ 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|
+ # only check for custom manifests, not submodules for now
+ config.pattern = modules_pattern('internal')
+ config.ignore_paths = exclude_paths
+ config.disable_checks = ['documentation', '140chars', 'arrow_alignment']
+ config.fail_on_warnings = false
+end
+
+# rake syntax::* tasks
+PuppetSyntax.exclude_paths = exclude_paths
+PuppetSyntax.future_parser = true
+
+desc "Validate erb templates"
+task :templates do
+ Dir.chdir(PLATFORM_DIR) do
+ Dir['**/templates/**/*.erb'].each do |template|
+ sh "erb -P -x -T '-' #{template} | ruby -c" unless template =~ /.*vendor.*/
+ end
+ end
+end
+
+namespace :platform do
+ desc "Compile hiera config for test_provider"
+ task :provider_compile do
+ Dir.chdir(PROVIDER_DIR) do
+ sh "bundle exec leap compile"
+ end
+ end
+end
+
+PuppetCatalogTest::RakeTask.new('catalog') do |t|
+ Rake::Task["platform:provider_compile"].invoke
+ t.module_paths = [File.join(PLATFORM_DIR, "puppet", "modules")]
+ t.manifest_path = File.join(PLATFORM_DIR, "puppet","manifests", "site.pp")
+ t.facts = {
+ "operatingsystem" => "Debian",
+ "osfamily" => "Debian",
+ "operatingsystemmajrelease" => "8",
+ "debian_release" => "stable",
+ "debian_codename" => "jessie",
+ "lsbdistcodename" => "jessie",
+ "concat_basedir" => "/var/lib/puppet/concat",
+ "interfaces" => "eth0"
+ }
+
+ # crucial option for hiera integration
+ t.config_dir = CI_DIR # expects hiera.yaml to be included in directory
+
+ # t.parser = "future"
+ #t.verbose = true
+end
+
+
+namespace :test do
+ # :syntax:templates fails on squirrel, see https://jenkins.leap.se/view/Platform%20Builds/job/platform_citest/115/console
+ # but we have our own synax test
+ desc "Run all puppet syntax checks required for CI (syntax , validate, templates, spec, lint)"
+ task :syntax => [:"syntax:hiera", :"syntax:manifests", :validate, :templates, :spec, :lint]
+
+ desc "Tries to compile the catalog"
+ task :catalog => [:catalog]
+
+ #task :all => [:syntax, :catalog]
+end
+
+# unfortunatly, we cannot have one taks to rule them all
+# because :catalog would conflict with :syntax or :validate:
+# rake aborted!
+# Puppet::DevError: Attempting to initialize global default settings more than once!
+# /home/varac/dev/projects/leap/git/leap_platform/vendor/bundle/ruby/2.3.0/gems/puppet-3.8.7/lib/puppet/settings.rb:261:in `initialize_global_settings'
+#desc "Run all platform tests"
+#task :test => 'test:all'
diff --git a/tests/platform-ci/ci-build.sh b/tests/platform-ci/ci-build.sh
new file mode 100755
index 00000000..85557b3f
--- /dev/null
+++ b/tests/platform-ci/ci-build.sh
@@ -0,0 +1,90 @@
+#!/bin/bash
+#
+# This script will run create a virtual provider
+# and run tests on it.
+#
+# This script is triggered by .gitlab-ci.yml
+#
+# It depends on:
+# * leap_platform: in ../..
+# * test provider: in provider/
+# * leap-platform-test: installed in path
+# * AWS credentials as environment variables:
+# * `AWS_ACCESS_KEY`
+# * `AWS_SECRET_KEY`
+# * ssh private key used to login to remove vm
+# * `SSH_PRIVATE_KEY`
+#
+# Todo:
+# - Running locally works fine, now use it in gitlab CI ( which ssh-key ? create cloud.json from env vars )
+# - Speed up vm boot if possible ( right now 3-4mins )
+
+# exit if any commands returns non-zero status
+set -e
+
+# leap_platform/tests/platform-ci
+# shellcheck disable=SC2086
+ROOTDIR=$(readlink -f "$(dirname $0)")
+
+# leap_platform/tests/platform-ci/provider
+PROVIDERDIR="${ROOTDIR}/provider"
+
+# leap_platform
+PLATFORMDIR=$(readlink -f "${ROOTDIR}/../..")
+
+LEAP_CMD="/usr/local/bin/bundle exec leap -v2 --yes"
+
+# create node(s) with unique id so we can run tests in parallel
+NAME="citest${CI_BUILD_ID}"
+# when using gitlab-runner locally, CI_BUILD_ID is always 1 which
+# will conflict with running/terminating AWS instances in subsequent runs
+# therefore we pick a random number in this case
+[ "$CI_BUILD_ID" -eq "1" ] && NAME+="000${RANDOM}"
+
+TAG='single'
+SERVICES='couchdb,soledad,mx,webapp,tor,monitor'
+SEEDS='sources.platform.apt.basic:http://deb.leap.se/experimental-0.9 sources.webapp.revision:master sources.nickserver.revision:master'
+
+
+#
+# Main
+#
+
+
+/bin/echo "CI directory: ${ROOTDIR}"
+/bin/echo "Provider directory: ${PROVIDERDIR}"
+/bin/echo "Platform directory: ${PLATFORMDIR}"
+cd "$PROVIDERDIR"
+
+# Ensure we don't output secret stuff to console even when running in verbose mode with -x
+set +x
+
+# Create cloud.json needed for `leap vm` commands using AWS credentials
+which jq || ( apt-get update -y && apt-get install jq -y )
+/usr/bin/jq ".platform_ci.auth |= .+ {\"aws_access_key_id\":\"$AWS_ACCESS_KEY\", \"aws_secret_access_key\":\"$AWS_SECRET_KEY\"}" < cloud.json.template > cloud.json
+
+# Configure ssh keypair
+[ -d ~/.ssh ] || /bin/mkdir ~/.ssh
+/bin/echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
+/bin/chmod 600 ~/.ssh/id_rsa
+/bin/cp users/gitlab-runner/gitlab-runner_ssh.pub ~/.ssh/id_rsa.pub
+
+[ -d "./tags" ] || mkdir "./tags"
+/bin/echo "{\"environment\": \"$TAG\"}" | /usr/bin/json_pp > "${PROVIDERDIR}/tags/${TAG}.json"
+
+$LEAP_CMD vm status "$TAG"
+# shellcheck disable=SC2086
+$LEAP_CMD vm add "$NAME" services:"$SERVICES" tags:"$TAG" $SEEDS
+$LEAP_CMD compile "$TAG"
+$LEAP_CMD vm status "$TAG"
+
+$LEAP_CMD node init "$TAG"
+$LEAP_CMD info "${TAG}"
+
+# Deploy and test
+$LEAP_CMD deploy "$TAG"
+$LEAP_CMD test "$TAG"
+
+# if everything succeeds, destroy the vm
+$LEAP_CMD vm rm "${TAG}"
+[ -f "nodes/${NAME}.json" ] && /bin/rm "nodes/${NAME}.json"
diff --git a/tests/platform-ci/hiera.yaml b/tests/platform-ci/hiera.yaml
new file mode 100644
index 00000000..a23d8b92
--- /dev/null
+++ b/tests/platform-ci/hiera.yaml
@@ -0,0 +1,16 @@
+---
+:backends:
+ - yaml
+ - puppet
+
+:logger: console
+
+:yaml:
+ :datadir: provider/hiera
+
+:hierarchy:
+ - catalogtest
+
+:puppet:
+ :datasource: data
+
diff --git a/tests/platform-ci/provider/Leapfile b/tests/platform-ci/provider/Leapfile
new file mode 100644
index 00000000..4852aed7
--- /dev/null
+++ b/tests/platform-ci/provider/Leapfile
@@ -0,0 +1 @@
+@platform_directory_path = File.expand_path("../../../..", __FILE__)
diff --git a/tests/platform-ci/provider/cloud.json.template b/tests/platform-ci/provider/cloud.json.template
new file mode 100644
index 00000000..28152e82
--- /dev/null
+++ b/tests/platform-ci/provider/cloud.json.template
@@ -0,0 +1,15 @@
+{
+ "platform_ci": {
+ "api": "aws",
+ "vendor": "aws",
+ "auth": {
+ "region": "us-west-2",
+ "aws_access_key_id": "",
+ "aws_secret_access_key": ""
+ },
+ "default_image": "ami-2a34e94a",
+ "default_options": {
+ "InstanceType": "t2.small"
+ }
+ }
+}
diff --git a/tests/platform-ci/provider/common.json b/tests/platform-ci/provider/common.json
new file mode 100644
index 00000000..a13f8f75
--- /dev/null
+++ b/tests/platform-ci/provider/common.json
@@ -0,0 +1,12 @@
+{
+ "sources": {
+ "platform": {
+ "apt": {
+ "basic": "http://deb.leap.se/experimental-0.9"
+ }
+ },
+ "nickserver": {
+ "revision": "develop"
+ }
+ }
+}
diff --git a/tests/platform-ci/provider/files/ca/ca.crt b/tests/platform-ci/provider/files/ca/ca.crt
new file mode 100644
index 00000000..01df56a7
--- /dev/null
+++ b/tests/platform-ci/provider/files/ca/ca.crt
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIFbzCCA1egAwIBAgIBATANBgkqhkiG9w0BAQ0FADBKMRAwDgYDVQQKDAdFeGFt
+cGxlMRwwGgYDVQQLDBNodHRwczovL2V4YW1wbGUub3JnMRgwFgYDVQQDDA9FeGFt
+cGxlIFJvb3QgQ0EwHhcNMTYwNjExMDAwMDAwWhcNMjYwNjExMDAwMDAwWjBKMRAw
+DgYDVQQKDAdFeGFtcGxlMRwwGgYDVQQLDBNodHRwczovL2V4YW1wbGUub3JnMRgw
+FgYDVQQDDA9FeGFtcGxlIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
+ggIKAoICAQCyW2rTcWimY288/Ddu7OPvJxShS1RInQqfq8hYy6hEK2QYn656dRDf
+pJXgSYWMvWzSXWJiQkyA8L2+DDilFtccqToqnKE7IwYHlxaeh8OSyZcHl4YCpWJi
+1rc7pysN/l/0pjsp1aKKyHEObnkGMev07uGmI8aOE4Yvd2K5LjBjlov5mNnbYEHW
+j+hWctV6OcphnxVboqtTy+0Ewv5D56snLjUtedyB7Er4ryRjIrWOyd+ZSyi03zov
+oY1xPXS5wSxCc6y6wOKt7/noIg9xxWi7XgSd3OVtPYRU3Io62lMBSzNG6fmos3Mb
+E4ui5ma7IFCJlMEFHirVSBiHn2jwsDtSsrTl0JHJS5ud8Eve5vV0r/n07QsDMhj5
+ol+YDq4+VvOekCAH75GFYkMIzpgcVzC2a1Rq7JTkcnINAF/m47yBLPomItklHv0z
++I23Q+jjTaM2A+40T2K+YRLjFyZwrlCAScjMwPFspnGxfa02miYENhPV1TdTP/ap
+QE7TSl6oFiNrTh7INHGvKgrZYRW598dgAWNKG4zWY8Vj/bIXR1lC8Lp6enQtsIsU
+WiF+zl+xq6bRHg7W419qQowiD349gPXIJGlXEzLqjgLpmpFywrpzxBv6sfZgYT9d
+OPATT+GSiOFYJh9K4/JIxOFBJhzDD6PribjhzydPMTojSJ2Xu7nsOwIDAQABo2Aw
+XjAdBgNVHQ4EFgQUlhC2wfrVFzGrtuzcA0mkO+yn9bgwDgYDVR0PAQH/BAQDAgIE
+MAwGA1UdEwQFMAMBAf8wHwYDVR0jBBgwFoAUlhC2wfrVFzGrtuzcA0mkO+yn9bgw
+DQYJKoZIhvcNAQENBQADggIBAAKdSviiZY8tINlDSVrib0CyDbXymO5uTPRqsf/u
+MC7/DYXlNFy0GHpX4Ls6GcJN5DdZAG0TaoWo5RkNerxqv78sGJsmPqWt55cpBPVe
+NLpFmxcOmLClSDLBhSaq5ggbxULScee7MS1gPHqz1BHXmi7ZJIip4VeVA2e1E52F
+J7E4Y36AJOdZYLgz50YOX/NZwSYBTMy7RI1MiqG/eJf1BjkwtSyO7FTjPXsdKi8x
+HhtRr5udm7Nprq1eJUUDD0+z4kAeTe/LJeuhxc4QKzpVZkE1peW6Wlklp0cdLJud
+7gUsY1GFnNhZDDQ3SW2ZJ/p2OdH35rX96cj+6VClqSQMbH4rL63tICLmAsEzPKwJ
+57bGVUM822n4mh0vn79dam40vMw7wkTKqIKVyLhk30N5/73XczpoLhvVdKDtA1Aj
+C6LseWq4CZsaRSCgk2VsEEYyl7M+BIREuhYOllsILneOTiCOCnU4EdnBQZIHdz3S
+xhduafYXLa7RHkFMfOjtmhogXXpGyaQuS8IsivIowOxKoIZo47IhYRRAghrVN2HK
+ZXrgftIHNfHsFLfe6iiQBgaRn/1w7xOIPVDBqlZKKAMQE7cvum2o6dJo03Sc4dIe
+rvIU1WGNRLM3/AsbZ/7gqwD3INiNUPeuVaiRqvLvXnKfHlR/4s2wZrnKqUgYF1Go
+arXF
+-----END CERTIFICATE-----
diff --git a/tests/platform-ci/provider/files/ca/ca.key b/tests/platform-ci/provider/files/ca/ca.key
new file mode 100644
index 00000000..c022b19a
--- /dev/null
+++ b/tests/platform-ci/provider/files/ca/ca.key
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKQIBAAKCAgEAsltq03FopmNvPPw3buzj7ycUoUtUSJ0Kn6vIWMuoRCtkGJ+u
+enUQ36SV4EmFjL1s0l1iYkJMgPC9vgw4pRbXHKk6KpyhOyMGB5cWnofDksmXB5eG
+AqViYta3O6crDf5f9KY7KdWiishxDm55BjHr9O7hpiPGjhOGL3diuS4wY5aL+ZjZ
+22BB1o/oVnLVejnKYZ8VW6KrU8vtBML+Q+erJy41LXncgexK+K8kYyK1jsnfmUso
+tN86L6GNcT10ucEsQnOsusDire/56CIPccVou14EndzlbT2EVNyKOtpTAUszRun5
+qLNzGxOLouZmuyBQiZTBBR4q1UgYh59o8LA7UrK05dCRyUubnfBL3ub1dK/59O0L
+AzIY+aJfmA6uPlbznpAgB++RhWJDCM6YHFcwtmtUauyU5HJyDQBf5uO8gSz6JiLZ
+JR79M/iNt0Po402jNgPuNE9ivmES4xcmcK5QgEnIzMDxbKZxsX2tNpomBDYT1dU3
+Uz/2qUBO00peqBYja04eyDRxryoK2WEVuffHYAFjShuM1mPFY/2yF0dZQvC6enp0
+LbCLFFohfs5fsaum0R4O1uNfakKMIg9+PYD1yCRpVxMy6o4C6ZqRcsK6c8Qb+rH2
+YGE/XTjwE0/hkojhWCYfSuPySMThQSYcww+j64m44c8nTzE6I0idl7u57DsCAwEA
+AQKCAgBGAzi186i+1/2MlP01n+wBrvecMTPOpUbMUuR8ZsWQrO/H8rbM/zM2dycW
+OgYgryMOmPXL2HarjtUMy0NZGtQqPgvFOmLYEfGF/Ts109ljv5p3snU6iK1MWzjm
+Q8LU5WvJX4+N5ny9ud0Xayo60lHrffI6A4UntGZSL60jQAxiq3Aa9HNgeDKgBTGQ
+7db6+cCF/aqmo/5ZEI3j9p9VDJXU9YCOb22t2pG7eRTxjWhzuq75P9Wk2pO+qs4Z
+C6TMXhX/p+TAEoNo//C7vNMPOAzasBdj2Jh+/0z4+vGQFK/MrDZeue302SxwDoYb
+1hGxlwfGWgxC9AqgWoK2ik7pXGSMc/DyFJHswvVgH1I4wK1rkydDJphlgmYNDk40
+3mvpbQcNgcs5q+q0jrbFmFJHPyLa2z3hMnwZr+3BViNQULQ7ihUqpi8lWEyTKtim
+fL3HbqGv2da/2HQtWUU135RQGBjQZBqcJX9LiCwfbdUI+/j+mVlDJyot/xXhJ0WJ
++6OKOVz443957QEV6df8YkRnnRkaTfvj86dNQWCStdIyiYHzaVZ64f8GDHpGOazv
+ubiv2o3ZaYvKS1mGqBKdXxEe1Dxndtq2+rDcnx/jXZjaHjEALFaxKJdZIQPUqh/3
+3UTe8OFFVeAcA9w0hqPyUwMfq34DczeKVtCEEEYkDldPyZ2MIQKCAQEA4e5bcaUW
+5n4joUGdgeCYpYyA6MGKJEahy5VeI4bs8/o36DHXFMLmmUrgI4tgy3r4GDG3CRcW
+q0fVi86qXTqHcScJ2S2jsEuX9Q91LLIwxiun1qakQ9w8kCaYvw6Wp+rcfVXTAe5g
+Px3i/Q6hy7Vhs1Usn0iuwCbrIvVpJ50gRul+QojLFcw5i1FLmCAU55uhj9u0h7JP
+/Ni3cCr7WCYct8xknLKRn6BHOHodIJDpX1/KNyOJ21V5k47gRAJwlcn90/OSs2O0
+SIFfZQ8Gafvr7C2wMs0YvVXC3oXSlhMkYUJt1B6PKp92qxwiRsw5i+HA1LXbGOoc
+btnpfJA4d3TREwKCAQEAyhgtsRmfVwXsQswASUvn9NNUJ61mZdpPMq9d4BSNQzSv
+EjM3aTjuqGBh/r01VQm666hSFhv7yo3GIlhzjez8hE+SExSnHMw0MMCUEsDBki7e
+SY3rZ0Dzj9FfGBYagOesyQQZSjFFSfmsnBRkrFkVwpJvA0nDMg4xYDaX6yyf8RFX
+2teXdI2q2UcTNK5001fVOHLY1ML4ytCIG7gGV/WVSGo2V3VOA+Xl/VdII1hZfa1i
+LcdCiBw65vsiDeROoG02F1v5xwDLei6A8JYmJEqOy73+ZgABe2Hk4wQx/GlEH8b5
+2jfNp+1L6aRkXFhAm3wfRWQsKfsYSB2XJxxB8RYFOQKCAQEAv6dc9viehoQ2YVKx
+9Dy8AKNBrzCOqNsp4PMiWmzYkNaPmm69DyWOTDdSD5TqVXJJBu0VYaauWjmjkueL
+aW5++qOtHQg0NRbLHt0v/uxhp5nc1J+j9NTco0O6i0gq0OLQi5nEV30JNEF8DkLd
+SVriOChmo/AaHXJmQM+BllMZ0E2+B17XN/R4VBBwWenNEfPZh5lOeVXvuIN2iLZN
+ZKdf8SJ3rt1j3s8t22DrWHbVIUy20zNYfDDz4xJueALB0q74nVWf+oD3rBHjBG1M
+eZd0uHLBZzbIZ8RafD11OE2grMiXNjt+IyAGoHxLL1eK8XheBZMG+wmNeRNtl3cY
+D22O9QKCAQEAx76kEqIXikSxYsgNFGTw61ugluLdDZh7pMYNy/ekM6Oz0hJLFzYN
+NOCmmshaGSXX2SnxkCaydF4yUioIdGOipgebgj5seZsfjnwZHnvkFt86F4ss+04I
+LcKr8buPEI9riPcDJACU0mvy/gVuB6a5Sim/jYlvY18B0G3FM81UfEk/A28JJEsN
+bVnBktVHZMgwV220AH6AtrzrejImGvQBS6Sm90RbCqFE82Q8Sar+MKiZHFQQ30S/
+tyLKYt6gFBI9X1MqClYvxyCFksVlB4OlpZyxABHLZS65suOnoCpPCfV5aAS1wN9a
+o6A3DcqweL1yjvxWZlvmgQi2KBLW3jl8iQKCAQB9S91mjvys1iwcz8sYneCNetHw
+Axlr1pfoHUgyTy1/9ategbPkEegLCDtAYmILRBiVb9hnSnmn9k1fYIo3P3nja/vU
+wJyYubpu9DshzlFRQ2GANpKixjm++NTfpMVIYpcBUjdqgqc501FPUYksbZkcpuDG
+xJNAM3OzSkEmc91sVkjUhcjXovW+UWXtqxGn6/T9TcgE2yrhgSbz8rnr3SDHEeHz
+GgUaQGXodg0kr3tLJSY/+FGuORL4mtV+0XQF7EbN8hC8b8B+bHpiIrWcMJ9OG7al
+1UfkeqXvOByN3Itx489BtrizyYGRIrMCfguTBKNxe4J06If6mkq9GKC2hnM8
+-----END RSA PRIVATE KEY-----
diff --git a/tests/platform-ci/provider/files/ca/client_ca.crt b/tests/platform-ci/provider/files/ca/client_ca.crt
new file mode 100644
index 00000000..c1214476
--- /dev/null
+++ b/tests/platform-ci/provider/files/ca/client_ca.crt
@@ -0,0 +1,33 @@
+-----BEGIN CERTIFICATE-----
+MIIFpzCCA4+gAwIBAgIBATANBgkqhkiG9w0BAQ0FADBmMRAwDgYDVQQKDAdFeGFt
+cGxlMRwwGgYDVQQLDBNodHRwczovL2V4YW1wbGUub3JnMTQwMgYDVQQDDCtFeGFt
+cGxlIFJvb3QgQ0EgKGNsaWVudCBjZXJ0aWZpY2F0ZXMgb25seSEpMB4XDTE2MDYx
+MTAwMDAwMFoXDTI2MDYxMTAwMDAwMFowZjEQMA4GA1UECgwHRXhhbXBsZTEcMBoG
+A1UECwwTaHR0cHM6Ly9leGFtcGxlLm9yZzE0MDIGA1UEAwwrRXhhbXBsZSBSb290
+IENBIChjbGllbnQgY2VydGlmaWNhdGVzIG9ubHkhKTCCAiIwDQYJKoZIhvcNAQEB
+BQADggIPADCCAgoCggIBAL+WKlA0V+1aMjDKCwk3HaVJz7tk+knutrr3RtjwUshp
+wPty3+t1WrTEtfLLUv6MNOFStTPv5/JKAtDVEcm5xVJ9DNAw8XBnouUnm77WrMa5
+t3Oa8iA6kL1GsdfCoAyKNSX7ArDlfumA/fakjIvPoRYmjplzsodlHISu5FqpHc+G
+NdX89K6yzcjgMhRhCvHLrL9d+pe+efBDLab5I8pA0CGpaLfzPQiUNc2E1jn+ApSJ
+Bkq+gVBscKcomluDa6rtP2UeGGvG1DkHtbpx1WA/a/T9Tt7ACFd1uIQ2Ob57MAHx
+WgP6jD+Kj+/r9sA0iXGN/JnWXxpsVfYjbEFhRhL80Z3Rpj3Hf6xgUJbx0LUE34xA
+CTAK/n9G5q+7oog6oSNx80AU6ihWoucARtrQpwrV8rMvEO+QAtT2DjQMShYupk+n
+vHV1BTsigsjfywB2eGODKC5u6Ev91Zc8JEFmVvR+/tEP/XNzUTejGVi1fuABLILq
+Id0rL37j/NZ9OyExGSJDIRXSH45gHMkjNlQlqXYJ4JiZZbs/8UHEv4TnwFceBBhM
+lk8NwQE13B8F+/mcpaLaQ3X9AJzYBIh0CWkAaSKXmpIMSrOFlljihIIsA/p2OmOc
+g1sumCK3IU8AXoUbzDM1EqL5/wE9jD+ns8Bsy4JR1FFZy1FOmQfacIJdbd46jkvD
+AgMBAAGjYDBeMB0GA1UdDgQWBBSrXJyoXQRw+uwU274hxHyKeX6kgDAOBgNVHQ8B
+Af8EBAMCAgQwDAYDVR0TBAUwAwEB/zAfBgNVHSMEGDAWgBSrXJyoXQRw+uwU274h
+xHyKeX6kgDANBgkqhkiG9w0BAQ0FAAOCAgEATF/s9DHNj3h8O4IN0eUC6YiXnpGv
+z3z4KPD5RYy9+O3uf+f6SxFOZZU5NU9GHE9VRenmerHSsux9FxEAGsCjpiCFQGXq
+PKPBINyuR6TIDo+E/bl97Te0wL7aATiy5HFfQd41IoYPjuDpgb1Fc25w6iv9VeFG
+WrZ1JLJp4wguZ6RKSSLhsBF3m+wGe6Mg89b1sdkCvFr6EVqlZZbOSPUpUjVYp46p
+v3WP+Grtx9rBlJxqPpA7RPIyqnyiE4ovZcznz+9glgB3n1ufO+dSCVjkAEPxvmLu
+Qj7Jc+rpNOE5xZCFBaqtCBaBm2Uht3OyHypK9UYLZ7QOAfrGnBdgLERkAzPG6Zok
+yXuo0YTjHpdy5BPUD8VOahsj/2tzkMXkYmRCW9/dRwhfvi3QQHyQpsRZizmWXgTV
+JWa6UYfF1B/rDt3sn+AjDCxhHeBe02YTw0MWG3frv3Gn2/JUESSQjK4Xhjg/DPxb
+pLfhSLuq7WWqtkJsI0sZVj+GAdkbTgGjMLvj6+ckXpqE9V8eDgvE7KqYlSS2i6Sm
+e3SofOC2h10D3pWtX1KSPUp20ClRE/MUS/YW9szKZhqA/ZNMX2eViF05hgqywYwg
+GvapgFpn0mbBj9sOrBuAZX/r+U3MBv/Pj8ErdX/m20Bg/eIPBcHftS465Y9fjGu+
+apsldYNSrCZ30p4=
+-----END CERTIFICATE-----
diff --git a/tests/platform-ci/provider/files/ca/client_ca.key b/tests/platform-ci/provider/files/ca/client_ca.key
new file mode 100644
index 00000000..160cad43
--- /dev/null
+++ b/tests/platform-ci/provider/files/ca/client_ca.key
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKAIBAAKCAgEAv5YqUDRX7VoyMMoLCTcdpUnPu2T6Se62uvdG2PBSyGnA+3Lf
+63VatMS18stS/ow04VK1M+/n8koC0NURybnFUn0M0DDxcGei5Sebvtasxrm3c5ry
+IDqQvUax18KgDIo1JfsCsOV+6YD99qSMi8+hFiaOmXOyh2UchK7kWqkdz4Y11fz0
+rrLNyOAyFGEK8cusv136l7558EMtpvkjykDQIalot/M9CJQ1zYTWOf4ClIkGSr6B
+UGxwpyiaW4Nrqu0/ZR4Ya8bUOQe1unHVYD9r9P1O3sAIV3W4hDY5vnswAfFaA/qM
+P4qP7+v2wDSJcY38mdZfGmxV9iNsQWFGEvzRndGmPcd/rGBQlvHQtQTfjEAJMAr+
+f0bmr7uiiDqhI3HzQBTqKFai5wBG2tCnCtXysy8Q75AC1PYONAxKFi6mT6e8dXUF
+OyKCyN/LAHZ4Y4MoLm7oS/3VlzwkQWZW9H7+0Q/9c3NRN6MZWLV+4AEsguoh3Ssv
+fuP81n07ITEZIkMhFdIfjmAcySM2VCWpdgngmJlluz/xQcS/hOfAVx4EGEyWTw3B
+ATXcHwX7+ZylotpDdf0AnNgEiHQJaQBpIpeakgxKs4WWWOKEgiwD+nY6Y5yDWy6Y
+IrchTwBehRvMMzUSovn/AT2MP6ezwGzLglHUUVnLUU6ZB9pwgl1t3jqOS8MCAwEA
+AQKCAgEAp4NO3+3Ea32PoOUnnRkZzKmq/jieNwKHtxX6VjhayWzeFX0tmBx2ANR2
+GiH5ISPKILFGSnEbJtfbemiyMuVBSIyaJXaFxDh5T0/Ad64QR3mek3AJAHD0mOo1
+GWfMtOoq6lh809r1iokEhSD+2kfimxF/YWCt2oBn3QNmGnb/37GDZOTVs+IW1+pf
+Hz5yaVQiaPhs4TzkNVUnl3UC/BaLZMNREnWVCek82cOp4+7aprDgVX4YZw9JuH5h
+6F4SR9NEuM8Fn0arzGmXVbuuS4dohz7sNQtGv+HoQYGAH7JqGWjDwfLRqcUncSmq
+CAhnnGf/UysC4IGU76+tOcUplfSD+aur0FWCtKf4scfZR1Uh6inpN2WQ1r7c34vW
+xKiRZDpoSRpDkxQaj3KQJeWEsAVSG+y1L9OgbKDjeGE/7U7Gt2fOKih+Aqt0l+xt
+7Go1v99u0VhbyCWiBDF9XCMFzJBx0fIK+RfNHXEkkcoZo8kbnTMGgdoBqfCQZjIS
+HRm9wysljMhdTYRFi1Vx8IrDGKNenc2USS1i31+6CQ6ZHoAZm5wvOxJ/nq3VS1I9
+MJDWTOQIsZQWHVlp+xq3hxBDd33ksQ91p/rsp38VrjYa7Bhd1Vp0xZUB51i4eUJA
+WX1RPKnJ/omwsUXGsQSLAOR/xOYH+CcWTCu2uMd4oX5Zx84xBGECggEBAOpt9oOx
+qHYtZHITFa+9htd9QWPnE5H5oUby/w4gPsymazY1raHETo6HWueByCQnNHDflSWs
+3LOUnrt166wtn2yhaOAw8mrHDCuxwUUfloFyXRal16Sh2QoQDqiaAQrVzg1AM1Oi
+kSBE//OB14YrAUrFFsPZpDHE76/+AOXqp7Ju+XKd0WUeX1ibQzMO6PxuUKKmH4q6
+2gZbwx5olkFb6AVY7dk7Yy0rrp+YxP4Js/JjoxjMu+DB1HOru/LAgCXU54cM3x+A
+VuxE1D+KATVpqzqtDAccEyWys4hfZBlil88Schbenvo53IREB048KXw0mrVHeDDH
+lIPEFwO4Gug+KKkCggEBANE3BwY+/8QDpgJ+EhEIZfraW9z10sQE5L5WIPwDLCnL
+8dXLLW2ayfUtLRe2d5chxqPiAcjJranYR+hZXkbNeRAqWKJWn9QbCrYgqMKz3fyv
+g9hiVS0rTM+rZmAgCxO9Wc6ZSBTcYjyXKe9NCeXYgrpEcNa0bsbdq99d4s/Rym6h
+wofm7c3HAiPBLvduJ7MNnOQpvHTe2wfkf+Meq8K8WPX2UnIQXY+C5EE3FJG2PUrC
+1wryWeVUraLyS3S9pGCUhMlsFJF0RXDp58nbVGvdcfIDfCcH/fjZD3PFpD2vUaJt
+DhGHraxasYC4C+WBm4SkG9P+hYmQD6hVjD7BiewneIsCggEAN5r3owsr00Q3FBvU
+xAeniUuLjB/Oc4yLpaGTwA0D+FTtD0GyOrGulH4koM8W4wRtmuxdmz8iZnI1KG/z
+A7canpC2qJ7TkWI/T8ns9vFkKLYwwGN7/+/n5Ewkvfcxkhles6PryMXBuK7FK0Q8
+E/X1a3/OQ4xHNwroc41DN0XumxNZlcc7WMnYgdLqIJ1DxESCWeIfjy988Y8oe/kA
+0uXy5fnPCPzeLGO1GuQIrd0tUqwxjntZgRlYxEsS3KSugMq8VDtIXVd6xrYYxi18
+1eeHlvZe6PzOyd1WWl2OB7tsGNDeQPBzMxUwaisctIDusihkHeWi66cbYhnL/7TW
+pQnBaQKCAQAxU+QYGOp88M9HbyobUfuZdbqLEnqrNOwp5GzKfoT/JdLTMaB4YzKS
+2B/1o1P3EkOfiD4bdVG45gGuSsPrta6BnTpgrEPq4qVX48NmhLomRcu0TRsAF2F4
+5VSx/VwfP1nZWFKieIPA/XMptORMiQvplxFzzf8AbGuFssEzdqdgBkuzd0NCbVWX
+0IieVh6OHPuM4DpK4/CIn9t3VVfyBi6Db5xowGsO1zGyHqZ+5JT295F0R0fixmBa
+Nv6Le9sx2lKkmxMOaHem879u3IO/GusuwJuZKE09SxBVn5fl41xAC65xe6f7JzcK
+vlovtqtQTtEw3qXllU3bxq/WbBN01qmZAoIBAGjkBPKbUqj6b7dNd5PN/+BHvbP5
+VgNXnx3URS1OVUwqBWi/sFdPCW5JrTAUgsgsLKWzmzxYq/2Ij1CnTHGFSvAd3olL
+6ycmkbk6kguD1mXpvvntJKQwAi9J3z6kNzjoy73PAblUd95TWhpqHwRHVp+C0hUF
+03N2Xn10zADA7zBXwydEk7cFtOuw/pv27zrEqqwwYuNBkjfn9vOxDpT86D9ah66e
+D3CyUM+xkgKp4nzVvbKS8530nxkWwonGJpou8wdHZ8yu5DrPLeRQIBLwy6XAVcdQ
+U4chotKxL81f2UvZ6cA2FGpSQef76mcW643njxzndEfwQ5+twtKBzx0TCH4=
+-----END RSA PRIVATE KEY-----
diff --git a/tests/platform-ci/provider/files/ca/dh.pem b/tests/platform-ci/provider/files/ca/dh.pem
new file mode 100644
index 00000000..3c86bf39
--- /dev/null
+++ b/tests/platform-ci/provider/files/ca/dh.pem
@@ -0,0 +1,19 @@
+-----BEGIN DH PARAMETERS-----
+MIIDDQKCAYEAhh7GNJktPFPgzCHPrWKCSmbhZtO1ypcVJCEZ0VkvpgUpUxAZnRl4
+TPZaQVbYx1gGpvJ6pV341zoeKlFjxK5h8iG5vWYplMk9FzxbI4O7oT2APZcVfR2U
+4lrmQMK7EFDrfRw+CYCuwv0/NxEoMFINRnWtyksLPw3ZtFDdnUAz4Dnu15yAFBW9
+vmOqM72Npx3BnkREOZtB5Fj5FkH9DOVSibuD6zMlUCcVXaX/bON4yrhDGnSctj0y
+mwCpkLK5GkpV24i7pW7LAY+MKXOtDObZHenwdJCBdcAMbNYO5BXuFFxlgJlxRT3T
+j6IH25j9/dRzaO73rh222Qp/EA3YGvhuEAMps/o30flbjZdsiAzn8ajg8NO1qf4+
+aDJDN4xTRVFkTOgTuORqamiLmV7Q3yU4wDFo9jf8H/fD8uIXESy1NOTuKbXjoITL
+D3RVivSlTXHWJ3rSOm13uFY0OVG+gx36Oz5O/hzbtGrebrTadKALS0SkH1cjh6pf
+sCpgVW7BorY3AoIBgAZWGr/JxYe8PtTwPm40EH2r1FUvDlhEEaxF4Ky0VRxh4sdq
+/Vdcvn5ww3KgItkwSSAM0TchtosXtjILXkuRSwJglu15OHOuJHZKsaaD7NeT+AoS
+HOfaiEUJRht9+/lNLbLwxpq8FSCOabWSeqj40rq1P7wQWUh2gyAh9GWc+KO9Lg1w
+Feo0re6IgmPaVhpWS/a2/IguHQwbMdly6EgWD8CqGIK9T4agWqYr4FIUzaEO3SOi
+fe+MPV6U5P1STcs9+UQG9LjzqHHDjMHIm4I3KNXKyM2myl8ncTrmD6uRRiRh6bhn
+wZHMXwk+JsJgbwz8d4T/xDoGNWvonGvnQWgPTaVry1N1TLjgWd+k8UCio0DmxgUJ
+qOz1x7LIGqLGiSOF1xUxA4M50we/JVw8731PLFxZNiSRvKHW/Dh3YsZ2jSoPT+1T
+1l+azCglr1Xz560GEjswedZgsAb1tBm7AFtpJIfujMLRZhhoUZl2rDX1A2h69/HN
+kn86NydyUXjVGYttQgICAQA=
+-----END DH PARAMETERS-----
diff --git a/tests/platform-ci/provider/files/cert/commercial_ca.crt b/tests/platform-ci/provider/files/cert/commercial_ca.crt
new file mode 100644
index 00000000..01df56a7
--- /dev/null
+++ b/tests/platform-ci/provider/files/cert/commercial_ca.crt
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIFbzCCA1egAwIBAgIBATANBgkqhkiG9w0BAQ0FADBKMRAwDgYDVQQKDAdFeGFt
+cGxlMRwwGgYDVQQLDBNodHRwczovL2V4YW1wbGUub3JnMRgwFgYDVQQDDA9FeGFt
+cGxlIFJvb3QgQ0EwHhcNMTYwNjExMDAwMDAwWhcNMjYwNjExMDAwMDAwWjBKMRAw
+DgYDVQQKDAdFeGFtcGxlMRwwGgYDVQQLDBNodHRwczovL2V4YW1wbGUub3JnMRgw
+FgYDVQQDDA9FeGFtcGxlIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
+ggIKAoICAQCyW2rTcWimY288/Ddu7OPvJxShS1RInQqfq8hYy6hEK2QYn656dRDf
+pJXgSYWMvWzSXWJiQkyA8L2+DDilFtccqToqnKE7IwYHlxaeh8OSyZcHl4YCpWJi
+1rc7pysN/l/0pjsp1aKKyHEObnkGMev07uGmI8aOE4Yvd2K5LjBjlov5mNnbYEHW
+j+hWctV6OcphnxVboqtTy+0Ewv5D56snLjUtedyB7Er4ryRjIrWOyd+ZSyi03zov
+oY1xPXS5wSxCc6y6wOKt7/noIg9xxWi7XgSd3OVtPYRU3Io62lMBSzNG6fmos3Mb
+E4ui5ma7IFCJlMEFHirVSBiHn2jwsDtSsrTl0JHJS5ud8Eve5vV0r/n07QsDMhj5
+ol+YDq4+VvOekCAH75GFYkMIzpgcVzC2a1Rq7JTkcnINAF/m47yBLPomItklHv0z
++I23Q+jjTaM2A+40T2K+YRLjFyZwrlCAScjMwPFspnGxfa02miYENhPV1TdTP/ap
+QE7TSl6oFiNrTh7INHGvKgrZYRW598dgAWNKG4zWY8Vj/bIXR1lC8Lp6enQtsIsU
+WiF+zl+xq6bRHg7W419qQowiD349gPXIJGlXEzLqjgLpmpFywrpzxBv6sfZgYT9d
+OPATT+GSiOFYJh9K4/JIxOFBJhzDD6PribjhzydPMTojSJ2Xu7nsOwIDAQABo2Aw
+XjAdBgNVHQ4EFgQUlhC2wfrVFzGrtuzcA0mkO+yn9bgwDgYDVR0PAQH/BAQDAgIE
+MAwGA1UdEwQFMAMBAf8wHwYDVR0jBBgwFoAUlhC2wfrVFzGrtuzcA0mkO+yn9bgw
+DQYJKoZIhvcNAQENBQADggIBAAKdSviiZY8tINlDSVrib0CyDbXymO5uTPRqsf/u
+MC7/DYXlNFy0GHpX4Ls6GcJN5DdZAG0TaoWo5RkNerxqv78sGJsmPqWt55cpBPVe
+NLpFmxcOmLClSDLBhSaq5ggbxULScee7MS1gPHqz1BHXmi7ZJIip4VeVA2e1E52F
+J7E4Y36AJOdZYLgz50YOX/NZwSYBTMy7RI1MiqG/eJf1BjkwtSyO7FTjPXsdKi8x
+HhtRr5udm7Nprq1eJUUDD0+z4kAeTe/LJeuhxc4QKzpVZkE1peW6Wlklp0cdLJud
+7gUsY1GFnNhZDDQ3SW2ZJ/p2OdH35rX96cj+6VClqSQMbH4rL63tICLmAsEzPKwJ
+57bGVUM822n4mh0vn79dam40vMw7wkTKqIKVyLhk30N5/73XczpoLhvVdKDtA1Aj
+C6LseWq4CZsaRSCgk2VsEEYyl7M+BIREuhYOllsILneOTiCOCnU4EdnBQZIHdz3S
+xhduafYXLa7RHkFMfOjtmhogXXpGyaQuS8IsivIowOxKoIZo47IhYRRAghrVN2HK
+ZXrgftIHNfHsFLfe6iiQBgaRn/1w7xOIPVDBqlZKKAMQE7cvum2o6dJo03Sc4dIe
+rvIU1WGNRLM3/AsbZ/7gqwD3INiNUPeuVaiRqvLvXnKfHlR/4s2wZrnKqUgYF1Go
+arXF
+-----END CERTIFICATE-----
diff --git a/tests/platform-ci/provider/files/cert/example.org.crt b/tests/platform-ci/provider/files/cert/example.org.crt
new file mode 100644
index 00000000..7de2982d
--- /dev/null
+++ b/tests/platform-ci/provider/files/cert/example.org.crt
@@ -0,0 +1,31 @@
+-----BEGIN CERTIFICATE-----
+MIIFbDCCA1SgAwIBAgIRAJW2X9xbiBvmbN1kMlRVKtQwDQYJKoZIhvcNAQELBQAw
+SjEQMA4GA1UECgwHRXhhbXBsZTEcMBoGA1UECwwTaHR0cHM6Ly9leGFtcGxlLm9y
+ZzEYMBYGA1UEAwwPRXhhbXBsZSBSb290IENBMB4XDTE2MDYxMTAwMDAwMFoXDTE3
+MDYxMTAwMDAwMFowKDEQMA4GA1UECgwHRXhhbXBsZTEUMBIGA1UEAwwLZXhhbXBs
+ZS5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDFuKIL//hf5cjU
+m18q5fSUyvwtmWREJPaVp+CiWiGJHmxFAiWMGuAFRRChhZ4SYmnEscNda0f6ntPz
+rO+XjhQeA05bIYD9JcFT25Jg4kSX4pQ0+pK2vuHqk4ascZgOOaq4fN8SXD6ZiL3m
+CONDRzbnZVR2LqsdCbEqIuHlo7VK7MO8/9A+rF7wKLVatBtk25uSWMQPt0Q41gw6
+YTV447SltFH3fgUZnNR6p7Oxpsi3qEWlt2vZMIa5xdq4ge2dx1GgC8oSBx1XT/Yd
+qu//GECAH5XsZsAaPXDuor1iTbWELzHyGrQ7V80e67lE2lxoaHxRCOE/NDUU6UXm
+CqXwhdBHarHehOCGSDXvHEwAH5zpV77XOm2bIoZmCjM1fRk5p2S3GmXteCdvCxBP
++2wECnRXuwN2aICrBk7sZ9FieRsYao8GZN/A7ZY24pf7CMEBsgjYktTjAwUb21m6
+vmmzt93dEVJgkd8LASFmoXn+YAIGF0/fD5ZutlsAsBfodoCH9JKBi25nVVTEQW8g
+TzUegTC3PUqnathWv4gZIYDG1ZUDxjk30beNmXV2XudASmP7NG4uSlQwGAEWn+cc
+dzOnRxR0BQpkMMNEV/HmJVuSV5Ak4DkruSXGjLpzi30BjJ8obx85YAusIrhWRUrR
+2oz6gqDUnwq3Nkr3Nk45iOEDC0cZnwIDAQABo28wbTAdBgNVHQ4EFgQUS7rm3WfC
+psxoh4i7q0YbTbMZWuIwCwYDVR0PBAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMB
+MAkGA1UdEwQCMAAwHwYDVR0jBBgwFoAUlhC2wfrVFzGrtuzcA0mkO+yn9bgwDQYJ
+KoZIhvcNAQELBQADggIBAKxeVSMEpUOdBO1zmwd5NtugOlYV3/Gu9GqmUQdlB4FF
+Wt6sKJmYYByNquKT79oJLb9dgUPw8qQiHCB+MAsjB4PpHvMRlpgrcDGsI8+esnfG
+dJny+82aRIFZ2KnNbH8FchcCh4bviaY+DE9kyJNHILk0ujICXabR0G6ArVISTbyB
+C+6BdFyKTT5zj9mtkiTgvZchlKCmOmvh/HeCONu6MGYbqcqp41RA3g1eEjFoROKO
+wmf65VvfOBeb9VydOTICh/bJWRSmAMJqWxbOiV8+Ldufi0vXMcOhEfsyo316xxRq
+1GMb5xVihtCxj/+qBKNoun4k9LTmUvComuPakbtEPT2QbxiTvqCbXsWHPoRwCKEj
+RcFPsxWAnUslzqSl1b0oLaE1zNjBmB/Zd82i2MC4PncLC2hLHtAU1imRZKP6rnHx
+cb1NyFLS0FmIPqZUz9qcY2Tj3GbjqYqRi/sXNKrR2axAUx+jGI/Ie7Zsqa4VZA0A
+ZsiF0BGN3RTCYHuoJbXfEVFQ3o97JGNC3t07u9XhVuC0fjCiQu5PBbMRHSSvtBdN
++LSrhR5j4aiCmppgQSeTtoKSIS3EiOzDtawdewxhffK+co0pGnO3nox+iINvSIQ5
+IevAREmZ2ytjFDU/kVFFlINesFsLRouO37DUf2Kjxaa0RgkCBHpOnTAAD7bXiSaJ
+-----END CERTIFICATE-----
diff --git a/tests/platform-ci/provider/files/cert/example.org.csr b/tests/platform-ci/provider/files/cert/example.org.csr
new file mode 100644
index 00000000..95e8b65d
--- /dev/null
+++ b/tests/platform-ci/provider/files/cert/example.org.csr
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIEqzCCApMCAQAwKDEQMA4GA1UECgwHRXhhbXBsZTEUMBIGA1UEAwwLZXhhbXBs
+ZS5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDFuKIL//hf5cjU
+m18q5fSUyvwtmWREJPaVp+CiWiGJHmxFAiWMGuAFRRChhZ4SYmnEscNda0f6ntPz
+rO+XjhQeA05bIYD9JcFT25Jg4kSX4pQ0+pK2vuHqk4ascZgOOaq4fN8SXD6ZiL3m
+CONDRzbnZVR2LqsdCbEqIuHlo7VK7MO8/9A+rF7wKLVatBtk25uSWMQPt0Q41gw6
+YTV447SltFH3fgUZnNR6p7Oxpsi3qEWlt2vZMIa5xdq4ge2dx1GgC8oSBx1XT/Yd
+qu//GECAH5XsZsAaPXDuor1iTbWELzHyGrQ7V80e67lE2lxoaHxRCOE/NDUU6UXm
+CqXwhdBHarHehOCGSDXvHEwAH5zpV77XOm2bIoZmCjM1fRk5p2S3GmXteCdvCxBP
++2wECnRXuwN2aICrBk7sZ9FieRsYao8GZN/A7ZY24pf7CMEBsgjYktTjAwUb21m6
+vmmzt93dEVJgkd8LASFmoXn+YAIGF0/fD5ZutlsAsBfodoCH9JKBi25nVVTEQW8g
+TzUegTC3PUqnathWv4gZIYDG1ZUDxjk30beNmXV2XudASmP7NG4uSlQwGAEWn+cc
+dzOnRxR0BQpkMMNEV/HmJVuSV5Ak4DkruSXGjLpzi30BjJ8obx85YAusIrhWRUrR
+2oz6gqDUnwq3Nkr3Nk45iOEDC0cZnwIDAQABoD4wPAYJKoZIhvcNAQkOMS8wLTAJ
+BgNVHRMEAjAAMAsGA1UdDwQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATANBgkq
+hkiG9w0BAQsFAAOCAgEAG0IpXLHZpgXtBZHEnGBghrucWnAuhRf0sXauboBVWnwA
+5noESIIX/hNq9DdaBba684u1Qga+lZcFsO1Zh/K1Guu74FTNxV2jCLKcX1T+Ymx4
+uRJ1jcdCc+YB/f+ce+pAhFJei/6sKP//MtYIBHlbe8aGQx1yVPJ5oSb4yS9Hloe4
+DuM0bp6ZXhXFv4YxxxDbaTMs9D46AKnqXV0rLe8WwHH1Mbdxl0bi7roZ3/1NPYsg
+diUMWQlnrR1d1xxUG7x+PJRpPcN3GmZQ0WyZoNrIQA7OLEg6nM8T4sQX5OZFdQrQ
+KQJyX8+Cc8j/UtPrPIPgch6iYX32e+1wTAP82npw1KMELxRsxjX6ERl65apkADFa
+w6LrCFtUQApWY/vZPz88udzSxVytJL4ZrHJxuZEG1WFE3kPY2Ak5LYw/IVxCDFsL
+GVfhb92zkn5iUkULXbwjcTytK3IqXZHl05PW+etGtqbkdh99m8eH1HxolKEgtehm
+l7FMD/JrC0GJWhI4Dl0CpvhAsV61pa8f1KmfGFTt+zpS4epSIItWTuSd4tzaXwNq
+3K1zJaKHs16VWBFuhH5kle4QGRIuDRPHchBQQg0wgy/sfHuzqbcVNotGZ7qzvnRL
+x5eXmWm1HaVKl1NpxbntMY4o9u0WgyzmU0VVsv+oWJj6J88T97rqTNg1Q1Uj8ic=
+-----END CERTIFICATE REQUEST-----
diff --git a/tests/platform-ci/provider/files/cert/example.org.key b/tests/platform-ci/provider/files/cert/example.org.key
new file mode 100644
index 00000000..7ca1c512
--- /dev/null
+++ b/tests/platform-ci/provider/files/cert/example.org.key
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKQIBAAKCAgEAxbiiC//4X+XI1JtfKuX0lMr8LZlkRCT2lafgolohiR5sRQIl
+jBrgBUUQoYWeEmJpxLHDXWtH+p7T86zvl44UHgNOWyGA/SXBU9uSYOJEl+KUNPqS
+tr7h6pOGrHGYDjmquHzfElw+mYi95gjjQ0c252VUdi6rHQmxKiLh5aO1SuzDvP/Q
+Pqxe8Ci1WrQbZNubkljED7dEONYMOmE1eOO0pbRR934FGZzUeqezsabIt6hFpbdr
+2TCGucXauIHtncdRoAvKEgcdV0/2Harv/xhAgB+V7GbAGj1w7qK9Yk21hC8x8hq0
+O1fNHuu5RNpcaGh8UQjhPzQ1FOlF5gql8IXQR2qx3oTghkg17xxMAB+c6Ve+1zpt
+myKGZgozNX0ZOadktxpl7XgnbwsQT/tsBAp0V7sDdmiAqwZO7GfRYnkbGGqPBmTf
+wO2WNuKX+wjBAbII2JLU4wMFG9tZur5ps7fd3RFSYJHfCwEhZqF5/mACBhdP3w+W
+brZbALAX6HaAh/SSgYtuZ1VUxEFvIE81HoEwtz1Kp2rYVr+IGSGAxtWVA8Y5N9G3
+jZl1dl7nQEpj+zRuLkpUMBgBFp/nHHczp0cUdAUKZDDDRFfx5iVbkleQJOA5K7kl
+xoy6c4t9AYyfKG8fOWALrCK4VkVK0dqM+oKg1J8KtzZK9zZOOYjhAwtHGZ8CAwEA
+AQKCAgAht6KquTP55o2g8/3+qshSt2rZu9bFaChEzSQZi5U8dNuxyPPuOIcLXwO/
+B7I1IGM5D7dpLupPatZqL4uMJMZ5d8bc85GzmcSmMEN+EhfwbssnXbO3RkXwYsgM
+kDKF+n+KhoDj+KcUN6VqnQlkZ7iNLVKB9ONpSEXWEazEJG6+IDIhAN7aUTq/abHD
+jgM959VX15tXssEHkDj1m64qt2oO9/kiY3MrMvtpD0Atg2unJiL6Z5UUrJnNBFiQ
+Llf/GAZrbJdBC8WNJi2qUYQr1E7rindeoQcRcnjXuRjisq3JpOK3jqY9mHN6Wmh1
+vWcUxvysNP90b8q9jipFWHuD0M37kq+BLn5Bub0ypiIkId0CUnAB9MBYcBJlYhai
+ZwI1fe0uGFD7XlJbHexTgnLreDAo3FR9CIUDo2HUWqmUNWadAl/rPNRe6+QDDvmP
+5v4HiFmSuCjZJOu9x2z/ly1JzM+iCUp+q6BxYYYW/5tDLYAw7sl1uaiLTzZuhrrM
+PlO6DNLAQhMn29jeszPHt7iXHdHAHAuYSeHpfeqnAV1qB+6x7UFVZjbDxXkt/Sn0
++LvCzJUQOwQNlnnzIwVdn8phS3r9TN2rI3dtlvPMWJqgBiheJ9qn2tHjjoPETt9I
+hfvw949Gi65D+AFSzowjNUFwDXzphOwETv5tpKCRROhdBRBdwQKCAQEA+L9RsVqT
+F+7HyGza+F53mgED5SQoS52vRA2OiAbCgiNjY7JH7bqIpuO4RlgqKo+GKoboCP6D
+1CmVGUm/Z8wYspzQs15O/jUO1bZ8KFREt8TquxFtikwyvIXQhUdJhZYUnhfMzV3O
+sH1blWhJnSX21rxJWlrkN0I8Zdkl6mjvFa97Kr9UA/pdZd0qgIw5Vi7MFLPC7j2Q
+YmTPhNsb0oZMJHGvwENUmuCQDhGiRhQV06R963mTMvxY7LWqUVf6dr7xg89Qt5Yo
+AdSHllOxHOMTAa+kZNF1N8UM9S2iJSn6ZeUEOXOJEuosghpE/QIuvo81Txm63G7e
+BjU3H7cFqDetfQKCAQEAy3xy2cQ/+GlSIbwXrzBr483Z0jXnvknlCJMh+NCTXObk
+idOhhnIuZu+JoAovv2AfKNPvYXotmb1xxmws5RSrlZDGiQQzEwvJPeLN2DnUGqzc
+ZPenu64Je6v9L35iRMF8vyx3xf27FC4zmR6nLuZbgfEfQdModqCbTpzh23Cl3mkM
+IZFYPhhfnh/pcwccuqfOn0Adt+1X3jvp3QzCh1jkEjhaRB5qjt58nlmxA2EKYv1w
+OzSTH9owqsCMmdrqzR7iKh59LrfOfggJbhHCyrORZ/S8h5lwqIk3+zLMrwGSvkXL
+tuKLXtkX/Xy98cbHwk5M/bf3hH6I5njlsssFsS8+SwKCAQEAxCzu2raaJ1fUDAd9
+sj+eh8ChN8gKV4hmv38Jl9Hs+QG70ta5z407VJNns2K47pP+te9rdBx2D48z3ZvB
+7rSSDduK5MtN9UIXDwk6Zfv/rgcJMLuP7nAl23SVfWc5Xrd8TypqBNUkuyBCaFS1
+KdDVGYmpOC9SqRn91D0rn/FeDXY15wK52eFMY5fHe1YbqhKCNRmIdKftBQyIdTjw
+elocFunqN/Fh+jt8oPvbRPV2OVITVPCu3JkT8KtdRYXjLF9uzgtkl0U/DCJ3RGGA
+301eogfJ2REwJumrTHnO1QyERHQXns+1nUs+CuV43ykngHYlDts1+b8eLzss3EBV
+n9M5aQKCAQEArqKmmtg/on0ZPNSFaxfecEq5lxwmQHyAsMQ9UqIG5qNOHi9fn9gc
+lMEdVxmG8vKWq16AQiMuQZSBsa4jNZNw0tLGYM8W2lCyLIea6+htbVtPZuPYs0zg
+3J+1ke4gfiukWRnbzTM+PEqOg+n3x1txy2pZzg9f2bdqsqQXflIGOIPlImXv2pLm
+dPmkS9Edyd+8h5XqK3DpiVPYGJsb1Dbove5ZIb8M6oJtZyVIssK0vFIP4O/1GFAU
+lmbcBCsKenH33ff+rXqYIDfbh/h8OaS0tQgoSSPZuPrS7aYiXku2Wc/izplMzWD5
+otZM2dQkmlDC6LjbF33VFh9J2xE8WF1YUwKCAQAeJYro7nBxM0eOmof1ty24UPfg
+jx72sH/FpgKIyvZ4yQoreNUc4TVsy5QMIVd0G966CRgvzaE0vcBHm//7YCXHtIa9
+ihqmYDo7SoaF7nZNjxJIxyQVPY0+Kntkwz0XAX0IbJ0nMx+3x6d5UhbQbxFVKe7X
+5WmOMb0ro9NLaCvh5IUxSHsG/a8hYRqoX3tZbPRvTJMZMTMxWslsscWINNu/80KS
+ggpD9Uu9hdVwT7yavl6JKC3ypRdBzmpKZfiLt5CTFex+XGIgKLHVqbHxXu487YsL
+AlexBvk1/RKMTHIgUl7uMmaJsUSD+ME4SWuU9cW115kwp+JBMXES4ZfWnRHZ
+-----END RSA PRIVATE KEY-----
diff --git a/tests/platform-ci/provider/files/mx/dkim.key b/tests/platform-ci/provider/files/mx/dkim.key
new file mode 100644
index 00000000..0dc069c6
--- /dev/null
+++ b/tests/platform-ci/provider/files/mx/dkim.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEAyrO7MRuCSyM2Iz5fXT17q2rpP8U8m4zE98gShMAzzKFOUjk2
+WldOzVDrNVMc6nlIkBifNpk85SCdgc5GRCWMMHifbKTWjduK9pCTtvIOVPq9H0Ak
+mgqXEgoVurn9gIxfUk2zpr+TzE9r7/U+O8ffmtmZMKbWldvvqwfm3rLRBUpvi9EH
+KWjmqEp9I4x0mXzkwRzoyrN0xZB2pLJJVlg/edeI7omPaqRRbf/OWQb1CHK5g3Yo
+l1zlnBEG4X7BsIqqvaKfPT6Zp1AM/QP5qJESjvLyEf5eguWSeU/kyUCtSLZqigxf
+sYqcClN7gWiQ1j5d3vkpv6xEvLZOL9Jhg00FnQIDAQABAoIBACZMxX7m4ryNv6nz
+HBPDDT37am0ZOHVvqLvkutMIegEdLW5Nzx5Mxt/2fSrLNHh9SB+p91Naqu3kNr6T
+GiXALnfuIrllgAC3zc7+zFpR7DFUWy2vcfsFKzxGWYq5n9ONMmmbsuk745JEI3Ho
+lcS35GEe4loV/A++yc84JABKK0JjUpXeafXr21dNGNe0mv0j/0zM8jWzZ6QFneeG
+0MpZMP4rR+STe70n3Cgqoue0IDejt+N/jA6Js6cYV3zQgDX8sQVPR3saTaYMrjHH
+UV7qj+tvzgUxMp0XEzhAxNKsHpGSwyDt0X2RPFj1Jye9OSLHV+p4Mbjij+9p8ZUB
+robaTGECgYEA/SWNspWkyNSGE9YNErxiFL6WUGaXgJgoPNA6R/i5KoC/7jv8gn4X
+TeZ9a5b+JHt5fSy3Ph1Pje8T3ZLDl+7Oahr2/Xh+pmcQ3Tb3gEA30e2dmQgLGfcD
+wIa/wr+FpW/DofdjXtdVMyn9urnTiOYbPaFVY82z4OcQmFF/kMfSKAkCgYEAzPyf
+FToGs1BmEz416js7QsXNdr3sQMONsBRVw3H26qWbYDxaT6piHBwNfw2N2awr1WFc
+6XuQ93xymHHwNfb4vjTOI+vJLPwZo3P8KOZwDwjUpVL8OTDSXt87yJMbcmexLATS
+Asmnoe5h8rVXHc+BJ8UR0HdkJN0SVD/LKlySTfUCgYAVm5D+v1szcUCIjOrMwJu2
+nZYDAt7HsTUuC7AN2KMlh5vaX/Brywt+MMBf4KGMx6VVE+4INURHHzMY5KAhZdbk
+o6yVciWNWprL5xc1MUYSey/Kki8wZi9Bzb6shuCHgIS4XH905vh0x47K03XE569H
+kW/Sdwp1lgOKnNpAp22+0QKBgEZQIQFW9hVr7peLL1M5Hgq5btDcNL3CVkefsgto
+fBng1HseOJw7BYw+0yJRs+aGeEKpMwWjrQY3WdeQvaTFIm2cD1mi907G6sR2dHhT
+Ev0VOlu7K2kypfaE/CzAyRllGBDRVng+U5HoAxENwuQm2Vaa8pFfYqqCalcbysSt
+HEJBAoGAS/liytZxCp9v8RCNyAOo8JPHPw/EdPGxuk5lP7m4iNbB1O9DqvEEmR4l
+RzgXcAPgIAy5+TEwUQwarqbHe8fgmGziMP4xtntN2X+epreD1fWqfTHphO2njaDT
+SKMlO5hUVlQXc7/J6DRbFzWFlEngvqNx+PzM5VlEYc7mK6xRSjo=
+-----END RSA PRIVATE KEY-----
diff --git a/tests/platform-ci/provider/files/mx/dkim.pub b/tests/platform-ci/provider/files/mx/dkim.pub
new file mode 100644
index 00000000..bbd32086
--- /dev/null
+++ b/tests/platform-ci/provider/files/mx/dkim.pub
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyrO7MRuCSyM2Iz5fXT17
+q2rpP8U8m4zE98gShMAzzKFOUjk2WldOzVDrNVMc6nlIkBifNpk85SCdgc5GRCWM
+MHifbKTWjduK9pCTtvIOVPq9H0AkmgqXEgoVurn9gIxfUk2zpr+TzE9r7/U+O8ff
+mtmZMKbWldvvqwfm3rLRBUpvi9EHKWjmqEp9I4x0mXzkwRzoyrN0xZB2pLJJVlg/
+edeI7omPaqRRbf/OWQb1CHK5g3Yol1zlnBEG4X7BsIqqvaKfPT6Zp1AM/QP5qJES
+jvLyEf5eguWSeU/kyUCtSLZqigxfsYqcClN7gWiQ1j5d3vkpv6xEvLZOL9Jhg00F
+nQIDAQAB
+-----END PUBLIC KEY-----
diff --git a/tests/platform-ci/provider/files/ssh/monitor_ssh b/tests/platform-ci/provider/files/ssh/monitor_ssh
new file mode 100644
index 00000000..81ff75e4
--- /dev/null
+++ b/tests/platform-ci/provider/files/ssh/monitor_ssh
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKQIBAAKCAgEAxG2QA8pYOcU8ViBfg5QFTS7jboEr7G9UFpUisHyGyY5rJZ/9
+N04UK7GRtCYS+Rd/9nqWmoV4StdVH9rXFLHxPvVH3z/jHGDir2fRpkywaWGVMiU/
+F0QSv67YbooNOdMaTacapWEwmwjO0ApDrHlqdBZwGb/gh1wW7lUpBgzHN+ZzNU8Z
+lVh7icYgqv114NAjfzA+VGOwVpCW1q3pR8c08lSJgfnMUZ2gEmjJPizC6Za1RvIx
+kzEkRVnmtlN4i62J6aSwLKMDXlyfjailFzfZaPdjlA5ijMvGZXo/zUWCaQJS8k1c
+0vkj1arGHZ0J/t5so90qycD9j5Q8s8nMYZ5rbc1C7uPsx2ywqCVwd0St1STcNq4d
+FTZEc5edaKFGNjC0fzp3ZGzIvEyAdJMimXFcC10JP+TBgorQmNrH7iGNfsrr8b0P
+65Xr9P3sopEOSknsONoVBiH/9MoDd0CuVY9A0ZmmExB8qf2uc9ll2/SLcdX2jon9
+mX5zBI2ENcGnezHgGv4jp3PKaBokmOaOrQWND70/bgXooDmTU7+O63SemPoJhdwS
+CiI4QU2Vi5aEEOz966q+hi2xTXd96G9/qoOjdlBBZThwSsF6FDGdGjdUe+qkfGQb
+aybWHXbdHJamAWnqAtfwHDbSkShKwfJK+BqukkHGoe+l0zXrfUP+DZ5rcxsCAwEA
+AQKCAgEAk1rNysok3VHFLacjgAWu5HPkUaW9WaU6o6ZFW7hPNS0N3C/lOXPtVcnj
+0A0v9oVWjYTxLgIqd5qKVVdKOlAy9lPzEttOeJ+F7qgncmXdgXCfB/tBFScQGZQE
+8QfHXDWtacuOBbqfR+6XlyHcGqsK3QNoHSkAOwsueKSSHePAH4NVsgwg2RSDuJtV
+LnDt2TTLLEL4vz35rzbQsUPN2PbsFU6tyT+nsyJYTvck4OubXLieTRarcgxPdWc3
+2FdN+xq4dvoA37t6b3N0jkSRdJWFF2Ve4lbYP18u+jl3W3pllnkT2ImItQwJgeSW
+suh38ybQwSzNSITqsqc10nn0RNcfJvLK5CscX3xL8RYMcMAk9uxIAa5pACKnVwVj
+a92pP0838E+3AP5yaAMNZg5xeC5jlZCOS82SM7xKBQacmeLHh7DmjZPqngzSHyDu
+UyIlRn4dYB7G51QuQ1ZOZui/uODpUDcmh0EvAN2P/FDfhE4yvdJ5jIznNci7qEbG
+GZ7S2RcMbexl7RSo6hvhXp8D4vuAgWMPpVXaIBf/G9BX+YBwpo90ohB+LRmnOdWA
+FJfm7tis4ANx6XT+aWwvFEc6YTxV2ECq9yKcm8Ws0dhifDE2eKbWD9sL41p2Ghaa
+NWGUJ55wgsidl6r9roA6spROMvaM0bRZFLE5OcIhuE9D7wnHD0ECggEBAO8t7v31
+23y8BSY4WuIN5JzNkcrY+d+P/WNM4KjATUw08Yzg29u1Ebh2JJLoSAHEyV6ngNJ3
+SO9uOhrH6mfBW+CC5RT1rE5g3G9bz8ZI1scMDJcXYfIHqhOynP3RbiaXR39fja+l
+u7lW76mVM3qqET5oj4LBxU7eUyXzQqV7UoQyHBKNW0TALL/bRVdTabUyGprVbo70
+Ww75j2JqD2hK/803Ebi+VkkN1NcGiLdaWm9qvgTYiRENsSb4UjZX2EalKZERXcHy
+e7VCKdOVWwbWpDdTG1mg/bI+EdQvHXXuD7yDIKs3z3d2sYeqdMhQ7rJE3Ra/P/0p
+Kim+GTnDlVOwfXMCggEBANI99A6zp1iu2tkTIExEYin189BxtRg5EQRVm1CJl15O
+RrfReUVuhvRSagQlXz7qnNiETG73F7ouGu4QTLYP0Lmjxa6UP7Lu0mQ23al2/OXE
+1agzSLGTZv50sRE8f0Oo7fi/n++QVUfQ1MRM7yMtZnrl9X85+2KKQLYI4Gwb3yUU
+geJMaX8X5s6CwffRYe9BtYb3q2o1ySTbMIcdL2aQbAorBz33DNkp/SyLwEiuaogt
+jb93KCtoMiOyYs6gRMI3MxLGg6dLGbSB8QwBxCCV87UoUAS7IODqAHJwnacYKhEV
+0EcA0oDjT9cQomX6lQQFmxXE/2A96P5e8wVPyTi5SbkCggEALkIA/ecF6yrmCA1Q
+LnYnZ9guQUATm5RamlDtBlYi3QFEUk3O18A+TCG1UyBPhOANXhwhQxNE7OGxpSpT
+AHwaC+Lk8VfOWl5LY9Iq7ht6RobjDHm+PLQUxbh+umw91ILflhfh7D2uf9r7gR3V
+Ff08VoiccNqPEYDYLffNRPoD7INQgJoMM9DDFtwOniQIxr2I/bcXqdhCoDPN8me2
+0SHoNUVYTRWq1HgzWN7vpB56bSAE3iUO5VhzkajnJZF5x7f7wQ3Nx0vhdx3zvvMc
+5sauffC50mzbhBSTGCmAliVTr87gi5zAqEcxcJ6b9X4JnDrLU7Hra0gB2o7kjBJy
+l/wDVwKCAQEAjcICNouCEbTMkUNpKqONQNe6zthsj+mihLaoI7SyYH8NBdJzH5K3
+4jNTknoUb5rHqOIDm2p2EC4YMF7DKpsdVJ6NovoIvUB0kefArAwz10VR/ridkkZe
+UsIhxgpxkRBtbKTgVSqPpf20CKwLLj/lcoZtcpyI2Nd5bIQtthdQ7XKXZRu6olxe
+Xu4hlVQT4bv/hwKmDNY5SuWUIfZWyKQmhPCgUHKsshyyvX95ZkhcQnfctLXGWwZF
+kHYuUz4TPpTzlfxONtXXfjODcWIbeRFCouqMkbQPJjgBlyhB1LHhY2W+6rEuPoOG
+iO+JYJOGOJEDEbmjq6Py3tjsqa8zcVDV2QKCAQB2O6qmJCgn6os9ladkcnpTO5oD
+I+poz8PdwPcoB+KxW/Jj759mmBCeFh0HtZlct9JMexWD8cB2+x0412y9cZC2XduK
+tX0tci1WhZTR9XEo2BjzNJBRvRxSDOz1Fk0y2D9fhsVrPkS6qZ5/+kt/O6cgyFxb
+4m0+2V4qnJcF075PF4G/Raq8sKKuPOg8EHTnVRZgyL7vmrprRlPqpq8CYJUwPX53
+ddK3exo96qLvYCf7qKtQvDedLbllrqgOE2xrhuPPAmaXjto2dHb/7NCVBoccL5mN
+SPFLi0V6EvPUlYZZ/e0XQafMT20/moMWnuIH1igkXPkw/hwpBLGVVEsLv5hl
+-----END RSA PRIVATE KEY-----
diff --git a/tests/platform-ci/provider/files/ssh/monitor_ssh.pub b/tests/platform-ci/provider/files/ssh/monitor_ssh.pub
new file mode 100644
index 00000000..8be32927
--- /dev/null
+++ b/tests/platform-ci/provider/files/ssh/monitor_ssh.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDEbZADylg5xTxWIF+DlAVNLuNugSvsb1QWlSKwfIbJjmsln/03ThQrsZG0JhL5F3/2epaahXhK11Uf2tcUsfE+9UffP+McYOKvZ9GmTLBpYZUyJT8XRBK/rthuig050xpNpxqlYTCbCM7QCkOseWp0FnAZv+CHXBbuVSkGDMc35nM1TxmVWHuJxiCq/XXg0CN/MD5UY7BWkJbWrelHxzTyVImB+cxRnaASaMk+LMLplrVG8jGTMSRFWea2U3iLrYnppLAsowNeXJ+NqKUXN9lo92OUDmKMy8Zlej/NRYJpAlLyTVzS+SPVqsYdnQn+3myj3SrJwP2PlDyzycxhnmttzULu4+zHbLCoJXB3RK3VJNw2rh0VNkRzl51ooUY2MLR/OndkbMi8TIB0kyKZcVwLXQk/5MGCitCY2sfuIY1+yuvxvQ/rlev0/eyikQ5KSew42hUGIf/0ygN3QK5Vj0DRmaYTEHyp/a5z2WXb9Itx1faOif2ZfnMEjYQ1wad7MeAa/iOnc8poGiSY5o6tBY0PvT9uBeigOZNTv47rdJ6Y+gmF3BIKIjhBTZWLloQQ7P3rqr6GLbFNd33ob3+qg6N2UEFlOHBKwXoUMZ0aN1R76qR8ZBtrJtYddt0clqYBaeoC1/AcNtKRKErB8kr4Gq6SQcah76XTNet9Q/4NnmtzGw== monitor
diff --git a/tests/platform-ci/provider/nodes/catalogtest.json b/tests/platform-ci/provider/nodes/catalogtest.json
new file mode 100644
index 00000000..05703666
--- /dev/null
+++ b/tests/platform-ci/provider/nodes/catalogtest.json
@@ -0,0 +1,39 @@
+{
+ "ip_address": "1.1.1.1",
+ "openvpn": {
+ "gateway_address": "1.1.1.2"
+ },
+ "services": [
+ "couchdb",
+ "mx",
+ "soledad",
+ "webapp",
+ "monitor",
+ "openvpn",
+ "tor",
+ "obfsproxy",
+ "static"
+ ],
+ "tags": ["catalogtest","development"],
+ "static": {
+ "domains":{
+ "example.org": {
+ "tls_only": true,
+ "locations": {
+ "front": {
+ "path": "/",
+ "format": "amber",
+ "source": {
+ "type": "git",
+ "repo": "https://leap.se/git/bitmask_help",
+ "revision": "origin/master"
+ }
+ }
+ },
+ "cert": "= file('cert/example.org.crt')",
+ "key": "= file('cert/example.org.key')",
+ "ca_cert": "= file('cert/commercial_ca.crt')"
+ }
+ }
+ }
+}
diff --git a/tests/platform-ci/provider/provider.json b/tests/platform-ci/provider/provider.json
new file mode 100644
index 00000000..218ff529
--- /dev/null
+++ b/tests/platform-ci/provider/provider.json
@@ -0,0 +1,18 @@
+//
+// General service provider configuration.
+//
+{
+ "domain": "example.org",
+ "name": {
+ "en": "Example"
+ },
+ "description": {
+ "en": "You really should change this text"
+ },
+ "contacts": {
+ "default": "root@example.org"
+ },
+ "languages": ["en"],
+ "default_language": "en",
+ "enrollment_policy": "open"
+}
diff --git a/tests/platform-ci/provider/tags/catalogtest.json b/tests/platform-ci/provider/tags/catalogtest.json
new file mode 100644
index 00000000..0967ef42
--- /dev/null
+++ b/tests/platform-ci/provider/tags/catalogtest.json
@@ -0,0 +1 @@
+{}
diff --git a/tests/platform-ci/provider/users/gitlab-runner/gitlab-runner_ssh.pub b/tests/platform-ci/provider/users/gitlab-runner/gitlab-runner_ssh.pub
new file mode 100644
index 00000000..3e72b70f
--- /dev/null
+++ b/tests/platform-ci/provider/users/gitlab-runner/gitlab-runner_ssh.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDEtniDgIYEm4WtGgiQsZKBpY8x3tbzDBIoMLbZT496juCu4c3f+F5KkMPLmYRPcAupF8tVf+j7Fns7z69PuTjdGfe/cA9CTw/4sNAu3iLpunGR0d2Wtctez5mwz13bKRu9fck3H9p2F9Z47vMKtRTJJ6iIgaUVWU/eFd/MSMJeUVd2ns4Wr7SkHCBB3PV+QL1xl4+AZsUtnGVQ5cE4MZZFia/g6SlrKQYFtLRVIIpDuuaDSvULg1BFMhSCBDNygts8dKTJsCEQYeGVvHZaDwtKTnMqEIwBP4TkIoP+YWnZTPrGywFEJOlZ8b+4HdgdUAFLcFCycWMM9nVcWX7P2lIN gitlab-runner_ssh
diff --git a/tests/platform-ci/setup.sh b/tests/platform-ci/setup.sh
new file mode 100755
index 00000000..39ef3130
--- /dev/null
+++ b/tests/platform-ci/setup.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+which bundle || /usr/bin/apt install bundle
+/usr/local/bin/bundle install --binstubs --path=/var/cache/gitlab-runner/ --with=test --jobs "$(nproc)"
diff --git a/tests/server-tests/README.md b/tests/server-tests/README.md
new file mode 100644
index 00000000..29db2e06
--- /dev/null
+++ b/tests/server-tests/README.md
@@ -0,0 +1,44 @@
+Tests for Server
+---------------------------------
+
+The tests in this directory are run against the servers of a live running
+provider.
+
+Usage
+---------------------------------
+
+To run the tests from a local workstation:
+
+ workstation$ cd <my provider directory>
+ workstation$ leap test
+
+To run the tests from the server itself:
+
+ workstation$ leap ssh servername
+ servername# run_tests
+
+Notes
+---------------------------------
+
+server-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.
+
+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
+---------------------------------
+
+server-tests/helpers/
+
+ Utility functions made available to all tests.
+
+server-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/server-tests/helpers/bonafide_helper.rb
index 5b886228..5b886228 100644
--- a/tests/helpers/bonafide_helper.rb
+++ b/tests/server-tests/helpers/bonafide_helper.rb
diff --git a/tests/helpers/client_side_db.py b/tests/server-tests/helpers/client_side_db.py
index 2f8c220f..2f8c220f 100644
--- a/tests/helpers/client_side_db.py
+++ b/tests/server-tests/helpers/client_side_db.py
diff --git a/tests/server-tests/helpers/couchdb_helper.rb b/tests/server-tests/helpers/couchdb_helper.rb
new file mode 100644
index 00000000..efb2c2bf
--- /dev/null
+++ b/tests/server-tests/helpers/couchdb_helper.rb
@@ -0,0 +1,143 @@
+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)
+ options = {:username => 'admin'}.merge(options || {})
+ 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/server-tests/helpers/files_helper.rb
index d6795889..d6795889 100644
--- a/tests/helpers/files_helper.rb
+++ b/tests/server-tests/helpers/files_helper.rb
diff --git a/tests/helpers/http_helper.rb b/tests/server-tests/helpers/http_helper.rb
index 0d0bb7d5..0d0bb7d5 100644
--- a/tests/helpers/http_helper.rb
+++ b/tests/server-tests/helpers/http_helper.rb
diff --git a/tests/helpers/network_helper.rb b/tests/server-tests/helpers/network_helper.rb
index 713d57aa..713d57aa 100644
--- a/tests/helpers/network_helper.rb
+++ b/tests/server-tests/helpers/network_helper.rb
diff --git a/tests/server-tests/helpers/os_helper.rb b/tests/server-tests/helpers/os_helper.rb
new file mode 100644
index 00000000..9923d5b1
--- /dev/null
+++ b/tests/server-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/server-tests/helpers/smtp_helper.rb
index ea7fb9fa..ea7fb9fa 100644
--- a/tests/helpers/smtp_helper.rb
+++ b/tests/server-tests/helpers/smtp_helper.rb
diff --git a/tests/helpers/soledad_sync.py b/tests/server-tests/helpers/soledad_sync.py
index f4fc81ae..f4fc81ae 100755
--- a/tests/helpers/soledad_sync.py
+++ b/tests/server-tests/helpers/soledad_sync.py
diff --git a/tests/helpers/srp_helper.rb b/tests/server-tests/helpers/srp_helper.rb
index b30fa768..b30fa768 100644
--- a/tests/helpers/srp_helper.rb
+++ b/tests/server-tests/helpers/srp_helper.rb
diff --git a/tests/order.rb b/tests/server-tests/order.rb
index 14aad9be..14aad9be 100644
--- a/tests/order.rb
+++ b/tests/server-tests/order.rb
diff --git a/tests/server-tests/white-box/couchdb.rb b/tests/server-tests/white-box/couchdb.rb
new file mode 100644
index 00000000..44a2769b
--- /dev/null
+++ b/tests/server-tests/white-box/couchdb.rb
@@ -0,0 +1,169 @@
+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
+
+ #
+ # 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/server-tests/white-box/dummy.rb
index a3e8ad68..a3e8ad68 100644
--- a/tests/white-box/dummy.rb
+++ b/tests/server-tests/white-box/dummy.rb
diff --git a/tests/server-tests/white-box/mx.rb b/tests/server-tests/white-box/mx.rb
new file mode 100644
index 00000000..0eeaacd0
--- /dev/null
+++ b/tests/server-tests/white-box/mx.rb
@@ -0,0 +1,271 @@
+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'
+ pass
+ 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."
+ end
+ 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_05_Can_deliver_email?
+ if pgrep('^/usr/sbin/clamd').empty? || pgrep('^/usr/sbin/clamav-milter').empty?
+ skip "Mail delivery is being deferred because clamav daemon is not running"
+ else
+ 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
+ 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/server-tests/white-box/network.rb b/tests/server-tests/white-box/network.rb
new file mode 100644
index 00000000..a08cdfbe
--- /dev/null
+++ b/tests/server-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.exist?('/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.exist?(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/server-tests/white-box/openvpn.rb
index 170d4503..170d4503 100644
--- a/tests/white-box/openvpn.rb
+++ b/tests/server-tests/white-box/openvpn.rb
diff --git a/tests/white-box/soledad.rb b/tests/server-tests/white-box/soledad.rb
index d41bee58..d41bee58 100644
--- a/tests/white-box/soledad.rb
+++ b/tests/server-tests/white-box/soledad.rb
diff --git a/tests/server-tests/white-box/webapp.rb b/tests/server-tests/white-box/webapp.rb
new file mode 100644
index 00000000..da1ec8c5
--- /dev/null
+++ b/tests/server-tests/white-box/webapp.rb
@@ -0,0 +1,114 @@
+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 '^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_privileges(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
+
+ #
+ # checks if user db exists and is properly protected
+ #
+ def assert_user_db_privileges(user)
+ db_name = "/user-#{user.id}"
+ get(couchdb_url(db_name)) do |body, response, error|
+ code = response.code.to_i
+ assert code != 404, "Could not find user db `#{db_name}` for test user `#{user.username}`\nuuid=#{user.id}\nHTTP #{response.code} #{error} #{body}"
+ # 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
+ # 401 should come as we aren't supposed to have read privileges on it.
+ assert code != 200, "Incorrect security settings (design doc) on user db `#{db_name}` for test user `#{user.username}`\nuuid=#{user.id}\nHTTP #{response.code} #{error} #{body}"
+ assert code == 401, "Unknown error on user db on user db `#{db_name}` for test user `#{user.username}`\nuuid=#{user.id}\nHTTP #{response.code} #{error} #{body}"
+ end
+ end
+
+end
diff --git a/tests/white-box/couchdb.rb b/tests/white-box/couchdb.rb
deleted file mode 100644
index 85dc6840..00000000
--- a/tests/white-box/couchdb.rb
+++ /dev/null
@@ -1,186 +0,0 @@
-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/mx.rb b/tests/white-box/mx.rb
deleted file mode 100644
index 6c0982ce..00000000
--- a/tests/white-box/mx.rb
+++ /dev/null
@@ -1,186 +0,0 @@
-raise SkipTest unless service?(:mx)
-
-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?
- 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 five times to get a valid doc
- for i in 1..5
- offset = rand(doc_count) # pick a random document
- count_url = couchdb_url("/identities/_all_docs?include_docs=true&limit=1&skip=#{offset}", couch_url_options)
- assert_get(count_url) do |body|
- assert response = JSON.parse(body)
- record = response['rows'].first
- if record['id'] =~ /_design/
- next
- else
- address = record['doc']['address']
- assert address, "Identity document #{record['id']} is missing an address field. #{record['doc'].inspect}"
- 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
- break
- end
- end
- end
- end
- 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
-
- #
- # 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_05_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
-
- 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
deleted file mode 100644
index 436fc8a8..00000000
--- a/tests/white-box/network.rb
+++ /dev/null
@@ -1,90 +0,0 @@
-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/webapp.rb b/tests/white-box/webapp.rb
deleted file mode 100644
index 68f3dcd2..00000000
--- a/tests/white-box/webapp.rb
+++ /dev/null
@@ -1,134 +0,0 @@
-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/configure-leap.sh b/vagrant/configure-leap.sh
deleted file mode 100755
index 9ddee039..00000000
--- a/vagrant/configure-leap.sh
+++ /dev/null
@@ -1,92 +0,0 @@
-#!/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/vagrant.config b/vagrant/vagrant.config
deleted file mode 100644
index e601488d..00000000
--- a/vagrant/vagrant.config
+++ /dev/null
@@ -1,22 +0,0 @@
-# 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"
-